当前位置:主页 > 移动开发 > Android代码 >

Flutter基本组件Basics Widget学习

时间:2023-02-28 11:47:48 | 栏目:Android代码 | 点击:

1. 概述

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

包括:

2. 常用组件

2.1 Text

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

Text("Hello World",
        textAlign: TextAlign.left,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
        textScaleFactor: 1.5);

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),
            )));
  }

效果如图:

一些属性:

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实现了一个基础文本和一个链接片段

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 库中的按钮都有以下共同点:

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 的数据源可以是

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,
  })

2.3.2 Icon

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

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 重绘。

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

2.4.1 属性

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

此外, 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,
  })

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

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

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 来获取

步骤为:

定义一个 controller
final TextEditingController _tfController = TextEditingController();
然后在 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((){
   print(focusNode.hasFocus);
})

true为获取焦点,false为失去焦点

2.5.2 表单

表单Form 对输入框进行分组和统一操作。 就像 Android 的原生组件 RadioGroup 之于 RadioButton 一样, Form 可以管理内容校验、输入框重置等。

Form 继承自 StatefulWidget,其状态管理在 FormState 里面,来看看 From 的定义:

class Form extends StatefulWidget {
  const Form({
    Key? key,
    required this.child,
    @Deprecated(
      'Use autovalidateMode parameter which provides more specific '
      'behavior related to auto validation. '
      'This feature was deprecated after v1.19.0.',
    )
    this.autovalidate = false,
    this.onWillPop,
    this.onChanged,
    AutovalidateMode? autovalidateMode,
  })
  ...
2.5.2.1 FormField

Form 的子孙元素是 FormField 类型,FormField 是一个抽象类,定义了几个属性, FormState 内部通过他们来完成操作, FormField 部分定义如下:

  const FormField({
    Key? key,
    required this.builder,
    this.onSaved,
    this.validator,
    this.initialValue,
    @Deprecated(
      'Use autovalidateMode parameter which provides more specific '
      'behavior related to auto validation. '
      'This feature was deprecated after v1.19.0.',
    )
    this.autovalidate = false,
    this.enabled = true,
    AutovalidateMode? autovalidateMode,
    this.restorationId,
  })

为了方便使用, Flutter 提供了一个 TextFormFild 组件,继承自 FormField 类,还包装了 TextFileld ,可以直接当成 Form 的 FormField 来使用, 相当于用 Form 来管理 TextField

2.5.2.2 FormState

Form 表单的状态类就是 FormState, 可以通过 Form.of 或者 GlobalKey 获得,通过获得它来对 Form 的子孙 FormField 进行统一操作。

FormState 常用的三个方法:

2.5.2.3 示例

我们做一个用户登录的程序,再点击登录前需要做到输入检查:

代码如下:

import 'package:flutter/material.dart';

class FormTestRoute extends StatefulWidget {
  const FormTestRoute({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _FormTestRouteState();
}

class _FormTestRouteState extends State<FormTestRoute> {
  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final GlobalKey _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Form demo'),
        ),
        body: Form(
            key: _formKey,
            autovalidateMode: AutovalidateMode.onUserInteraction,
            child: Column(
              children: [
                TextFormField(
                  autofocus: true,
                  controller: _usernameController,
                  decoration: const InputDecoration(
                      labelText: "username",
                      hintText: "username or email",
                      icon: Icon(Icons.person)),
                  validator: (username) {
                    return username!.trim().isNotEmpty
                        ? null
                        : "username cannot empty";
                  },
                ),
                TextFormField(
                  controller: _passwordController,
                  decoration: const InputDecoration(
                      labelText: "password",
                      hintText: "please input your password",
                      icon: Icon(Icons.lock)),
                  obscureText: true,
                  validator: (pwd) {
                    return pwd!.trim().length >= 6
                        ? null
                        : "password digit cannot less than 6!";
                  },
                ),
                // login button
                Padding(
                  padding: const EdgeInsets.only(top: 28.0),
                  child: Row(
                    children: [
                      Expanded(
                          child: ElevatedButton(
                        onPressed: () {
                          if ((_formKey.currentState as FormState).validate()) {
                            print("Loing success");
                          }
                        },
                        child: const Padding(
                          padding: EdgeInsets.all(16.0),
                          child: Text("Login"),
                        ),
                      ))
                    ],
                  ),
                )
              ],
            )));
  }
}

效果如下图所示:

您可能感兴趣的文章:

相关文章