Flutter开发3:UI功能控件集合

一、SafeArea

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

SafeArea(
        child: Container()
)

二、显示与隐藏

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

Visibility 属性

属性名 类型 简介
replacement Widget 不可见时显示的控件,仅当maintainState为false时有效
visible bool 子控件是否可见
maintainState bool 不可见时是否维持状态
maintainAnimation bool 不可见时是否维持子控件动画
maintainSize bool 不可见时是否保留空间
maintainInteractivity bool 不可见时是否保留交互性
 Wrap(
              children: [
                Offstage(offstage: true, child: TextButton(onPressed: (){}, child: const Text('社会心理学'))),
                Visibility(visible: false, child: TextButton(onPressed: (){}, child: const Text('发展心理学'))),
                TextButton(onPressed: (){}, child: const Text('变态心理学')),
                TextButton(onPressed: (){}, child: const Text('健康心理学')),
                TextButton(onPressed: (){}, child: const Text('咨询心理学')),
              ],
            ),

三、裁剪

  • ClipOval 子控件为正方形时剪裁为内切圆,若为矩形时,剪裁为内切椭圆
  • ClipRRect 将子控件剪裁为圆角矩形
  • ClipRect 剪裁溢出部分【?】
  • ClipPath 路径裁剪,可配合CustomClipper实现各种不规则效果
    除此外,还有一个控件CircleAvatar也具有类似的功能,但这是一个视图控件,而不是功能控件,用于头像显示。
//剪裁为内切椭圆
ClipOval(
              child: Image.asset('assets/img/nezha1.jpeg'),
            ),
//剪裁为圆角矩形
ClipRRect(borderRadius: BorderRadius.circular(15),child: Image.asset('assets/img/nezha1.jpeg'),),
            const SizedBox(height: 10,),

//圆形头像
const Center(
              child: CircleAvatar(
                backgroundImage: NetworkImage(
                    'https://c-ssl.duitang.com/uploads/item/201810/07/20181007131933_qhjkl.thumb.1000_0.jpg'),
                maxRadius: 100,
              ),
            ),
            const SizedBox(height: 10,),

//昵称头像
const Center(
              child: CircleAvatar(
                child: Text('洋哥'),
                backgroundColor: Colors.blueAccent,
                maxRadius: 30,
              ),
            ),
20220429113053.jpg

路径剪裁

ClipPath(
              clipper: MyClipper(),
              child: Container(
                width: 400,
                height: 300,
                decoration: const BoxDecoration(
                    color: Color(0xff622F74),
                    gradient: LinearGradient(
                        colors: [Colors.red, Colors.yellow],
                        begin: Alignment.centerRight,
                        end: Alignment(-1.0, -1.0))),
              ),
            ),
class MyClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0, 300);
    path.lineTo(400, 150);
    path.lineTo(400, 0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper oldClipper) {
    return false;
  }
}
20220429113521.jpg

四、变换 Transform

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

常用变换

  • 平移
  • 旋转
  • 缩放
  • 斜切
    Transform控件通常有两种使用方式,一种使用默认构造方法,另一种则使用命名构造方法。默认构造方法更强大灵活,命名构造方法则更简单。

命名构造方法如下

  • Transform.translate
  • Transform.scale
  • Taransform.rotate

平移

  double dx = 0;
  double dy = 0;
Container(
              color: Colors.purpleAccent,
              child: Transform.translate(offset: Offset(dx, dy),
                child: const Text("走么No加油💪🏻"),

              ),
            ),
            TextButton(onPressed: (){
              setState(() {
                dx++;
                dy++;
              });
            }, child: const Text('点我移动')),
            TextButton(onPressed: (){
              setState(() {
                dx--;
                dy--;
              });
            }, child: const Text('点我回位')),

旋转

double PI = 2;
Container(
              color: Colors.yellow,
              child: Transform.rotate(angle:pi/PI,
                child: const Text("看我旋转"),

              ),
            ),
            TextButton(onPressed: (){
              setState(() {
                PI++;
              });
            }, child: const Text('点我旋转')),
            TextButton(onPressed: (){
              setState(() {
                PI--;
              });
            }, child: const Text('点我旋转')),

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

import 'package:vector_math/vector_math_64.dart' as v;
Container(
              color: Colors.blue,
              child: Transform(
                transform: Matrix4.translation(v.Vector3(5,5,0)),
                child: const Text('会当凌绝顶,一览众山小'),
              ),
            ),

Matrix4 的常用构造方法

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

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

Container(
              color: Colors.pinkAccent,
              child: Transform(
                transform: Matrix4.identity()
                  ..translate(5.0,5.0,0.0),
                child: const Text('会当凌绝顶,一览众山小'),
              ),
            ),

通过这种链式调用,在后面连续调用其他变换方法,可同时组合多种变换。
需要注意,斜切变换只能使用命名构造方法实现

Container(
              color: Colors.lightBlue,
              child: Transform(
                transform: Matrix4.skewY(-pi/18),
                child: const Text('会当凌绝顶,一览众山小'),
              ),
            ),

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

20220429143223.jpg

五、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 被系统遮挡的部分,独立于padding和viewInsets,通常是全屏
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控件之后使用。

下面是iPhone 12 mini的模拟器打印数据

    // 屏幕大小
    Size mSize = MediaQuery.of(context).size;
    debugPrint(mSize.width.toString()); // 375.0
    debugPrint(mSize.height.toString()); // 812.0

    // 密度
    double mRatio = MediaQuery.of(context).devicePixelRatio;
    debugPrint(mRatio.toString()); // 3.0

    // 设备真实像素
    double width = mSize.width * mRatio;
    double heigth = mSize.height * mRatio;
    debugPrint(width.toString()); // 1125.0
    debugPrint(heigth.toString()); // 2436.0

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

六、返回拦截 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。它的主要用途有两个

获取某个控件中的上下文对象(BuildContext)
使用一个函数来构建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>[
                SizedBox(
                  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: 2.0,sigmaY: 1.0),
                  child: Center(
                    child: Container(
                      height: 200,
                      width: 100,
                      color: Colors.red.withOpacity(0),
                    ),
                  ),
                )
              ],
            ),

九、截图 RepaintBoundary*

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

十、主题 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 Divider和PopupMenuDivider的颜色,也用于ListTile之间、DataTable的行之间等
highlightColor Color 用于溅墨动画或指示菜单被选中时的高亮颜色
splashColor Color 溅墨效果颜色(水波纹)
splashFactory InteractiveInkFeatureFactory 定义InkWall和InkResponse的外观
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 用于突出显示Switch、Radio和Checkbox等可切换小部件的活动状态的颜色
fontFamily String 字体类型
textTheme TextTheme 与卡片和画布对比的文本颜色
primaryTextTheme TextTheme 与primaryColor形成对比的文本主题
accentTextTheme TextTheme 与accentColor形成对比的文本主题
inputDecorationTheme InputDecorationTheme InputDecorator、TextField和TextFormField的默认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的颜色、高度、亮度、iconTheme和textTheme的主题
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;
}

十一、异步 UI*

1.FutureBuilder
2.StreamBuilder

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容