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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容