一、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,
),
),
路径剪裁
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;
}
}
四、变换 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相同。
五、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