UI 功能控件

SafeArea

用于在屏幕安全区中显示布局。当我们没有使用Scaffold或未设置AppBar时,页面的布局会伸展到系统状态栏下,如果我们不需要这种沉浸式状态栏效果,那么就可以使用SafeArea跳过状态栏区域(包括底部导航栏)。

显示与隐藏

  • Offstage 具有简单的隐藏功能,属性为true时表示隐藏,且不占用空间
  • VisibilityOffstage 具有更多功能,visible属性为false时表示隐藏
  • Opacity 该控件提供透明度的设置能力,当完全透明时,亦可实现隐藏控件的效果

Visibility 属性

属性 类型 简述
replacement Widget 不可见时显示的控件,仅当maintainState为false时有效
visible bool 子控件是否可见
maintainState bool 不可见时是否维持状态
maintainAnimation bool 不可见时是否维持子控件动画
maintainSize bool 不可见时是否保留空间
maintainInteractivity bool 不可见时是否保留交互性

裁剪

  • ClipOval 子控件为正方形时剪裁为内切圆,若为矩形时,剪裁为内切椭圆
  • ClipRRect 将子控件剪裁为圆角矩形
  • ClipRect 剪裁溢出部分
  • ClipPath 路径裁剪,可配合CustomClipper实现各种不规则效果

除此外,还有一个控件CircleAvatar也具有类似的功能,但这是一个视图控件,而不是功能控件,用于头像显示。

变换 Transform

Transform可以对子控件做一系列变换操作。需要注意的是,它的变换是在绘制阶段进行的,而不是布局(layout)阶段,因此无论对子控件应用何种变换,其占用空间的大小和在屏幕上的位置都是在一开始确定的,不会变化的。

常用变换

  • 平移
  • 旋转
  • 缩放
  • 斜切

Transform控件通常有两种使用方式,一种使用默认构造方法,另一种则使用命名构造方法。默认构造方法更强大灵活,命名构造方法则更简单。

命名构造方法如下

  • Transform.translate
  • Transform.scale
  • Taransform.rotate
    Container(color: Colors.red,
          child: Transform.translate(
              // x正方向、y正方向各平移20个单位
              offset: Offset(20.0, 20.0),
              child: Text("hello world"),
     )),

     Center(
        child: Container(color: Colors.red,
            child: Transform.rotate(
              // 围绕Z轴顺时针旋转90度
              angle:math.pi/2,
              child: Text("hello world"),
            )),
      )

使用默认构造方法时,transform属性是必传,此时需要使用 Matrix4 类作为 4D 矩阵

/// 需注意导包
import 'package:vector_math/vector_math_64.dart' as v;    

        Container(color: Colors.red,
          child: Transform(
            // x正方向、y正方向各平移20个单位,此时设置z轴无效
            transform: Matrix4.translation(v.Vector3(20,20,0)),
            child: Text("hello world"),
        ))

Matrix4 的常用构造方法

  • scale 缩放
  • transform 平移
  • rotationZ 绕z轴旋转
  • rotationX 绕x轴旋转
  • rotationY 绕y轴旋转
  • skewX 沿x轴方向斜切
  • skewY 沿y轴方向斜切
  • skew 沿x、y轴共同矩阵斜切

直接使用Matrix4 的命名构造方法还是有些繁琐,还涉及到导入一些数学库,因此真正推荐的写法是使用identity构造方法来初始化一个Matrix4对象,然后调用对应的功能方法,示例如下

    Container(color: Colors.red,
          child: Transform(
            // x正方向、y正方向各平移20个单位,此时设置z轴无效
            transform: Matrix4.identity()
              ..translate(20.0, 20.0, 0.0),
            child: Text("hello world"),
     ))

通过这种链式调用,在后面连续调用其他变换方法,可同时组合多种变换。

需要注意,斜切变换只能使用命名构造方法实现

Center(
        child: Container(color: Colors.red,
            child: Transform(
              // 逆时针方向,从x轴扭曲旋转30度
              transform: Matrix4.skewY(-math.pi/6),
              child: Text("hello world"),
            )),
      )

注意,除了直接使用Transform控件,还可以通过设置Containertransform属性来实现同样的变换功能,其用法与Transform相同。

MediaQuery

MediaQuery主要用于查询媒体相关的数据,使用MediaQuery.of(context)可返回一个MediaQueryData类型的数据,通常不会直接将MediaQuery作为一个控件使用,但它也可以作为Widget控件树中的控件使用。

MediaQueryData 的属性

属性名 类型 简述
size Size 获取屏幕宽、高。单位为逻辑像素,非物理像素。物理像素 = size*devicePixelRatio
devicePixelRatio double 设备像素比(密度)。单位逻辑像素对应的物理像素数量
textScaleFactor double 单位逻辑像素的字体像素数,若设为1.5,则放大50%
platformBrightness Brightness 平台当前亮度模式(iOS夜间模式、安卓9以上支持)
viewInsets EdgeInsets 被系统遮挡的部分,通常指键盘。viewInsets.bottom表示键盘的高度
padding EdgeInsets 被系统遮挡的部分,此处指“刘海屏”和安卓底部导航栏高度
viewPadding EdgeInsets 被系统遮挡的部分,独立于paddingviewInsets,通常是全屏
systemGestureInsets EdgeInsets 沿着屏幕边缘的区域,系统在这里消耗某些输入事件,并阻止将这些事件传递给APP。APP应避免将手势检测器定位在系统手势识别的区域内
physicalDepth double 设备的最大深度(主要在Fuchsia系统上设置)
alwaysUse24HourFormat bool 是否是24小时制
accessibleNavigation bool 否使用TalkBack或VoiceOver等辅助功能与程序进行交互
invertColors bool 是否支持颜色反转
highContrast bool 仅iOS 13以上支持。通过“设置”->“辅助功能”->“增加对比度”
disableAnimations bool 平台是否要求尽可能禁用或减少动画
boldText bool 平台是否要求使用粗体
orientation Orientation 是横屏还是竖屏

需要注意,MediaQuery必须在MaterialApp的作用域下使用,即在MaterialApp控件之后使用。

    // 屏幕大小
    Size mSize = MediaQuery.of(context).size;
    // 密度
    double mRatio = MediaQuery.of(context).devicePixelRatio;

    // 设备真实像素
    double width = mSize.width * mRatio;
    double height = mSize.height * mRatio;

    // 上下边距 (状态栏 和 内置导航键)
    double topPadding = MediaQuery.of(context).padding.top;
    double bottomPadding = MediaQuery.of(context).padding.bottom;

返回拦截 WillPopScope

Flutter中可以通过WillPopScope来实现返回按钮(iOS上的滑动返回)拦截。

WillPopScope中的onWillPop属性是一个回调函数,当用户点击返回按钮时会被调用(或手势操作)。该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回);最终值为true时,当前路由出栈退出。可以通过这个回调来决定是否退出。

WillPopScope(
        onWillPop: () async {
          if (_lastPressedAt == null ||
              DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) {
            //两次点击间隔超过1秒则重新计时
            _lastPressedAt = DateTime.now();
            return false;
          }
          return true;
        },
        child: Container(
          alignment: Alignment.center,
          child: Text("1秒内连续点击两次返回键才退出"),
        )
    );

Builder

使用一个闭包来创建Widget。它的主要用途有两个

  1. 获取某个控件中的上下文对象(BuildContext)
  2. 使用一个函数来构建Widget,这样可以在构建前做一些初始化操作
// 以下局部主题修改不生效,则需要使用Builder获取正确的上下文对象。
MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.orange,
        primaryColor: Colors.orange,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            "Flutter",
            style: TextStyle(color: Theme.of(context).accentColor),
          ),
        ),
        body: Container(
          alignment: Alignment.center,
          child: Theme(
            data: Theme.of(context).copyWith(primaryColor: Colors.red),
            child: Text(
              "测试",
              style: TextStyle(color: Theme.of(context).primaryColor),
            ),
          ),
        ),
      ),
    );

模糊处理 BackdropFilter

该控件主要用于模糊处理,它不仅可以处理图片,也可以处理任意的其他控件。但通常不建议使用模糊处理,对渲染性能影响很大。

模糊图层使用 ImageFilter.blur 设置模糊度,一般是在 0.0-10.0 之间,数值越大模糊度越高,超过 10.0 时完全不可见。另外蒙层还需要设置一个色值,通常可使用 withOpacity 方法设置透明度,一般是在 0.0-1.0 之间。

示例

Stack(
        alignment: Alignment.center,
        children: <Widget>[
          Container(
            width: 300,
            height: 400,
            child: Image.network('https://c-ssl.duitang.com/uploads/item/201810/07/20181007131933_qhjkl.thumb.1000_0.jpg'),
          ),
          BackdropFilter(
            filter: ImageFilter.blur(sigmaX: 4.0,sigmaY: 4.0),
            child: Center(
              child: Container(
                color: Colors.red.withOpacity(0),
              ),
            ),
          )
        ],
      )

截图 RepaintBoundary

可用于截取当前屏幕的Widget的截图,只需要套在想要截图的控件的外层。如要获取全屏截图,将RepaintBoundary包裹在最外层即可。

  GlobalKey _gKey = new GlobalKey();
  Uint8List imgData ;

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: _gKey,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Flutter Widget"),
        ),
        body: Stack(
          fit: StackFit.expand,
          alignment: Alignment.center,
          children: <Widget>[
            Container(
              width: 300,
              height: 400,
              child: imgData == null?
              Image.network('https://c-ssl.duitang.com/uploads/item/201810/07/20181007131933_qhjkl.thumb.1000_0.jpg')
              :Image.memory(imgData),
            ),

            FlatButton(
              child: Text("截图"),
              onPressed: () async {
                var data = await _capture();
                setState(() {
                  imgData = data;
                });
              },
            )
          ],
        ),
      ),
    );
  }

  // 返回图片的二进制数据
  Future<Uint8List> _capture() async {
    RenderRepaintBoundary boundary = _gKey.currentContext.findRenderObject();
    ui.Image image = await boundary.toImage();
    // 获取png格式数据
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    return pngBytes;
  }

主题 Theme

Theme 控件为Material APP 定义了主题数据(ThemeData)。在Flutter 中已预定义了一系列的主题,许多控件或部分或全部应用了这些主题,因此当更改了预定义主题后,所有使用了这些主题的Widget也都会发生相应的变化。

Theme 主要描述了应用程序的颜色和排版选择。主题分为两种:

  • 全局 Theme 是由应用程序根MaterialApp创建的主题

    MaterialApp(
        title: title,
        theme: ThemeData(
             primaryColor: Colors.red,
             ///...
        ),
    );
    
  • 局部 Theme 在应用程序某个区域范围中用于覆盖全局主题,实现灵活的差异化

    // 对于修改主题的控件,使用Theme包裹
    Theme(
        data: ThemeData(
            accentColor: Colors.yellow,
            //...
        ),
        child: Text('Hello World'),
    );
    

如需获取主题,可使用如下方式

Container(
    color: Theme.of(context).accentColor,
    chile: Text(
        'Text with a background color',
        style: Theme.of(context).textTheme.title,
    ),
);

有时候我们不想要覆盖所有的主题属性,这时候可以扩展父主题

Theme(
  /// 使用 copyWith 找到并扩展父主题
  data: Theme.of(context).copyWith(accentColor: Colors.yellow),
  child: FloatingActionButton(
    onPressed: null,
    child: Icon(Icons.add),
  ),
);

Flutter 中主要通过ThemeData去保存应用的主题及样式等信息,因此需要重点了解该类的属性。

属性名 类型 简述
brightness Brightness 应用的整体主题亮度(可用于适配夜间模式)
primarySwatch MaterialColor Material 定义的主题颜色样本。它是具有十种颜色阴影的颜色样本
primaryColor Color 主色,决定导航栏颜色
primaryColorBrightness Brightness primaryColor的亮度
primaryColorLight Color primaryColor的较浅版本
primaryColorDark Color primaryColor的较深版本
accentColor Color 小控件的前景色(按钮、文本、覆盖边缘效果等)
accentColorBrightness Brightness accentColor的亮度
canvasColor Color MaterialType.canvas 的默认颜色
scaffoldBackgroundColor Color Scaffold下的Material默认色,用于app的背景色
bottomAppBarColor Color bottomAppBarColor的默认颜色
cardColor Color 用在卡片(Card)上的Material的颜色
dividerColor Color DividerPopupMenuDivider的颜色,也用于ListTile之间、DataTable的行之间等
highlightColor Color 用于溅墨动画或指示菜单被选中时的高亮颜色
splashColor Color 溅墨效果颜色(水波纹)
splashFactory InteractiveInkFeatureFactory 定义InkWallInkResponse的外观
selectedRowColor Color 高亮选定行的颜色
unselectedWidgetColor Color 用于处于非活动(但已启用)状态的小控件的颜色。例如未选中的复选框
disabledColor Color 禁用状态下小控件的颜色
buttonColor Color RaisedButtons使用的默认填充色
buttonTheme ButtonThemeData 定义按钮部件的默认配置
secondaryHeaderColor Color 选定行时PaginatedDataTable标题的颜色
textSelectionColor Color 文本框(如TextField)中文本被选中的颜色
cursorColor Color 文本框中光标的颜色
textSelectionHandleColor Color 用于调整当前选定文本部分的句柄的颜色
backgroundColor Color primaryColor形成对比的颜色,例如用作进度条的剩余部分
dialogBackgroundColor Color Dialog的背景色
indicatorColor Color TabBar中选中的指示器颜色
hintColor Color 用于提示文本或占位符文本的颜色,例如在TextField
errorColor Color 用于输入验证错误的颜色,例如在TextField
toggleableActiveColor Color 用于突出显示SwitchRadioCheckbox等可切换小部件的活动状态的颜色
fontFamily String 字体类型
textTheme TextTheme 与卡片和画布对比的文本颜色
primaryTextTheme TextTheme primaryColor形成对比的文本主题
accentTextTheme TextTheme accentColor形成对比的文本主题
inputDecorationTheme InputDecorationTheme InputDecoratorTextFieldTextFormField的默认InputDecoration值基于此主题
iconTheme IconThemeData 与卡片和画布颜色形成对比的图标主题
primaryIconTheme IconThemeData primaryColor形成对比的图标主题
accentIconTheme IconThemeData accentColor形成对比的图标主题
sliderTheme SliderThemeData 用于呈现Slider的颜色和形状
tabBarTheme TabBarTheme 用于自定义选项卡指示器的大小、形状和颜色的主题
cardTheme CardTheme Card的颜色和样式
chipTheme ChipThemeData Chip的颜色和样式
platform TargetPlatform 小控件应该适应目标的平台,应该被用来根据平台的约定来样式化UI元素
materialTapTargetSize MaterialTapTargetSize 配置某些Material部件的命中测试大小
pageTransitionsTheme PageTransitionsTheme 每个目标平台的默认MaterialPageRoute转换
appBarTheme AppBarTheme 用于自定义Appbar的颜色、高度、亮度、iconThemetextTheme的主题
bottomAppBarTheme BottomAppBarTheme 自定义BottomAppBar的形状、高度和颜色的主题
colorScheme ColorScheme 一组13种颜色,可用于配置大多数组件的颜色属性
dialogTheme DialogTheme 自定义Dialog的主题形状
typography Typography 用于配置TextTheme、primaryTextTheme和accentTextTheme的颜色和几何文本主题值
cupertinoOverrideTheme CupertinoThemeData 用来覆盖Cupertino主题的样式
/// 判断当前是否是夜间模式
bool isDarkMode(BuildContext context){
    return Theme.of(context).brightness == Brightness.dark;
}

最近更新:

Flutter 1.17版本完成了2018 Material Design规范的Type Scale部分的实现,更新了TextTheme API以匹配当前的Material规范,但保留了旧名称,以使你的代码不会中断。 但是,旧名称已被弃用,因此你将收到警告,以鼓励你采用新名称。

TextStyle的名称和配置

异步 UI

FutureBuilder

FutureBuilder是一个将Dart异步操作和异步UI更新结合起来的控件。由于网络请求,数据库读取等都是耗时的异步操作,通常可以使用该控件将这些耗时操作与UI状态切换进行无缝结合。

future参数传入一个需要异步执行的任务,而builder则表示根据任务的不同状态构建不同的UI。builder中的snapshot表示任务在执行过程中的不同状态的快照。

Container(
        child: FutureBuilder(
          future: _buildFuture,
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
                print('future 还未执行');
                return Container();
              case ConnectionState.waiting:
                print('future 正在执行');
                return Center(
                  child: CircularProgressIndicator(),
                );
              case ConnectionState.done:
                print('future 执行完成');
                if (snapshot.hasError) return Text('Error: ${snapshot.error}');
                print(snapshot.data);
                return _buildListView(context, snapshot);
              default:
                return Container();
            }
          },
        )

需要注意,在使用FutureBuilder时,如果我们调用setState(或其他原因)重建UI,那么会导致对应的future任务被多次执行,造成不必要的重绘甚至是状态错误。对于这个问题,一般有两种处理方式

  • 使用AsyncMemoizer类,缓存异步任务的返回值,而不是重新执行异步任务。具有用法是使用AsyncMemoizerrunOnce方法在外面包装我们需要执行的异步任务。
  • 在外部仅初始化一次异步任务(通常是initState中),并将异步任务的future作为成员变量存起来,然后再传递给FutureBuilder

StreamBuilder

用法上与FutureBuilder基本相同,它是将Dart的Stream与UI结合起来的控件。StreamFuture的区别,在第一季课程《Flutter 全栈式开发之Dart编程指南》中做了详细说明,Stream本质是一个异步事件的序列,这里不再赘述。

需要注意的是,它的ConnectionState状态中多了一个ConnectionState.active,表示Stream处于激活状态(流上已经有数据传递了)


公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2020 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:00:54

results matching ""

    No results matching ""