Flutter - 基础Widget

1,631 阅读8分钟

欢迎关注微信公众号:FSA全栈行动 👋

一、文本 Widget

1、纯文本(Text)

Text 控件常见属性:

  • textAlign: 文本对齐,默认是左对齐,可以根据实际情况修改为居中(TextAlign.center)或右对齐(TextAlign.right)
  • maxLines: 文字最多显示多少行,通常与 overflow 搭配使用
  • overflow: 内容溢出显示效果,可以设置显示省略号(TextOverflow.ellipsis)
  • textScaleFactor: 缩放因子,默认是 1。
  • style: 文本样式,直接使用 TextStyle()
    • fontSize: 文字大小
    • color: 文字颜色
    • fontWeight: 文字粗细
/// Text的使用Demo
class TextWidgetDemo extends StatelessWidget {
  final textContent = "《定风波》 苏轼 \n莫听穿林打叶声,何妨吟啸且徐行。\n竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。\n";
  // final textContent = "《定风波》 苏轼 莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。";

  @override
  Widget build(BuildContext context) {
    return Text(
      textContent,
      // 默认情况下,Text是包裹文字的,文字内容太少时可能看不出效果
      textAlign: TextAlign.center,
      // 文字最多显示2行
      maxLines: 3,
      // 文字超过2行时,显示...
      overflow: TextOverflow.ellipsis,
      // 缩放因子,默认是1
      textScaleFactor: 1,
      style: TextStyle(
        fontSize: 30,
        color: Colors.red,
        fontWeight: FontWeight.bold, // 字体加粗
      ),
    );
  }
}

注意:Text 并不是最终渲染的 Widget,最终渲染的是 RichText。Text 的父类是 StatelessWidget,最终渲染的 Widget 是 build()方法创建出来的 RenderObjectWidget,即 RichText

2、富文本(Text.rich())

Text.rich() 有一个必须参数 InlineSpan textSpan,InlineSpan 是抽象类且无工厂构造函数,无法直接创建,故需要使用其子类:

  • TextSpan : 用于构建纯文本的 Span
  • WidgetSpan : 用于构建内嵌 Widget 的 Span (比如:Icon)
/// Text.rich()的使用Demo
class TextRichDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text.rich(
      TextSpan(
        // text: "Hello lqr",
        // style: TextStyle(color: Colors.red,fontSize: 20),
        children: [
          TextSpan(text: "Hello World", style: TextStyle(color: Colors.red)),
          TextSpan(
              text: "Hello Flutter", style: TextStyle(color: Colors.green)),
          WidgetSpan(child: Icon(Icons.favorite, color: Colors.red)),
          TextSpan(text: "Hello Dart", style: TextStyle(color: Colors.blue)),
        ],
      ),
      style: TextStyle(fontSize: 26),
    );
  }
}

二、按钮 Widget

1、常见 Button

  • RaisedButton : 突出的 Button(从 v1.25.0 过时,推荐使用 ElevatedButton)
  • FlatButton : 扁平的 Button(从 v1.25.0 过时,推荐使用 TextButton)
  • OutlineButton : 边框 Button(从 v1.25.0 过时,推荐使用 OutlinedButton)
  • FloatingActionButton : 浮动按钮,简称 FAB,一般用在 Scaffold 中
    • floatingActionButtonLocation : 可指定 FAB 在界面中的位置,比如底部居中: FloatingActionButtonLocation.centerFloat

在使用这些常见 Widget 时,经常会看到构造方法中有两类 "必传"参数

  • 必传参数 : 指的是 Dart 语法中方法的必传参数,这种参数不传一定报错(编译不通过)。
  • required 参数 : 使用 @required(或 required 关键字)修饰的 可选参数,这种不传编译可以通过,但是会报警告。
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础Widget")),
      body: HomeContent(),
      // 4. FloatingActionButton
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () => print("FloatingActionButton Click"),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }
}

...

class ButtonDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. 突出的Button(从 v1.25.0 过时,推荐使用 ElevatedButton)
        RaisedButton(
          child: Text("RaisedButton"),
          textColor: Colors.red, // 文字颜色(也可以在Text的style中设置)
          color: Colors.blue, // 按钮背景色
          onPressed: () => print("RaisedButton Click"),
        ),

        // 2. 扁平的Button(从 v1.25.0 过时,推荐使用 TextButton)
        FlatButton(
          child: Text("FlatButton"),
          color: Colors.orange,
          onPressed: () => print("FlatButton Click"),
        ),

        // 3. 边框Button(从 v1.25.0 过时,推荐使用 OutlinedButton)
        OutlineButton(
            child: Text("OutlineButton"),
            onPressed: () => print("OutlineButton Click")),

        // 4. FloatingActionButton,一般用在Scaffold中
        // FloatingActionButton(onPressed: onPressed)

        // 5. 自定义Button:图标-文字-背景-圆角
        FlatButton(
          color: Colors.amberAccent,
          shape: RoundedRectangleBorder( // 圆角
            borderRadius: BorderRadius.circular(8),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min, // 默认是max,占满父Widget;min是包裹内容
            children: [
              Icon(Icons.favorite, color: Colors.red), // 图标
              Text("喜欢作者"), // 文字
            ],
          ),
          onPressed: () => print("自定义Button"), // onPressed必传,否则样式可能会出问题
        )
      ],
    );
  }
}

2、定制 Button

  • 默认间隔 : 默认情况下 Button 上下有一定有间隔,可以指定 materialTapTargetSize 来修改
    • MaterialTapTargetSize.padded:(默认值) 当按钮宽(或高)不足 48px 时,就把宽(或高)扩展到 48px
    • MaterialTapTargetSize.shrinkWrap:紧缩包裹,可以去除上下的间隔
  • 最小宽度 : ButtonTheme(也是个 Widget,包裹 Button) 或 minWidth(Button 的一个属性)
  • 内间距 : 修改 padding 属性值
class ButtonExtensionDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        /// 1. 默认情况下Button上下有一定有间隔
        ///   MaterialTapTargetSize.padded:当按钮宽(或高)不足48px时,就把宽(或高)扩展到48px。
        ///   MaterialTapTargetSize.shrinkWrap:紧缩包裹,可以去除上下的间隔。
        FlatButton(
          color: Colors.red,
          child: Text("Flat Button1"),
          textColor: Colors.white,
          onPressed: () {},
          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
        ),
        FlatButton(
          color: Colors.red,
          child: Text("Flat Button2"),
          textColor: Colors.white,
          onPressed: () {},
          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
        ),

        /// 2. 修改按钮的最小宽度:ButtonTheme
        FlatButton(
          minWidth: 30,
          height: 30,
          color: Colors.red,
          child: Text(""),
          onPressed: () {},
        ),
        ButtonTheme(
          minWidth: 30,
          height: 30,
          child: FlatButton(
            color: Colors.red,
            child: Text(""),
            onPressed: () {},
          ),
        ),

        /// 3. 修改按钮的内间距
        FlatButton(
          padding: EdgeInsets.all(0),
          // 只能去除左右内间距,上下内间距可以指定一个固定height解决
          color: Colors.red,
          child: Text("Float Button3"),
          textColor: Colors.white,
          onPressed: () {},
        ),
      ],
    );
  }
}

三、图片 Widget

Image 控件需要一个必传参数 ImageProvider image,常见子类如下:

  • NetworkImage : 用于加载网络图片
    • 简单写法 : Image.network('http://lqr.com/FSA_QR.png')
  • AssetImage : 用于加载 app 包内图片
    • 简单写法 : Image.asset('assets/images/FSA_QR.png')

1、NetworkImage

  • 常见属性:
    • fit : 图片填充方式
      • BoxFit.fill : 拉伸
      • BoxFit.contain : 内容缩放至最长的一边贴边
      • BoxFit.cover : 内容缩放至最短的一边贴边
      • BoxFit.fitWidth : 宽度一定,高度自适应
      • BoxFit.fitHeight : 高度一定,宽度自适应
    • alignment :
      • Alignment.bottomCenter : 底部居中
      • Alignment.center : 居中
      • Alignment(x, y) : 左上角是(-1, -1),右下角是(1, 1)
    • color : color 不是背景色,而是用于图像混入的颜色,配合 colorBlendMode 使用
    • repeat : 重复模式,比如纵向重复 ImageRepeat.repeatY
class ImageDemo01 extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisSpacing: 8,
      mainAxisSpacing: 8,
      crossAxisCount: 3,
      children: [
        wrapItem(
          Image(image: NetworkImage(imageUrl)),
          "NetworkImage",
        ),
        wrapItem(
          Image.network(imageUrl),
          "Image.network()",
        ),
        Container(),
        wrapItem(
          Image.network(imageUrl, fit: BoxFit.fill), // 拉伸
          "BoxFit.fill",
        ),
        wrapItem(
          Image.network(imageUrl, fit: BoxFit.contain), // 内容缩放至最长的一边贴边
          "BoxFit.contain",
        ),
        wrapItem(
          Image.network(imageUrl, fit: BoxFit.cover), // 内容缩放至最短的一边贴边
          "BoxFit.cover",
        ),
        wrapItem(
          Image.network(imageUrl, fit: BoxFit.fitWidth), // 宽度一定,高度自适应
          "BoxFit.fitWidth",
        ),
        wrapItem(
          Image.network(imageUrl, fit: BoxFit.fitHeight), //高度一定,宽度自适应
          "BoxFit.fitHeight",
        ),
        Container(),
        wrapItem(
          Image.network(imageUrl, alignment: Alignment.bottomCenter),
          "Alignment.topLeft",
        ),
        wrapItem(
          Image.network(imageUrl, alignment: Alignment.center),
          "Alignment.center",
        ),
        wrapItem(
          // 左上角是(-1, -1),右下角是(1, 1)
          Image.network(imageUrl, alignment: Alignment(0, -1)),
          "Alignment(0, -1)",
        ),
        wrapItem(
          // color不是背景色,而是用于图像混入的颜色,配合 colorBlendMode 使用
          Image.network(imageUrl,
              color: Colors.green, colorBlendMode: BlendMode.colorDodge),
          "BlendMode.colorDodge",
        ),
        wrapItem(
          Image.network(imageUrl, repeat: ImageRepeat.repeatY),
          "ImageRepeat.repeatY",
        ),
      ],
    );
  }

  Widget wrapItem(Widget widget, String tip) {
    Text genTip(String tip) {
      return Text(
        tip,
        style: TextStyle(
          fontSize: 14,
          color: Colors.white,
          backgroundColor: Colors.black,
        ),
      );
    }

    return Stack(
      fit: StackFit.expand,
      children: [
        Container(color: Colors.red[100], child: widget),
        Positioned(left: 4, bottom: 4, child: genTip(tip)),
      ],
    );
  }
}

2、AssetImage

使用 AssetImage 加载包内图片步骤如下:

  1. 在 Flutter 项目中创建一个文件夹目录(比如 assets/image),存储图片
  2. pubspec.yaml 进行配置
assets:
  # - assets/images/FSA_QR.png # 配置单张图片
  - assets/images/ # 配置多张图片
  1. 使用图片
class ImageDemo02 extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // 简单写法
    // Image.asset('assets/images/FSA_QR.png');

    return Image(
      image: AssetImage('assets/images/FSA_QR.png'),
    );
  }
}

3、占位图(placeHolder)

在网络图片未加载出来之前显示的图片称为占位图,可以使用 FadeInImage 实现占位图功能:

class ImageExtensionDemo extends StatelessWidget {
  final imageUrl =
      "https://up.enterdesk.com/edpic_source/ab/a0/40/aba040ce2daa32fa9cb0cc624b385c0a.jpg";

  @override
  Widget build(BuildContext context) {
    return FadeInImage(
      fadeInDuration: Duration(milliseconds: 1),
      fadeOutDuration: Duration(milliseconds: 1),
      placeholder: AssetImage("assets/images/FSA_QR.png"),
      image: NetworkImage(imageUrl),
    );
  }
}

Flutter 会自动进行图片缓存(默认最多缓存 1000 张,缓存空间最多 100m)
api.flutter.dev/flutter/wid…
api.flutter.dev/flutter/pai…

四、字体图标 Widget

Icon 字体图标和图片图标对比:

  • 字体图标是矢量图(放大的时候不会失真)
  • 字体图标可以设置颜色
  • 图标很多时,字体图标占据空间更小

Icon 控件接收一个必传参数 IconData icon,Icons 中配备了大量常用 icon (如 Icons.pets),可以使用 Icons.xxxIconData(编码,字体) 这 2 种方式来得到 IconData 对象。另外,IconData 的本质就是字体,因此也可以使用 Text 来显示字体图标:

class IconDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // return Icon(Icons.pets, size: 300, color: Colors.orange);
    // return Icon(IconData(0xe90e, fontFamily: 'MaterialIcons'),size: 300,color: Colors.orange);

    /// 1. 使用Text显示字体图标时,需要将字体编码 -> unicode编码
    /// 2. 设置对应的字体fontFamily
    // return Text("0xe90e", style: TextStyle(fontSize: 100, color: Colors.orange));
    return Text(
      "\ue90e",
      style: TextStyle(
        fontSize: 100,
        color: Colors.orange,
        fontFamily: 'MaterialIcons',
      ),
    );
  }
}

五、表单 Widget

1、TextField 配置

  • decoration : 用于自定义输入框样式,InputDecoration():
    • labelText : 输入框上的 label 文字
    • icon : 输入框左侧的 icon
    • hintText : 输入框中的提示文字
    • border : 边框,通常使用 OutlineInputBorder()
    • filled : 是否使用填充色,默认为 false
    • fillColor : 填充色(可以理解为输入框的背景色)
  • obscureText : 是否模糊文字,默认为 false,密文模式设置设置为 true
  • onChanged : 监听文字内容变化
  • onSubmitted : 监听提交事件
class TextFieldDemo1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(),
        TextField(
          decoration: InputDecoration(
            labelText: 'username(labelText)',
            icon: Icon(Icons.people),
            hintText: '请输入用户名(hintText)',
          ),
        ),
        TextField(
          decoration: InputDecoration(
            hintText: '请输入用户名(hintText)',
          ),
        ),
        SizedBox(height: 10),
        TextField(
          decoration: InputDecoration(
            labelText: 'OutlineInputBorder',
            icon: Icon(Icons.people),
            border: OutlineInputBorder(),
          ),
        ),
        TextField(
          decoration: InputDecoration(
            labelText: 'fillColor',
            icon: Icon(Icons.people),
            border: OutlineInputBorder(),
            filled: true,
            fillColor: Colors.red[100],
          ),
        ),
        TextField(
          decoration: InputDecoration(hintText: '监听事件'),
          onChanged: (value) => print("onChange:$value"), // 监听文字内容变化
          onSubmitted: (value) => print("onSubmitted:$value"), // 监听提交事件
        ),
        TextField(
          obscureText: true, // 模糊文本
          decoration: InputDecoration(
            labelText: 'password',
            icon: Icon(Icons.lock),
            border: OutlineInputBorder(),
          ),
        )
      ],
    );
  }
}

2、TextField 内容获取

Flutter 是声明式 UI,也没有提供 ref 之类的方式去获取到 TextField 控件,因此无法通过 TextField 对象来获取输入框中的内容,不过,可以为 TextField 指定 controller,通过 controller 来获取对应输入框中的内容:

题外话:Vue 和 React 可以为控件指定 ref 值,用于在 js 或 jsx 中来获取到控件。

class TextFieldDemo2 extends StatelessWidget {
  final usernameTextEditController = TextEditingController();
  final passwordTextEditController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: usernameTextEditController,
            decoration: InputDecoration(
              labelText: 'username',
              icon: Icon(Icons.people),
              hintText: '请输入用户名',
              border: OutlineInputBorder(),
            ),
          ),
          SizedBox(height: 8),
          TextField(
            controller: passwordTextEditController,
            obscureText: true,
            decoration: InputDecoration(
              labelText: 'password',
              hintText: '请输入密码',
              icon: Icon(Icons.lock),
              border: OutlineInputBorder(),
            ),
          ),
          SizedBox(height: 8),
          Container(
            // width: 300,
            width: double.infinity,
            height: 40,
            child: FlatButton(
              color: Colors.blue,
              textColor: Colors.white,
              child: Text("登录"),
              onPressed: () {
                // 1. 获取用户名和密码
                final username = usernameTextEditController.text;
                final password = passwordTextEditController.text;
                print("账号:$username , 密码:$password");
              },
            ),
          ),
        ],
      ),
    );
  }
}

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~