Flutter 学习 Basics Widget

Posted RikkaTheWorld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 学习 Basics Widget相关的知识,希望对你有一定的参考价值。

1. 概述

上一篇说到,Basics Widget 并不是 Flutter 的一个专门的Widget类别,而是 Flutter 官方挑选一些开发常用的 Widget 构成的,希望我们掌握到一些最基本的开发能力。

包括:

  • 文本 Text
  • 按钮 Button
  • 图片 Image
  • 单选框、复选框
  • 输入框、表单
  • 指示器
  • Container

2. 常用组件

2.1 Text

Text 用于显示简单样式文本,然后可以填充一些文本显示样式的属性,如下例子:

Text("Hello World",
        textAlign: TextAlign.left,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
        textScaleFactor: 1.5);
  • textAlign
    文本对齐方式
  • maxLinesoverflow
    maxLines 指定文本显示的最大行数。
    当文本内容超过最大行数时, overflow 指定了阶段方式, 例如 ellipsis 就是将多余的文本用 “…” 表示
  • textScaleFactor
    代表文本相对于当前字体大小的缩放因子,想你对于去设置文本的样式 style 属性的 fontSize, 它是调整字体大小的一个快捷方式, 该属性的默认值可以通过 MediaQueryData.textScaleFactor 获得, 如果没有 MediaQuery,那么会默认值为 1.0

2.1.1 TextStyle

TextStyle 用于指定文本样式,例如颜色、字体、粗细、背景等,如下:

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
        title: "Flutter",
        home: Scaffold(
            appBar: AppBar(
              title: const Text("Basics Widget"),
            ),
            body: Text(
              "Hello World",
              style: TextStyle(
                  color: Colors.blue,
                  fontSize: 19.0,
                  height: 2,
                  fontFamily: "Courier",
                  background: Paint()..color = Colors.yellow,
                  decoration: TextDecoration.underline,
                  decorationStyle: TextDecorationStyle.dashed),
            )));
  

效果如图:

一些属性:

  • height
    行高,它不是一个绝定的值,因为具体的行高为 height*fontSize ,同理行宽也是
  • fontFamily
    由于不同平台默认支持的字体集不同,所以在手动指定字体时一定要先在不同平台测试一下
  • fontSize
    改属性和 Text 的 textScaleFactor 都用于控制字体大小,但是有两个区别,
    ①:fontSize 可以精确指定字体大小, 而 textScaleFactor 只能缩放比例
    ②: textScaleFactor 主要是用于系统字体大小设置改变时,对Flutter 应用字体进行全局调整,而 fontSzie通常用于单个文本,字体大小不会跟随系统字体大小变化

2.1.2 TextSpan

如果我们需要对Text内容不同部分按照不同的样式显示,就可以使用 TextSpan,代表文本的一个“片段”,看看 TextSpan的定义:

  const TextSpan(
    this.text,
    this.children,
    TextStyle? style,
    this.recognizer,
    MouseCursor? mouseCursor,
    this.onEnter,
    this.onExit,
    this.semanticsLabel,
    this.locale,
    this.spellOut,
  )

其中 styletext 代表样式和文本内容, children是 List<InlineSpan>? 类型,也就说 TextSpan 可以包含其他 Span
reconizer 用于表示该文本片段上用于手势进行识别处理,下面我们看一个效果图,然后用 TextSpan 来实现:

body: const Text.rich(TextSpan(children: [
              TextSpan(text: "Home: "),
              TextSpan(
                text: "https://flutterchina.club",
                style: TextStyle(color: Colors.blue),
                recognizer: _recognizer
              ),
            ]))));

这里的代码,用 TextSpan实现了一个基础文本和一个链接片段

  • Text.rich 方法将 TextSpan 添加到 Text 中,之所以可以这样做,是因为 Text 其实就是 RichText 的一个包装,而 RichText 是可以显示多种多样的 widget
  • _reconizer 是点击链接的处理器

2.1.3 DefaultTextStyle

在 Widget 树中, 文本的样式默认是可以被继承的,因此如果 Widget树的某一个节点处设置一个默认的文本样式,那么该节点的子树所有的文本都会默认使用这个样式,而 DefaultTextStyle 正是用于设置默认文本样式的,看下面例子:

DefaultTextStyle(
  //1.设置文本默认样式  
  style: TextStyle(
    color:Colors.red,
    fontSize: 20.0,
  ),
  textAlign: TextAlign.start,
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text("hello world"),
      Text("I am Jack"),
      Text("I am Jack",
        style: TextStyle(
          inherit: false, //2.不继承默认样式
          color: Colors.grey
        ),
      ),
    ],
  ),
);

这里的代码首先设置了一个默认的样式,字体大小为20,、颜色为红色,然后将 DefaultTextStyle 设置给了子树,这样一来 Column 所有子孙 Text 默认都会继承该样式, 除非 Text 设置 inherit: false,如下所示:

2.1.4 使用字体

在 Flutter 中可以使用自定义的字体,或者其他第三方字体, 这里就不介绍配置了,具体可以看官方文档:字体

2.2 Button

Material 组件库提供了多种多样的按钮,他们都是直接或间接对 RawMaterialButton 的包装定制,所以大部分属性都一样。另外 Marterial 库中的按钮都有以下共同点:

  1. 按下时都有水波纹动画
  2. 统一用 onPressed 属性来设置回调,当按钮按下时会执行该回调,如果不提供回调则按钮会处于禁用状态,不会响应用户点击

2.2.1 ElevatedButton

即 带阴影的按钮, 默认带有阴影和灰色背景,按下后阴影会变大,如下所示:

代码如下:

        child: ElevatedButton(
          child: const Text("i am ElevatedButton"),
          onPressed: () ,
        ),
      ),

2.2.2 TextButton

文本按钮,按下后会有背景色,如下图所示:

2.2.3 OutlinedButton

默认有一个边框,不带阴影且背景透明,按下后,边框颜色会变亮、同时出现背景和阴影,如下图所示:

2.2.4 IconButton

可以点击的 Icon, 不包含文字,点击后会出现背景,如下所示:

代码设置为:

IconButton(
 icon: Icon(Icons.eleven_mp),
 onPressed: () ,
),

2.2.5 带图标的按钮

上面学到的 ElevatedButtonTextButtonOutlinedButton 都有一个 icon() 的构造函数,这样就可以代入一个图片进去,例如设置:

ElevatedButton.icon(
          icon: const Icon(Icons.send),
          label: const Text("发送"),
          onPressed: () ,
        ),

效果为(这里有编码问题,可以无视):

2.3 图片及Icon

2.3.1 图片

可以通过 Image 组件来加载并显示布局, Image 的数据源可以是

  • asset
  • 文件
  • 内存
  • 网络

2.3.1.1 ImageProvider

ImageProvider 是抽象类,主要定义了图片的获取接口 load(),从不同的数据源获取图片需要实现不同的 ImageProvider,如 AssetImage 是实现了从 Asset 中加载图片, NetworkImage 则实现了从网络中加载图片。

2.3.1.2 Image Widget

Image 组件在构建时有一个必选的 image 参数,它对应一个 ImageProvier,下面分别演示一下如何从 asset 和 网络中加载图片。

1.从 asset 中加载图片
在工程根目录下创建一个 images 目录,并将图片拷贝到该目录。
接下来在 pubspec.yaml 文件的 flutter部分 中,写入(注意缩进):

flutter:
  ..
  assets:
    - assets/images/bobo.jpg

最后在代码中使用:

Image(
  image: AssetImage("images/bobo.jpg"),
  width: 100.0,
)

就能展示图片。

(不过我这里遇到一个问题,使用手机运行Flutter应用能正常展示图片,但是使用 Chrome 模拟器会报错,不知道是什么原因造成的

2.从网络URL中加载图片
直接使用代码:

Image(
  image: NetworkImage("https://www.wahaotu.com/uploads/allimg/201904/1554901831804910.jpg"),
  width: 100.0,
)

可以正常展示图片。

(不过这里出现了很上面一样的问题,但是使用官方使用的url又能正常展示图片

2.3.1.3 Image 参数

我们可以来看下 Image 的参数,通过这些参数可以控制图片外观、大小、混合效果等。

  const Image(
    Key? key,
    required this.image,
    this.frameBuilder,
    this.loadingBuilder,
    this.errorBuilder,
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width,
    this.height,
    this.color,
    this.opacity,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.isAntiAlias = false,
    this.filterQuality = FilterQuality.low,
  )
  • widthheight
    设置图片宽高,当不指定宽高时,会根据当前父容器的限制尽可能的显示其原始大小,如果只设置其中一个,那么另一个属性默认会按比例缩放
  • fit
    该属性用于用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在 BoxFit 中定义的,它是一个枚举类型,有这些值:
    fill:拉伸填充满显示空间 ,图片会便是
    cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示部分会被剪裁
    contain:图片默认适应规则,图片会保证图片本身长宽比不变的情况下缩放以适应当前的显示空间
    fitWidth:图片宽度会缩放到显示空间的宽度,高度会按比例缩放,居中显示,图片不会变形
    fitHeight:和上面的反着来
    none:图片没有适应策略,会在显示空间内显示图片
  • colorcolorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而 colorBlendMode 指定混合模式下,因为用的比较少,这里就不做实例
  • repeat
    当图片本身大小小于显示空间时,指定图片的重复规则,这里也不做展示

2.3.2 Icon

android中有 svg 矢量图, 而 Flutter 中的也有,就是 Icon,它有下面这些优点:

  • 体积小
  • 因为是矢量图,所以拉伸不会影响清晰程度
  • 可以通过 TextSpan 和 文本混用
  • 可以引用到文本样式

Flutter 默认实现了一套Icon,在 pubspec.yaml 的配置文件可以看到:

flutter:
  uses-material-design: true

来看下官方的示例代码:

String icons = "";
// accessible: 0xe03e
icons += "\\uE03e";
// error:  0xe237
icons += " \\uE237";
// fingerprint: 0xe287
icons += " \\uE287";

Text(
  icons,
  style: TextStyle(
    fontFamily: "MaterialIcons",
    fontSize: 24.0,
    color: Colors.green,
  ),
);

效果为:

为了不让开发者码点,Flutter 封装了 IconDataIcon来专门显示字体图片,上面的例子也可以用下面方式实现:

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.accessible,color: Colors.green),
    Icon(Icons.error,color: Colors.green),
    Icon(Icons.fingerprint,color: Colors.green),
  ],
)

我们也可以使用自定义的字体图标,这里就不赘述了,可以看看官方示例:Icon自定义字体图标

2.4 单选开关和复选框

Flutter 提供了 Material 风格的 开关Switch复选框Checkbox,它们都继承自 StatfulWidget但是它们不会保存选中的状态,选中状态是由父组件来管理的。 当 Switch 或者 Checkbox 被点击时,会触发 onChanged 回调,我们可以在此回调中处理选中状态改变逻辑,下面看官方例子:

class SwitchAndCheckBoxTestRoute extends StatefulWidget 
  @override
  _SwitchAndCheckBoxTestRouteState createState() => _SwitchAndCheckBoxTestRouteState();


class _SwitchAndCheckBoxTestRouteState extends State<SwitchAndCheckBoxTestRoute> 
  bool _switchSelected=true; //维护单选开关状态
  bool _checkboxSelected=true;//维护复选框状态
  @override
  Widget build(BuildContext context) 
    return Column(
      children: <Widget>[
        Switch(
          value: _switchSelected,//当前状态
          onChanged:(value)
            //重新构建页面  
            setState(() 
              _switchSelected=value;
            );
          ,
        ),
        Checkbox(
          value: _checkboxSelected,
          activeColor: Colors.red, //选中时的颜色
          onChanged:(value)
            setState(() 
              _checkboxSelected=value!;
            );
           ,
        )
      ],
    );
  

代码中需要维护 SwitchCheckbox 的选中状态,所以 Widget 继承自 StatefulWidget。 在其 build 方法中分别状态了 Switch 和 Checkbox, 并且用两个 bool 值来维护分别的选中状态。 当按钮被点击时,会回调 onChanged 回调选中状态出去,此时我们需要调用 setState() 方法来触发 Flutter 重绘。

为什么要这样子设计,我的理解是:

  • 将开关、复选框的状态抛给父组件,可以更加灵活,比如在勾选时候做一些网络请求,即异步的操作
  • 一般来说,这些item是否选中,是和用户数据关联的,用户数据也不可能是他们的私有状态,所以放在一起管理更好

2.4.1 属性

它们的属性比较简单,常用的有:

  • activeColor:设置激活状态的颜色
  • tristate: 是否为三态,仅 Checbox有,一般情况下只有 “true” 和 “false”,表示选中和非选中,如果设置了 tristate 后,还会增加一个 “null” 状态

此外, Checkbox 不可设置宽高,其大小是自定义的,而 Switch 也仅能设置宽度而已。

2.5 输入框以及表单

Flutter Material组件提供了 输入款TextField表单Form

2.5.1 输入框 TextField

2.5.1.1 属性

来看下 TextField 提供的属性:

  const TextField(
    ...
    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.obscuringCharacter = '•',
    this.obscureText = false,
    this.autocorrect = true,
    SmartDashesType? smartDashesType,
    SmartQuotesType? smartQuotesType,
    this.enableSuggestions = true,
    this.maxLines = 1,
    this.minLines,
    this.expands = false,
    this.maxLength,
    this.maxLengthEnforcement,
    this.onChanged,
    this.onEditingComplete,
    this.onSubmitted,
    this.onAppPrivateCommand,
    this.inputFormatters,
    this.enabled,
    this.cursorWidth = 2.0,
    this.cursorHeight,
    this.cursorRadius,
    this.cursorColor,
    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
    this.keyboardAppearance,
    this.scrollPadding = const EdgeInsets.all(20.0),
    this.dragStartBehavior = DragStartBehavior.start,
    this.enableInteractiveSelection = true,
    this.selectionControls,
    this.onTap,
    this.mouseCursor,
    this.buildCounter,
    this.scrollController,
    this.scrollPhysics,
    this.autofillHints,
    this.restorationId,
    this.enableIMEPersonalizedLearning = true,
  )

属性比较多,列几个关键的讲解:

  • controller
    编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数情况下我们都需要显示提供一个 controller 来与文本框交互,如果设置的话, TextField 内部会创建一个
  • focusNode
    用于控制 TextField 是否占有当前键盘的输入焦点
  • InputDecoration
    用于控制 TextField 的外观显示,如提示文本、背景颜色、边框等。
  • keyboardType
    用于设置该输入框默认的键盘输入类型, 有文本、电话、email等格式
  • textInputAction
    键盘动作按钮图标,就是右下角的那个图标设置
  • style
    文本的样式(正在编辑中的)
  • textAlign
    输入框内编辑文本在水平方向的对齐方式
  • autofocus
    是否自动获取焦点
  • obscureText
    是否隐藏正在编辑的文本, 比如输入密码的场景,文本内容会用 “•” 来代替
  • maxLines
    最大行数
  • maxLenthmaxLengthEnforcement
    maxLenth 代表输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数
    maxLengthEnforcement 决定输入文本长度超过 maxLength 时如何处理,如截断
  • toolbarOptions
    长按时出现的菜单,可以选择 copy、cut、paste 、selectAll
  • onChange
    输入框内容改变的回调, 当然 controller 也可以做到监听
  • onEditingCompleteonSubmitted
    作用一样,都是在输入完成时触发,比如点击了键盘的 完成键、搜索键不同的是两个回调签名不同
  • inputFormatters
    指定输入格式,当用户输入内容改变时,会根据指定格式来校验
  • enable
    如果为false, 则输入框会被禁用
  • cursorWidthcursorRadiuscursorColor
    分别表示自定义输入框光标宽度、圆角和颜色

一个简单的设置代码如下:

Column(children: const <Widget>[
        TextField(
          autofocus: true,
          decoration: InputDecoration(
            labelText: "用户名",
            hintText: "请输入用户名或密码",
            prefixIcon: Icon(Icons.person)
          ),
        ),
        TextField(
          decoration: InputDecoration(
            labelText: "密码",
            hintText: "请输入密码",
            prefixIcon: Icon(Icons.lock)
          ),
          obscureText: true,
        )
      ]),

2.5.1.2 通过 controller 获取输入内容

我们可以通过 onChange 拿到内容。 当然也可以使用 controller 来获取

步骤为:

  1. 定义一个 controller
final TextEditingController _tfController = TextEditingController();
  1. 然后在 TextFiled 中传入这个 controller
TextField(
  controller: _tfController,
  ...
)

最后就可以通过 : print(_tfController.text) 来获得输入框的内容

2.5.1.3 通过 controller 监听文本内容变化

可以通过 onChange 来监听文本, controller 可以通过设置监听器来监听文本,如下:

  @override
  void initState() 
    super.initState();
    _tfController.addListener(()  
      print(_tfController.text);
    );
  

controller 的功能更多,除了监听文本,还可以设置默认值、选择文本等,这里就不多赘述。

2.5.1.4 控制焦点

可以使用 FocusNodeFocusScopeNode 来控制焦点。默认情况下是由 FocusScope 来管理,可以在这个范围内通过 FocusScopeNode 在输入框之间移动焦点、设置默认焦点。

我们可以通过下面代码来获取当前 Widget 树中默认的 FocusScopeNode:

focusScopeNode = FocusScope.of(context)

拿到句柄后,可以使用下面代码来获取焦点:

focusScopeNode.requestFocus(focusNode);

其中 focucsNode 是为 TextField 创建的 FocusNode, 这个操作可以让该 TextField 获取焦点。 调用 focusNode.unfocus() 可以取消焦点。

2.5.1.5 监听焦点状态改变事件

通过 FocusNode 可以监听焦点改变的事件:

focusNode.addListener((以上是关于Flutter 学习 Basics Widget的主要内容,如果未能解决你的问题,请参考以下文章

Flutter学习 Widget简介

Flutter学习-基础Widget

Flutter-Widget-学习笔记

Flutter学习-滚动的Widget

flutter学习-Widget-Element-RenderObject

Flutter学习-多子布局Widget