UI 功能控件
SafeArea
用于在屏幕安全区中显示布局。当我们没有使用Scaffold
或未设置AppBar
时,页面的布局会伸展到系统状态栏下,如果我们不需要这种沉浸式状态栏效果,那么就可以使用SafeArea
跳过状态栏区域(包括底部导航栏)。
显示与隐藏
Offstage
具有简单的隐藏功能,属性为true时表示隐藏,且不占用空间Visibility
比Offstage
具有更多功能,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
控件,还可以通过设置Container
的transform
属性来实现同样的变换功能,其用法与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 | 被系统遮挡的部分,独立于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
控件之后使用。
// 屏幕大小
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。它的主要用途有两个
- 获取某个控件中的上下文对象(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>[
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 | 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;
}
最近更新:
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
类,缓存异步任务的返回值,而不是重新执行异步任务。具有用法是使用AsyncMemoizer
的runOnce
方法在外面包装我们需要执行的异步任务。 - 在外部仅初始化一次异步任务(通常是
initState
中),并将异步任务的future
作为成员变量存起来,然后再传递给FutureBuilder
StreamBuilder
用法上与FutureBuilder
基本相同,它是将Dart的Stream
与UI结合起来的控件。Stream
与Future
的区别,在第一季课程《Flutter 全栈式开发之Dart编程指南》中做了详细说明,Stream
本质是一个异步事件的序列,这里不再赘述。
需要注意的是,它的ConnectionState
状态中多了一个ConnectionState.active
,表示Stream
处于激活状态(流上已经有数据传递了)