一、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