1. 文本Widget

Android中,使用TextView,iOS中我们使用UILabel来显示文本;
Flutter中,我们使用Text组件控制文本如何展示;

1.1. 普通文本展示

在Flutter中,文本的控制显示分成两类:

  • 控制文本布局的参数: 如文本对齐方式 textAlign、文本排版方向 textDirection,文本显示最大行数 maxLines、文本截断规则 overflow 等等;
  • 控制文本样式的参数: 如字体名称 fontFamily、字体大小 fontSize、文本颜色 color、文本阴影 shadows 等等,这些参数被统一封装到了构造函数中的参数 style 中;

    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Text(
    5. "《定风波》 苏轼 \n莫听穿林打叶声,何妨吟啸且徐行。\n竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。",
    6. style: TextStyle(
    7. fontSize: 20,
    8. color: Colors.purple
    9. ),
    10. );
    11. }
    12. }

    可以通过一些属性来改变Text的布局:

  • textAlign:文本对齐方式,比如TextAlign.center

  • maxLines:最大显示行数,比如1
  • overflow:超出部分显示方式,比如TextOverflow.ellipsis
  • textScaleFactor:控制文本缩放,比如1.24

    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Text(
    5. "《定风波》 苏轼 \n莫听穿林打叶声,何妨吟啸且徐行。
    6. \n竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。",
    7. textAlign: TextAlign.center, // 所有内容都居中对齐
    8. maxLines: 3, // 显然 "生。" 被删除了
    9. overflow: TextOverflow.ellipsis, // 超出部分显示...
    10. // textScaleFactor: 1.25,
    11. style: TextStyle(
    12. fontSize: 20,
    13. color: Colors.purple
    14. ),
    15. );
    16. }
    17. }

    1.2. 富文本展示

    单独加样式?可以利用分片来进行操作

    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Text.rich(
    5. TextSpan(
    6. children: [
    7. TextSpan(text: "《定风波》", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Colors.black)),
    8. TextSpan(text: "苏轼", style: TextStyle(fontSize: 18, color: Colors.redAccent)),
    9. TextSpan(text: "\n莫听穿林打叶声,何妨吟啸且徐行。\n竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。")
    10. ],
    11. ),
    12. style: TextStyle(fontSize: 20, color: Colors.purple),
    13. textAlign: TextAlign.center,
    14. );
    15. }
    16. }

    二. 按钮Widget

    2.1. 按钮的基础

    Material widget库中提供了多种按钮Widget

  • FloatingActionButton

  • RaisedButton
  • FlatButton
  • OutlineButton等

    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Column(
    5. children: <Widget>[
    6. FloatingActionButton(
    7. child: Text("FloatingActionButton"),
    8. onPressed: () {
    9. print("FloatingActionButton Click");
    10. },
    11. ),
    12. RaisedButton(
    13. child: Text("RaisedButton"),
    14. onPressed: () {
    15. print("RaisedButton Click");
    16. },
    17. ),
    18. FlatButton(
    19. child: Text("FlatButton"),
    20. onPressed: () {
    21. print("FlatButton Click");
    22. },
    23. ),
    24. OutlineButton(
    25. child: Text("OutlineButton"),
    26. onPressed: () {
    27. print("OutlineButton Click");
    28. },
    29. )
    30. ],
    31. );
    32. }
    33. }

    2.2. 自定义样式

    1. RaisedButton(
    2. child: Text("同意协议", style: TextStyle(color: Colors.white)),
    3. color: Colors.orange, // 按钮的颜色
    4. highlightColor: Colors.orange[700], // 按下去高亮颜色
    5. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), // 圆角的实现
    6. onPressed: () {
    7. print("同意协议");
    8. },
    9. )

    属性:elevation,用于控制阴影的大小。

    三. 图片Widget

    Flutter中使用Image组件
    Image组件有很多的构造函数:

  • Image.assets:加载本地资源图片;

  • Image.network:加载网络中的图片;

    3.1. 加载网络图片

    Flutter中加载网络图片直接传入URL并不需要什么配置。

    1. const Image({
    2. ...
    3. this.width, //图片的宽
    4. this.height, //图片高度
    5. this.color, //图片的混合色值
    6. this.colorBlendMode, //混合模式
    7. this.fit,//缩放模式
    8. this.alignment = Alignment.center, //对齐方式
    9. this.repeat = ImageRepeat.noRepeat, //重复方式
    10. ...
    11. })
  • width、height:用于设置图片的宽、高,当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小,如果只设置width、height的其中一个,那么另一个属性默认会按比例缩放,但可以通过下面介绍的fit属性来指定适应规则。

  • fit:该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在BoxFit中定义,它是一个枚举类型,有如下值:
    • fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
    • cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。
    • contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。
    • fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。
  • color和 colorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而colorBlendMode指定混合模式;
  • repeat:当图片本身大小小于显示空间时,指定图片的重复规则。

属性演练:

  • Container,可以把理解成一个UIView或者View,就是一个容器;
    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Center(
    5. child: Container(
    6. child: Image.network(
    7. "http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
    8. alignment: Alignment.topCenter,
    9. repeat: ImageRepeat.repeatY,
    10. color: Colors.red,
    11. colorBlendMode: BlendMode.colorDodge,
    12. ),
    13. width: 300,
    14. height: 300,
    15. color: Colors.yellow,
    16. ),
    17. );
    18. }
    19. }

    3.2. 加载本地图片

    加载本地图片稍微麻烦一点,需要将图片引入,并且进行配置
    1. class MyHomeBody extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Center(
    5. child: Container(
    6. width: 300,
    7. height: 300,
    8. color: Colors.yellow,
    9. child: Image.asset("images/test.jpeg"),
    10. ),
    11. );
    12. }
    13. }

    3.3. 实现圆角图像

    在Flutter中实现圆角效果也是使用一些Widget来实现的。

3.3.1. 实现圆角头像

方式一:CircleAvatar
CircleAvatar可以实现圆角头像,也可以添加一个子Widget:

  1. const CircleAvatar({
  2. Key key,
  3. this.child, // 子Widget
  4. this.backgroundColor, // 背景颜色
  5. this.backgroundImage, // 背景图像
  6. this.foregroundColor, // 前景颜色
  7. this.radius, // 半径
  8. this.minRadius, // 最小半径
  9. this.maxRadius, // 最大半径
  10. })

我们来实现一个圆形头像:

  • 注意一:这里使用的是NetworkImage,因为backgroundImage要求我们传入一个ImageProvider;
    • ImageProvider是一个抽象类,事实上所有我们前面创建的Image对象都有包含image属性,该属性就是一个ImageProvider
  • 注意二:这里我还在里面添加了一个文字,但是我在文字外层包裹了一个Container;
    • 这里Container的作用是为了可以控制文字在其中的位置调整;
      1. class HomeContent extends StatelessWidget {
      2. @override
      3. Widget build(BuildContext context) {
      4. return Center(
      5. child: CircleAvatar(
      6. radius: 100,
      7. backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
      8. child: Container(
      9. alignment: Alignment(0, .5),
      10. width: 200,
      11. height: 200,
      12. child: Text("兵长利威尔")
      13. ),
      14. ),
      15. );
      16. }
      17. }

方式二:ClipOval
ClipOval也可以实现圆角头像,而且通常是在只有头像时使用

  1. class HomeContent extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Center(
  5. child: ClipOval(
  6. child: Image.network(
  7. "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
  8. width: 200,
  9. height: 200,
  10. ),
  11. ),
  12. );
  13. }
  14. }

实现方式三:Container+BoxDecoration

3.3.2. 实现圆角图片

方式一:ClipRRect
ClipRRect用于实现圆角效果,可以设置圆角的大小。
代码如下:

  1. class HomeContent extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Center(
  5. child: ClipRRect(
  6. borderRadius: BorderRadius.circular(10),
  7. child: Image.network(
  8. "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
  9. width: 200,
  10. height: 200,
  11. ),
  12. ),
  13. );
  14. }
  15. }

方式二:Container+BoxDecoration

四. 表单Widget

4.1. TextField的使用

4.1.1. TextField的介绍

TextField用于接收用户的文本输入:

  1. const TextField({
  2. Key key,
  3. this.controller,
  4. this.focusNode,
  5. this.decoration = const InputDecoration(),
  6. TextInputType keyboardType,
  7. this.textInputAction,
  8. this.textCapitalization = TextCapitalization.none,
  9. this.style,
  10. this.strutStyle,
  11. this.textAlign = TextAlign.start,
  12. this.textAlignVertical,
  13. this.textDirection,
  14. this.readOnly = false,
  15. ToolbarOptions toolbarOptions,
  16. this.showCursor,
  17. this.autofocus = false,
  18. this.obscureText = false,
  19. this.autocorrect = true,
  20. this.maxLines = 1,
  21. this.minLines,
  22. this.expands = false,
  23. this.maxLength,
  24. this.maxLengthEnforced = true,
  25. this.onChanged,
  26. this.onEditingComplete,
  27. this.onSubmitted,
  28. this.inputFormatters,
  29. this.enabled,
  30. this.cursorWidth = 2.0,
  31. this.cursorRadius,
  32. this.cursorColor,
  33. this.keyboardAppearance,
  34. this.scrollPadding = const EdgeInsets.all(20.0),
  35. this.dragStartBehavior = DragStartBehavior.start,
  36. this.enableInteractiveSelection = true,
  37. this.onTap,
  38. this.buildCounter,
  39. this.scrollController,
  40. this.scrollPhysics,
  41. })
  • 一些属性比较简单:keyboardType键盘的类型,style设置样式,textAlign文本对齐方式,maxLength最大显示行数等等;
  • decoration:用于设置输入框相关的样式
    • icon:设置左边显示的图标
    • labelText:在输入框上面显示一个提示的文本
    • hintText:显示提示的占位文字
    • border:输入框的边框,默认底部有一个边框,可以通过InputBorder.none删除掉
    • filled:是否填充输入框,默认为false
    • fillColor:输入框填充的颜色
  • controller:
  • onChanged:监听输入框内容的改变,传入一个回调函数
  • onSubmitted:点击键盘中右下角的down时,会回调的一个函数

    4.1.2. TextField的样式以及监听

    TextField的decoration属性以及监听: ```dart class HomeContent extends StatelessWidget { @override Widget build(BuildContext context) { return Container(
    1. padding: EdgeInsets.all(20),
    2. child: Column(
    3. mainAxisAlignment: MainAxisAlignment.center,
    4. children: <Widget>[
    5. TextFieldDemo()
    6. ],
    7. ),
    ); } }

class TextFieldDemo extends StatefulWidget { @override _TextFieldDemoState createState() => _TextFieldDemoState(); }

class _TextFieldDemoState extends State { @override Widget build(BuildContext context) { return TextField( decoration: InputDecoration( icon: Icon(Icons.people), labelText: “username”, hintText: “请输入用户名”, border: InputBorder.none, filled: true, fillColor: Colors.lightGreen ), onChanged: (value) { print(“onChanged:$value”); }, onSubmitted: (value) { print(“onSubmitted:$value”); }, ); } }

  1. <a name="TQrkw"></a>
  2. #### 4.1.3. TextField的controller
  3. 可以添加一个控制器(Controller),设置文本的初始值,监听文本的改变;<br />如果没有为TextField提供一个Controller,Flutter默认创建一个TextEditingController:
  4. ```dart
  5. @override
  6. void initState() {
  7. super.initState();
  8. // ...其他代码
  9. if (widget.controller == null)
  10. _controller = TextEditingController();
  11. }

我们也可以自己来创建一个Controller控制一些内容:

  1. class _TextFieldDemoState extends State<TextFieldDemo> {
  2. final textEditingController = TextEditingController();
  3. @override
  4. void initState() {
  5. super.initState();
  6. // 1.设置默认值
  7. textEditingController.text = "Hello World";
  8. // 2.监听文本框
  9. textEditingController.addListener(() {
  10. print("textEditingController:${textEditingController.text}");
  11. });
  12. }
  13. // ...省略build方法
  14. }

4.2. Form表单的使用

在开发注册、登录页面时,通常会有多个表单需要同时获取内容或者进行一些验证,如果对每一个TextField都分别进行验证,是一件比较麻烦的事情。 Flutter也借鉴了前端的思想:可以通过Form对输入框进行分组,统一进行一些操作。

4.2.1. Form表单的基本使用

Form表单也是一个Widget,可以在里面放入我们的输入框。但是Form表单中输入框必须是FormField类型的

  • TextField是继承自StatefulWidget,并不是一个FormField类型;
  • 使用TextFormField,类似于TextField,并且是继承自FormField的; ```dart class FormDemo extends StatefulWidget { @override _FormDemoState createState() => _FormDemoState(); }

class _FormDemoState extends State {

@override Widget build(BuildContext context) { return Form( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextFormField( decoration: InputDecoration( icon: Icon(Icons.people), labelText: “用户名或手机号” ), ), TextFormField( obscureText: true, decoration: InputDecoration( icon: Icon(Icons.lock), labelText: “密码” ), ), SizedBox(height: 16,), Container( width: double.infinity, height: 44, child: RaisedButton( color: Colors.lightGreen, child: Text(“注 册”, style: TextStyle(fontSize: 20, color: Colors.white),), onPressed: () { print(“点击了注册按钮”); }, ), ) ], ), ); } }

  1. <a name="BUVYE"></a>
  2. #### 4.2.2. 保存和获取表单数据
  3. 有了表单后,我们需要在点击注册时,可以同时获取和保存表单中的数据,怎么可以做到呢?
  4. - 1、需要监听注册按钮的点击,在之前已经监听的onPressed传入的回调中来做即可。
  5. - 2、监听到按钮点击时,同时获取`用户名`和`密码`的表单信息。
  6. 如何同时获取`用户名`和`密码`的表单信息?
  7. - 如果调用`Form的State对象`的save方法,就会调用Form中放入的TextFormField的onSave回调:
  8. ```dart
  9. TextFormField(
  10. decoration: InputDecoration(
  11. icon: Icon(Icons.people),
  12. labelText: "用户名或手机号"
  13. ),
  14. onSaved: (value) {
  15. print("用户名:$value");
  16. },
  17. ),
  • 但是,有没有办法可以在点击按钮时,拿到 Form对象 来调用它的save方法呢?

知识点:在Flutter如何可以获取一个通过一个引用获取一个StatefulWidget的State对象呢?
答案:绑定一个GlobalKey即可。

  1. class FormDemo extends StatefulWidget {
  2. @override
  3. _FormDemoState createState() => _FormDemoState();
  4. }
  5. class _FormDemoState extends State<FormDemo> {
  6. final registerFormKey = GlobalKey<FormState>();
  7. String username, password;
  8. void registerForm() {
  9. registerFormKey.currentState.save();
  10. print("username:$username password:$password");
  11. }
  12. @override
  13. Widget build(BuildContext context) {
  14. return Form(
  15. key: registerFormKey,
  16. child: Column(
  17. mainAxisAlignment: MainAxisAlignment.center,
  18. children: <Widget>[
  19. TextFormField(
  20. decoration: InputDecoration(
  21. icon: Icon(Icons.people),
  22. labelText: "用户名或手机号"
  23. ),
  24. onSaved: (value) {
  25. this.username = value;
  26. },
  27. ),
  28. TextFormField(
  29. obscureText: true,
  30. decoration: InputDecoration(
  31. icon: Icon(Icons.lock),
  32. labelText: "密码"
  33. ),
  34. onSaved: (value) {
  35. this.password = value;
  36. },
  37. ),
  38. SizedBox(height: 16,),
  39. Container(
  40. width: double.infinity,
  41. height: 44,
  42. child: RaisedButton(
  43. color: Colors.lightGreen,
  44. child: Text("注 册", style: TextStyle(fontSize: 20, color: Colors.white),),
  45. onPressed: registerForm,
  46. ),
  47. )
  48. ],
  49. ),
  50. );
  51. }
  52. }

4.2.3. 验证填写的表单数据

  • 1、为TextFormField添加validator的回调函数;
  • 2、调用Form的State对象的validate方法,就会回调validator传入的函数;

也可以为TextFormField添加一个属性:autovalidate

  • 不需要调用validate方法,会自动验证是否符合要求;