Day06 - Flutter 常用的基础 Widget

概述:

  • Text 文本
  • Icon图标及按钮Button组件
  • Image 图片组件
  • TextField 输入框

一、文本 Widget

  • 1.1、Text 文本组件的概述
    文本组件(Text) 负责文本和定义显示样式,常用属性如下表

    属性名 类型 默认值 说明
    data String 数据为要显示的文本
    maxLine int 0 文本显示的最大行数
    style TextStyle null 文本样式,可定以文本的字体大小,颜色,粗细等等
    textAlign TextAlign TextAlign.center 文本水平方向对齐方式,取值有 center、end、justify、left,right、start、values
    textDirection TextDirection TextDirection.ltr 文本书写的方向,默认是从左到右
    textScaleFactor double 1.0 字体的缩放系数
    textSpan TextSpan null 文本块,TextSpan 里面可以放文本的内容和样式
    overflow TextOverflow 文本放不下的益处方式
  • 1.2、基本的 Text 例子,更多的属性我们可以点击 Text 进去看看

    Text(
       "秋风用时光的旋律,用桂花的芬芳、苹果的馨香、菊花的灿烂、牵牛花的奔放",
       // 设置样式
       style: TextStyle(
          // 字体的大小
          fontSize: 22,
          // 字体的颜色
          color: Colors.green,
          // 设置下划线
          decoration: TextDecoration.overline,
          // 设置下划线的颜色
          decorationColor: Colors.red
          // 字体倾斜
          fontStyle: FontStyle.italic,
          // 字体加粗
          fontWeight: FontWeight.bold
       ),
       // 设置对齐方式
       textAlign: TextAlign.center,
       // 设置方向
       textDirection: TextDirection.rtl,
       // 设置缩放系数
       textScaleFactor: 1.0,
       // 最大行数
       maxLines: 2,
       // 超出显示 ...
       overflow: TextOverflow.ellipsis,
    );
    
  • 1.3、Text.Rich富文本

    Text.rich(
       TextSpan(
          children: [
             TextSpan(text: "Hello World", style: TextStyle(color: Colors.green)),
             TextSpan(text: "Hello Flutter", style: TextStyle(color: Colors.brown)),
             WidgetSpan(child: Icon(Icons.favorite, color: Colors.red,)),
             TextSpan(text: "Hello 北京", style: TextStyle(color: Colors.black)),
          ]
       )
    );
    

二、图标及按钮组件

  • 2.1、图标组件
    图标组件(Icon) 为展示图标的组件,该组件不可交互,要实现可交互的图标,可以考虑使用 IconButton 组件。 图标组件 相关的组件有以下几个:

    • IconButton: 可交互的 Icon
    • Icons:框架自带 Icon集合
    • IconTheme:Icon 主题
    • IconImage:通过 AssetImages 或者其他图片显示 Icon

    我们看下面的一个例子


    Icon(
       Icons.phone,
       color: Colors.green,
       size: 80,
    );
    

    扩展:使用自定义的Icon


    截屏2021-09-14 15.48.14.png
  • 2.2、图标按钮组件
    图标按钮组件(IconButton)是基于 Material Design 风格的组件,它可以响应按下事件,并且按下时会带一个水波纹的效果。如果它的 onPressed 回调函数为null, 那么这个按钮处于禁用的状态,并且不可以按下。

    IconButton(
       icon: Icon(Icons.volume_up, size: 48.0,),
       tooltip: '按下操作',
       onPressed: () {
          print("按下操作");
       },
    );
    
  • 2.3、RaisedButton 凸 起组件按钮(被废弃了),新的使用 ElevatedButton 详细使用地址
    凸起组件按钮(RaisedButton) 是Material Design 风格中的 button ,一个凸起的材质矩形按钮,它可以响应以下事件,并且按下时会带一个触摸的效果。

    • RaisedButton


      RaisedButton
      RaisedButton(
        child: Text("RaisedButton"),
        textColor: Colors.red,
        color: Colors.brown,
        onPressed: () {
          print("RaisedButton Click");
        },
      )
      
    • FlatButton 废弃了,被 TextButton 取代

      FlatButton

      FlatButton(
         child: Text("FlatButton"),
         color: Colors.red,
         onPressed: () {
            print("FlatButton Click");
         },
      )
      

      TextButton的使用如下

      TextButton(
         style: ButtonStyle(
           backgroundColor: MaterialStateProperty.all(Colors.brown), 
         ),
         onPressed: () {
           print("喜欢");
         },
         child: Row(
           mainAxisSize: MainAxisSize.min,
           children: [
             Text("喜欢"),
             Icon(Icons.favorite, color: Colors.red,)
           ],
         )
      )
      

      扩展

      TextButton(
          style: ButtonStyle(
            //圆角
           shape: MaterialStateProperty.all(
               RoundedRectangleBorder(
                   borderRadius:
                      BorderRadius.circular(
                          18.67))),
           //边框
            side: MaterialStateProperty.all(
             BorderSide(
                                color: Colors.red,
                  width: 0.67),
               ),
              //背景
               backgroundColor:
                  MaterialStateProperty.all(
                     Colors.transparent)),
            child: Text(
              '确定',
              style: TextStyle(
                 fontWeight: FontWeight.bold,
                 color: Colors.white,
               fontSize: 14),
              ),
             onPressed: () {}
      )
      
    • OutlineButton


      OutlineButton
      OutlineButton(
         child: Text("OutlineButton"),
         color: Colors.red,
         onPressed: () {
            print("FlatButton Click");
         },
      )
      
    • FloatingActionButton

      FloatingActionButton(
         child: Icon(Icons.add),
         onPressed: () {
            print("FloatingActionButton Click");
         },
      )
      
  • 2.4、自定义 Button


    自定义 Button
    FlatButton(
        color: Colors.amberAccent,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(6.0)
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.favorite, color: Colors.red,),
            Text("喜欢你")
          ],
        ),
        onPressed: () {
          print("自定义FlatButton Click");
        },
      )
    

三、图片组件
Image支持的图片格式:JPEG、PNG、GIF、Animated GIF、WebP、Animated WebP,BMP和WBMP。

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

    • 在Flutter项目中创建一个文件夹,存储图片,通过key从AssetBundle获得的图像

    • 在pubspec.yaml进行配置


      在pubspec.yaml进行配置
      assets:
         - assets/images/juren.jpeg
         - assets/images/2.0x/
         - assets/images/3.0x/
      

      配置完执行下:flutter packages get

    • 使用图片

      AssetImage("assets/images/juren.jpeg")
      
  • 3.2、Image.network:加载网络中的图片;
    Image属性可以设置:

    const Image({
       ...
       this.width, //图片的宽
       this.height, //图片高度
       this.color, //图片的混合色值
       this.colorBlendMode, //混合模式
       this.fit,//缩放模式
       this.alignment = Alignment.center, //对齐方式
       this.repeat = ImageRepeat.noRepeat, //重复方式
        ...
    })
    
    • width、height:用于设置图片的宽、高,当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小,如果只设置width、height的其中一个,那么另一个属性默认会按比例缩放,但可以通过下面介绍的fit属性来指定适应规则。
    • fit:该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在BoxFit中定义,它是一个枚举类型,有如下值:
      • fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
      • cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。
      • contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。
      • fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
      • fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
      • none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。
      • color和 colorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而colorBlendMode指定混合模式;
      • repeat:当图片本身大小小于显示空间时,指定图片的重复规则。

    示例代码如下

    final imageUrl = 'http://image.xcar.com.cn/attachments/a/day_200323/2020032314_59939b0716c40f9be872JrmcP75B4KfO.jpg-app';
    Image(
       image: NetworkImage(imageUrl),
       width: 200,
       height: 200,
       // 宽度固定,高度自适应
       fit: BoxFit.fitHeight,
       // 范围 -1 到 1
       alignment: Alignment(0, 0),
    );
    

    扩展其他加载图片的方式
    Image.file 从本地文件中获取图片
    举例:

    加载完整路径的本地图片
    import 'dart:io';
    Image.file(File('/sdcard/Download.Stack.png')),
    加载相对路径的本地图片,需要导入插件:path_provider
    导入头文件使用
    import 'dart:io'
    import 'package:path_provider/path_provider.dart'
    
    FutureBuilder(future: _getLocalFile("Download/stack.png")
    builder: (BuildContext context, AsyncSnapshot snapshot) {
         return snapshot.data != null ? Image.file(snapshot.data) : Container();
    }),
    // 获取 SDCard的路径
    Future _getLocalFile(String filename) async {
        String dir (await getExternalStorageDirectory()).path;
        File f = File('$dir/$filename');
        return f;
    }
    

    Image.memory 用于从Uint8List获取图像

  • 3.3、如何设置 placeholder
    为了设置 Placeholder 我们需要借助 FadeInImage,它能够从内存,本地资源中加载 placeholder。
    插件:transparent_image

    • 使用方式如下:

      FadeInImage.memoryNetwork(
            placeholder: kTansparentImage,
            image: '图片链接' 
      )
      
    • 使用本地的资源图片作为placeholder

      FadeInImage.assetNetwork(
           placeholder: 'assets/图片地址',
           image: '图片链接' 
      )
      
  • 3.4、配置图片缓存
    在Flutter中我们可以借助:cached_network_image 插件,来从网络上加载图片,并将其缓存到本地,以供下次使用

    CachedNetworkImage(
          placeholder: CircularProgressIndicator(),
          imageUrl: '图片链接'        
    )
    
  • 3.5、实现圆角图像

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

      const CircleAvatar({
         Key key,
         this.child, // 子Widget
         this.backgroundColor, // 背景颜色
         this.backgroundImage, // 背景图像
         this.foregroundColor, // 前景颜色
         this.radius, // 半径
         this.minRadius, // 最小半径
         this.maxRadius, // 最大半径
      })
      

      我们来实现一个圆形头像

      • 注意一:这里我们使用的是NetworkImage,因为backgroundImage要求我们传入一个ImageProvider;
        ImageProvider是一个抽象类,事实上所有我们前面创建的Image对象都有包含image属性,该属性就是一个ImageProvider

      • 注意二:这里我还在里面添加了一个文字,但是我在文字外层包裹了一个Container;
        这里Container的作用是为了可以控制文字在其中的位置调整;

        class HomeContent extends StatelessWidget {
           @override
           Widget build(BuildContext context) {
              return Center(
                child: CircleAvatar(
                   radius: 100,
                   backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
                   child: Container(
                      alignment: Alignment(0, .5),
                      width: 200,
                      height: 200,
                      child: Text("兵长利威尔")
                   ),
               ),
            );
          }
        }
        
    • 方式二:ClipOval
      ClipOval也可以实现圆角头像,而且通常是在只有头像时使用

      Center(
         child: ClipOval(
            child: Image.network(
                imageUrl,
                width: 200,
                height: 200,
            ),
         )
      );
      
  • 3.6、实现圆角图片

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

      Center(
         child: ClipRRect(
             borderRadius: BorderRadius.circular(10),
             child: Image.network(
                "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
                width: 200,
                height: 200,
             ),
          ),
      );
      
    • 方式二:Container+BoxDecoration

      class HomeContent extends StatelessWidget {
         @override
         Widget build(BuildContext context) {
             return Center(
                child: Container(
                   width: 200,
                   height: 200,
                   decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(20),
                      image: DecorationImage(
                          image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
                      )
                   ),
                ),
            );
         }
      }
      

四、TextField

  • 4.1、TextField的介绍
    TextField用于接收用户的文本输入,它提供了非常多的属性,我们来看一下源码:
    但是我们没必要一个个去学习,很多时候用到某个功能时去查看是否包含某个属性即可

    const TextField({
       Key key,
       this.controller,
       this.focusNode,
       this.decoration = const InputDecoration(),
       TextInputType keyboardType,
       this.textInputAction,
       this.textCapitalization = TextCapitalization.none,
       this.style,
       this.strutStyle,
       this.textAlign = TextAlign.start,
       this.textAlignVertical,
       this.textDirection,
       this.readOnly = false,
       ToolbarOptions toolbarOptions,
       this.showCursor,
       this.autofocus = false,
       this.obscureText = false,
       this.autocorrect = true,
       this.maxLines = 1,
       this.minLines,
       this.expands = false,
       this.maxLength,
       this.maxLengthEnforced = true,
       this.onChanged,
       this.onEditingComplete,
       this.onSubmitted,
       this.inputFormatters,
       this.enabled,
       this.cursorWidth = 2.0,
       this.cursorRadius,
       this.cursorColor,
       this.keyboardAppearance,
       this.scrollPadding = const EdgeInsets.all(20.0),
       this.dragStartBehavior = DragStartBehavior.start,
       this.enableInteractiveSelection = true,
       this.onTap,
       this.buildCounter,
       this.scrollController,
       this.scrollPhysics,
    })
    

    几个比较常见的属性:
    一些属性比较简单:keyboardType键盘的类型,style设置样式,textAlign文本对齐方式,maxLength最大显示行数等等;

    • decoration:用于设置输入框相关的样式
    • icon:设置左边显示的图标
    • labelText:在输入框上面显示一个提示的文本
    • hintText:显示提示的占位文字
    • border:输入框的边框,默认底部有一个边框,可以通过InputBorder.none删除掉
    • filled:是否填充输入框,默认为false
    • fillColor:输入框填充的颜色
    • controller:TextEditingController()
    • onChanged:监听输入框内容的改变,传入一个回调函数
    • onSubmitted:点击键盘中右下角的down时,会回调的一个函数
  • 4.2、TextField 的样式以及监听

    TextField(
       decoration: InputDecoration(
          labelText: "username",
          icon: Icon(Icons.favorite),
          hintText: "请输入用户名",
          // OutlineInputBorder()
          // InputBorder.none
          border: OutlineInputBorder()
       ),
       onChanged: (value) {
           print("输入的内容:$value");
       },
       onSubmitted: (value) {
           print("提交的值:$value");
       },
       // 输入不可见
       obscureText: true,
    )
    
  • 4.3、TextField的controller
    我们可以给TextField添加一个控制器(Controller),可以使用它设置文本的初始值,也可以使用它来监听文本的改变;
    事实上,如果我们没有为TextField提供一个Controller,那么会Flutter会默认创建一个TextEditingController的,这个结论可以通过阅读源码得到:

    @override
    void initState() {
        super.initState();
        // ...其他代码
        if (widget.controller == null)
        _controller = TextEditingController();
    }
    

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

    class _TextFieldDemoState extends State {
        final textEditingController = TextEditingController();
    
        @override
        void initState() {
            super.initState();
            // 1.设置默认值
            textEditingController.text = "Hello World";
            // 2.监听文本框
            textEditingController.addListener(() {
                print("textEditingController:${textEditingController.text}");
            });
        }
        // ...省略build方法
    }
    

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

  • 5.1、Form 表单的基本使用
    Form表单也是一个Widget,可以在里面放入我们的输入框。
    但是Form表单中输入框必须是FormField类型的
    我们查看刚刚学过的TextField是继承自StatefulWidget,并不是一个FormField类型;
    我们可以使用TextFormField,它的使用类似于TextField,并且是继承自FormField的;
    我们通过Form的包裹,来实现一个注册的页面:

    class JKHomeContent extends StatefulWidget {
      @override
      _JKHomeContentState createState() => _JKHomeContentState();
    }
    
    class _JKHomeContentState 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: 20,
                   ),
                   Container(
                      // double.infinit
                      width: 200,
                      height: 44,
                      child: RaisedButton(
                         color: Colors.green,
                         child: Text("注 册", style: TextStyle(fontSize: 20, color: Colors.white),),
                      onPressed: () {
                           print('点击了 注册');
                      },
                   ),
                )
             ],
           )
        );
      }
    }
    
  • 5.2、保存和获取表单数据
    有了表单后,我们需要在点击注册时,可以同时获取和保存表单中的数据,怎么可以做到呢?

    • 1、需要监听注册按钮的点击,在之前我们已经监听的onPressed传入的回调中来做即可。(当然,如果嵌套太多,我们待会儿可以将它抽取到一个单独的方法中)
    • 2、监听到按钮点击时,同时获取用户名和密码的表单信息。

    如何同时获取 用户名密码 的表单信息?

    • 如果我们调用 Form的State对象 的save方法,就会调用Form中放入的TextFormField的onSave回调:

      TextFormField(
         decoration: InputDecoration(
            icon: Icon(Icons.people),
            labelText: "用户名或手机号"
         ),
         onSaved: (value) {
            print("用户名:$value");
         },
      ),
      

    但是,我们有没有办法可以在点击按钮时,拿到 Form对象 来调用它的save方法呢?
    知识点:在Flutter如何可以获取一个通过一个引用获取一个StatefulWidget的State对象:通过绑定一个GlobalKey即可

    class JKHomeContent extends StatefulWidget {
       @override
       _JKHomeContentState createState() => _JKHomeContentState();
    }
    
    class _JKHomeContentState extends State {
         final registerFormKey = GlobalKey();
         String username, password;
    
         void registerForm() {
            registerFormKey.currentState.save();
            print("username:$username password:$password");
         }
    
         @override
         Widget build(BuildContext context) {
            return Form(
                key: registerFormKey,
               child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                       TextFormField(
                          decoration: InputDecoration(
                             icon: Icon(Icons.people),
                             labelText: '用户名或者手机号'
                          ),
                          onSaved: (value) {
                             username = value;
                          },
                       ),
                       TextFormField(
                          obscureText: true,
                          decoration: InputDecoration(
                             icon: Icon(Icons.lock),
                             labelText: "密码"
                          ),
                          onSaved: (value) {
                             password = value;
                          },
                       ),
                       SizedBox(
                          height: 20,
                       ),
                       Container(
                          // double.infinit
                          width: 200,
                          height: 44,
                          child: RaisedButton(
                              color: Colors.green,
                              child: Text("注 册", style: TextStyle(fontSize: 20, color: Colors.white),),
                          onPressed: () {
                            print('点击了 注册');
                            registerForm();
                          },
                       ),
                   )
               ],
            )
          );
        }
    }
    
  • 5.3、验证填写的表单数据
    在表单中,我们可以添加 验证器,如果不符合某些特定的规则,那么给用户一定的提示信息
    比如我们需要账号和密码有这样的规则:账号和密码都不能为空。
    按照如下步骤就可以完成整个验证过程:

    • 1、为TextFormField添加validator的回调函数;

      validator: (value) {
         return value.length < 6 ? "密码长度不够6位" : null;
      },
      
    • 2、调用Form的State对象的validate方法,就会回调validator传入的函数;
      也可以为TextFormField添加一个属性:autovalidate
      不需要调用validate方法,会自动验证是否符合要求;


      void registerForm() {
         // 读取当前 From 的状态
         var registerFrom = registerFormKey.currentState;
      
         // 验证From表单
         if (registerFrom.validate()) {
            // 符合要求进行存储
            registerFrom.save();
            print("username:$username password:$password");
         }
      }
      

你可能感兴趣的:(Day06 - Flutter 常用的基础 Widget)