Flutter UI【功能型组件(集合)】

UI 功能控件

SafeArea

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

显示与隐藏

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

Visibility 属性

属性类型简述
replacementWidget不可见时显示的控件,仅当maintainState为false时有效
visiblebool子控件是否可见
maintainStatebool不可见时是否维持状态
maintainAnimationbool不可见时是否维持子控件动画
maintainSizebool不可见时是否保留空间
maintainInteractivitybool不可见时是否保留交互性

裁剪

  • 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 的属性

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

需要注意,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去保存应用的主题及样式等信息,因此需要重点了解该类的属性。

属性名类型简述
brightnessBrightness应用的整体主题亮度(可用于适配夜间模式)
primarySwatchMaterialColorMaterial 定义的主题颜色样本。它是具有十种颜色阴影的颜色样本
primaryColorColor主色,决定导航栏颜色
primaryColorBrightnessBrightnessprimaryColor的亮度
primaryColorLightColorprimaryColor的较浅版本
primaryColorDarkColorprimaryColor的较深版本
accentColorColor小控件的前景色(按钮、文本、覆盖边缘效果等)
accentColorBrightnessBrightnessaccentColor的亮度
canvasColorColorMaterialType.canvas 的默认颜色
scaffoldBackgroundColorColorScaffold下的Material默认色,用于app的背景色
bottomAppBarColorColorbottomAppBarColor的默认颜色
cardColorColor用在卡片(Card)上的Material的颜色
dividerColorColorDividerPopupMenuDivider的颜色,也用于ListTile之间、DataTable的行之间等
highlightColorColor用于溅墨动画或指示菜单被选中时的高亮颜色
splashColorColor溅墨效果颜色(水波纹)
splashFactoryInteractiveInkFeatureFactory定义InkWallInkResponse的外观
selectedRowColorColor高亮选定行的颜色
unselectedWidgetColorColor用于处于非活动(但已启用)状态的小控件的颜色。例如未选中的复选框
disabledColorColor禁用状态下小控件的颜色
buttonColorColorRaisedButtons使用的默认填充色
buttonThemeButtonThemeData定义按钮部件的默认配置
secondaryHeaderColorColor选定行时PaginatedDataTable标题的颜色
textSelectionColorColor文本框(如TextField)中文本被选中的颜色
cursorColorColor文本框中光标的颜色
textSelectionHandleColorColor用于调整当前选定文本部分的句柄的颜色
backgroundColorColorprimaryColor形成对比的颜色,例如用作进度条的剩余部分
dialogBackgroundColorColorDialog的背景色
indicatorColorColorTabBar中选中的指示器颜色
hintColorColor用于提示文本或占位符文本的颜色,例如在TextField
errorColorColor用于输入验证错误的颜色,例如在TextField
toggleableActiveColorColor用于突出显示SwitchRadioCheckbox等可切换小部件的活动状态的颜色
fontFamilyString字体类型
textThemeTextTheme与卡片和画布对比的文本颜色
primaryTextThemeTextThemeprimaryColor形成对比的文本主题
accentTextThemeTextThemeaccentColor形成对比的文本主题
inputDecorationThemeInputDecorationThemeInputDecoratorTextFieldTextFormField的默认InputDecoration值基于此主题
iconThemeIconThemeData与卡片和画布颜色形成对比的图标主题
primaryIconThemeIconThemeDataprimaryColor形成对比的图标主题
accentIconThemeIconThemeDataaccentColor形成对比的图标主题
sliderThemeSliderThemeData用于呈现Slider的颜色和形状
tabBarThemeTabBarTheme用于自定义选项卡指示器的大小、形状和颜色的主题
cardThemeCardThemeCard的颜色和样式
chipThemeChipThemeDataChip的颜色和样式
platformTargetPlatform小控件应该适应目标的平台,应该被用来根据平台的约定来样式化UI元素
materialTapTargetSizeMaterialTapTargetSize配置某些Material部件的命中测试大小
pageTransitionsThemePageTransitionsTheme每个目标平台的默认MaterialPageRoute转换
appBarThemeAppBarTheme用于自定义Appbar的颜色、高度、亮度、iconThemetextTheme的主题
bottomAppBarThemeBottomAppBarTheme自定义BottomAppBar的形状、高度和颜色的主题
colorSchemeColorScheme一组13种颜色,可用于配置大多数组件的颜色属性
dialogThemeDialogTheme自定义Dialog的主题形状
typographyTypography用于配置TextTheme、primaryTextTheme和accentTextTheme的颜色和几何文本主题值
cupertinoOverrideThemeCupertinoThemeData用来覆盖Cupertino主题的样式
/// 判断当前是否是夜间模式
bool isDarkMode(BuildContext context){
    return Theme.of(context).brightness == Brightness.dark;
}

最近更新:

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

TextStyle的名称和配置

图片[1]-Flutter UI【功能型组件(集合)】-IT网络技术分享

异步 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处于激活状态(流上已经有数据传递了)

THE END
喜欢就支持一下吧
点赞8 分享