Flutter 學習筆記-基礎篇 Flutter 學習筆記-基礎篇

Flutter 學習筆記-基礎篇

如果你要獲取與該筆記配套的源碼,請點擊這裏

一、常用命令及快捷鍵

1. 常用命令

flutter doctor  //Flutter自檢命令
flutter run         //運行項目到設備上

2. 常用快捷鍵

r 鍵:熱加載(快速部署)。

p 鍵:顯示網格,用於開發時調試UI。

o 鍵:切換Android和iOS的預覽模式。

q 鍵:退出調試預覽模式。

以上幾個按鍵需要已經執行過flutter run之後纔可以在終端中使用。

二、基本Widget組件

Flutter中一切皆組件(Widget)。所有的自定義組件、系統組件、自定義頁面等其實都是Widget的子類。每個widget的構造函數都有一個key參數,這個參數的作用是什麼呢?Key用於在widget的位置改變時保留其狀態。比如,保留用戶的滑動位置,或者在保留widget狀態的情況下修改一個widget集合,如Row、Column等,這一篇博客詳細d 講解了Widget中的key。

1. 有狀態無狀態組件。

Flutter中如果要自定組件,一般都繼承自有狀態組件或無狀態組件(小到組件大到頁面),如果一個頁面加載出來之後就不會再改變了那麼就繼承自StatelessWidget,如果加載之後還需要根據數據的變化而變化,那麼就需要繼承自StatefulWidget。

​ 1). StatelessWidget 無狀態組件。

​ StatelessWidget組件是一個抽象類,繼承該組件必須要實現Widget build(BuildContext context)方法。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return Text("Hello Flutter!");
  }
}

​ 2). StatefulWidget 有狀態組件。

​ StatefulWidget是一個抽象組件,繼承該組件必須要實現State<StatefulWidget> createState()方法。該方法需要返回一個State類的實例。而State是一個抽象類,要繼承State需要實現Widget build(BuildContext context)抽象方法,並制定泛型爲擁有該State的Widget。

​ 如果要實現改變狀態,需要使用State類中的setState方法。下面的栗子是點擊按鈕後按鈕上面的數字動態加1,具體代碼如下:

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Custom(),
    );
  }
}

class Custom extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _CustomState();
  }
}

class _CustomState extends State<Custom>{
  var myNumber = 0;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text("$myNumber"),
        SizedBox(height: 20),
        RaisedButton(
          onPressed: () {
            setState(() {
              myNumber++;
            });
          },
          child: Text('增加'),
        )
      ],
    );
  }
}

2. 裝飾組件

​ 1). MaterialApp

​ MaterialApp是一個方便的Widget,他封裝了應用程序實現 Material Design 所需要的一些Widget。一般作爲一個頁面的最頂層的Widget使用。

參數 類型 可選 默認值 說明
home Widget -- 頁面的具體內容組件。
title String '' 頁面標題。
color Color -- 頁面顏色。
theme ThemeData -- 頁面主題。
routes Map<String, WidgetBuilder> const <String, WidgetBuilder>{} 路由。

​ 2). Scaffold

​ Scaffold 是 Material Design 佈局結構的基本實現。此類提供了用於顯示 drawer、snackbar 和底部的 sheet 的參數。

參數 類型 可選 默認值 說明
appBar Widget -- 導航欄
body Widget --
drawer Widget --
bottomNavigationBar Widget --

​ 3). InkWell & GestureDetector 手勢組件

​ 在Flutter中並不是所有的Widget都支持點擊事件,但是如果我們想給某些我們自己的Widget設置點擊事件時應該怎麼辦呢(例如我們列表中的item)?這時我們只需要使用InkWell將我們要設置點擊事件的Widget包裹起來就可以了。

參數 類型 可選 默認值 說明
child Widget -- 子組件
onTap GestureTapCallback -- 單擊事件回調
onDoubleTap GestureTapCallback -- 雙擊事件回調
onLongPress GestureLongPressCallback -- 長按事件回調

​ GestureDetector和InkWell用法基本一致,只是GestureDetector比InkWell多了更多回調設置,可以監聽更多的事件。

3. Container 容器組件

參數 類型 可選 默認值 說明
constraints BoxConstraints -- 用不約束寬高(最大寬高、最小寬高等)
alignment AlignmentGeometry -- 對齊方式
decoration Decoration -- 設置邊框裝飾
padding EdgeInsetsGeometry -- 內邊距
margin EdgeInsetsGeometry -- 外邊距
transform Matrix4 -- 旋轉、平移

4. Text 文本組件。

參數 類型 可選 默認值 說明
data String -- 用於設置現在界面上的文字
textAlign TextAlign -- center:居中 / left:居左 / right:居右 / justfy:兩端對齊
textDirection TextDirection -- 文本方向,ltr:從左至右 rtl:從右至左
overflow TextOverflow -- 文字超出屏幕後的處理方式,clip:裁剪 fade:漸隱 ellipsis:省略號
style TextStyle -- 用於設置文字的樣式
maxLines int -- 用於設置文字的最大顯示行數
textScaleFactor double -- 字體顯示倍率

5. ListView 列表組件。

參數 類型 可選 默認值 說明
itemCount Int 必填 -- 設置列表一共有多少個條目
itemBuilder Widget Function(BuildContext context, int index) 必填 -- 用於構建item(cell)視圖的回調方法。
scrollDirection Axis Axis.vertical 滾動方向:horizontal-橫向 vertical縱向。
reverse bool false 是否反向排列。
keyboardDismissBehavior ScrollViewKeyboardDismissBehavior ScrollViewKeyboardDismissBehavior.manual 滾動時鍵盤的處理方式。

靜態列表

@override
Widget build(BuildContext context) {
  return ListView(
    children: [
      Text('我是一個條目'),
      Text('我是一個條目'),
      Text('我是一個條目'),
      Text('我是一個條目'),
      Text('我是一個條目'),
      Text('我是一個條目'),
    ],
  );
}

動態列表

@override
Widget build(BuildContext context) {
  return ListView.builder(
      itemCount: 5, 
      itemBuilder: (context, position){
        return ListTile(
          leading: item.avatarUrl,
          title: item.name,
          subtitle: item.desc,
        );
      }
  );
}

6. GridView

參數 類型 可選 默認值 說明
crossAxisSpacing double -- 水平間距
mainAxisSpacing double -- 垂直間距
scrollDirection Axis Axis.vertical 滾動方向:horizontal-橫向 vertical縱向。
reverse bool false 是否反向排列。
childAspecet double -- item的寬高比例。
crossAxisCount int -- 設置列數
gridDelegate SliverGridDelegate 必填 -- builder專用。用於設置間距列數等。
itemBuilder Widget Function(BuildContext context, int index) 必填 -- builder專用。用於構建item(cell)視圖的回調方法。

靜態列表

@override
Widget build(BuildContext context) {
  return GridView.count(
      crossAxisCount: 2,
      children: [
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目'),
        Text('我是一個條目')
      ],
  );
}

動態列表

@override
Widget build(BuildContext context) {
  return GridView.builder(
      itemCount: 600,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3
      ),
      itemBuilder: (context, position){
        return Text('我是第$position個條目');
      }
  );
}

7. Padding 內邊距組件

參數 類型 可選 默認值 說明
padding EdgeInsetsGeometry 必填 -- 用於設置內邊距,其實是設置child的外邊距。
child Widget -- 子元素。

8. Row 水平佈局組件

參數 類型 可選 默認值 說明
mainAxisAlignment MainAxisAlignment MainAxisAlignment.start 主軸的排序方式
crossAxisAlignment CrossAxisAlignment CrossAxisAlignment.center 次軸的排序方式
children List<Widget> const <Widget>[] 子組件

9. Column 垂直佈局組件

參數 類型 可選 默認值 說明
mainAxisAlignment MainAxisAlignment MainAxisAlignment.start 主軸的排序方式
crossAxisAlignment CrossAxisAlignment CrossAxisAlignment.center 次軸的排序方式
children List<Widget> const <Widget>[] 子組件

10. Expanded 組件,類似Web中的Flex佈局。

Expanded組件作爲Column或Row組件的子組件使用。可設置當前組件佔用父組件的比例。

參數 類型 可選 默認值 說明
flex Int 1 佔父組件的比例(權重)
child Widget 必填 -- 子組件

11. SizedBox 佔位組件

參數 類型 可選 默認值 說明
width double -- 寬度
height double -- 高度
child Widget 必填 -- 子組件

12. Stack 層疊組件

Stack組件可以單獨使用,也可配合 Align 和 Positioned 實現定位佈局。其實Stack有點類似於Android原生的幀佈局。

參數 類型 可選 默認值 說明
alignment AlignmentGeometry AlignmentDirectional.topStart 所有子組件的顯示位置
children List<Widget> const <Widget>[] 子組件
overflow Overflow Overflow.clip 子組件溢出後的處理方式
Stack(
      alignment: Alignment.center,
      children: [
        Align(
          alignment: Alignment.topCenter,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.orange,
        ),
        Positioned(
          bottom: 10,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.green,
          ),
        )
      ],
    );

13. Align

參數 類型 可選 默認值 說明
alignment AlignmentGeometry Alignment.center 所有子組件的顯示位置
child Widget -- 子組件

14. Positioned

參數 類型 可選 默認值 說明
top double -- 距頂部的距離
bottom double -- 距底部的距離
left double -- 距左邊的距離
right double -- 距右邊的距離
child Widget -- 子組件
width double -- 寬度
height double -- 高度

15. AspectRatio

作用是可以設置子組件的寬高比。

參數 類型 可選 默認值 說明
aspectRatio double -- 設置寬高比例
child Widget -- 子組件

16. Card

參數 類型 可選 默認值 說明
margin EdgeInsetsGeometry -- 外邊距
shape ShapeBorder -- 邊框及陰影效果,還可以使用RoundedRectangleBorder對其設置圓角
clipBehavior Clip Clip.none 對子組件的裁剪方式
elevation doublle 1.0 陰影高度
child Widget -- 子組件

17. CircleAvatar 原型頭像組件

參數 類型 可選 默認值 說明
backgroundImage ImageProvider -- 設置頭像
onBackgroundImageError Function(dynamic exception, StackTrace stackTrace) -- 圖片加載失敗的回調

18. Wrap組件(流式佈局組件)

參數 類型 可選 默認值 說明
spacing double 0.0 主軸的子組件之間的間隔
runSpacing double 0.0 次軸的子組件之間的間隔
direction Axis Axis.horizontal 子組件的排列方向,horizontal橫向,vertical縱向。
alignment WrapAlignment WrapAlignment.start 主軸子組件的對齊方式。
runAlignment WrapAlignment WrapAlignment.start 次軸子組件的對齊方式。
textDirection TextDirection -- 文本的排列方向。

19. RaisedButton & MaterialButton & FlatButton & OutlineButton & IconButton & FloatingActiongButton 按鈕組件。

Flutter中給我們預先定義好了一些按鈕控件給我們用,常用的按鈕如下

  • RaisedButton :凸起的按鈕,其實就是Android中的Material Design風格的Button ,繼承自MaterialButton

    如果要讓RaisedButton支持圖標,那麼可以使用RaisedButton.icon()

  • FlatButton :扁平化的按鈕,繼承自MaterialButton

  • OutlineButton :帶邊框的按鈕,繼承自MaterialButton

  • IconButton :圖標按鈕,繼承自StatelessWidget

  • FloatingActionButton : 浮動按鈕,繼承自StatelessWidget

按鈕通常情況下是不能直接設置寬高的,如果要設置寬高可是在外層包一個Container組件。也可以在外層包一個Expanded組件使其可以自適應寬度。

常用屬性如下

參數 類型 可選 默認值 說明
onPressed VoidCallback 必傳 -- 按下按鈕時觸發的回調方法,傳null表示按鈕禁用,會顯示禁用相關樣式。
child Widget -- 子組件,不傳就無法顯示內容。
textColor Color -- 文本顏色
color Color -- 按鈕的顏色
disabledColor Color -- 按鈕禁用時的顏色
disabledTextColor Color -- 按鈕禁用時的文本顏色
splashColor Color -- 點擊按鈕時水波紋的顏色
highlightColor Color -- 點擊(長按)按鈕後按鈕的顏色
elevation double -- 陰影的範圍,值越大陰影範圍越大
padding EdgeInsetsGeometry -- 內邊距
shape ShapeBorder -- 設置按鈕的形狀
minWidth double -- 最小寬度
height double -- 高度
materialTapTargetSize MaterialTapTargetSize MaterialTapTargetSize.padded 由於按鈕按下後通常會有陰影效果,所以按鈕默認都會有邊距,如果不希望有邊距可以通過該屬性設置,MaterialTapTargetSize.padded:有邊距。MaterialTapTargetSize.shrinkWrap:無邊距。

20. Chip 碎片組件

該組件一般用作標籤。

參數 類型 可選 默認值 說明
avatar Widget -- 一般用來設置左邊的圖標。
label Widget 必填 -- 一般用來設置文字。
deleteIcon Widget -- 刪除圖標,如果設置了onDeleted回調,即使該參數不設置也會顯示默認圖標。
deleteIconColor Color -- 刪除圖標的顏色。
deleteButtonTooltipMessage String delete 刪除圖標被按壓時的提示文字。
shape ShapeBorder 默認爲兩邊是半圓的形狀 設置背景形狀。
backgroundColor Color -- 背景顏色。
materialTapTargetSize MaterialTapTargetSize MaterialTapTargetSize.padded 由於按鈕按下後通常會有陰影效果,所以按鈕默認都會有邊距,如果不希望有邊距可以通過該屬性設置,MaterialTapTargetSize.padded:有邊距。MaterialTapTargetSize.shrinkWrap:無邊距。

21. BottomNavigationBar 底部導航條組件

參數 類型 可選 默認值 說明
currentIndex int -- 當前選中的Tab的索引。
items List<BottomNavigationBarItem> 必填 -- 設置當前有多少個Item。
iconSize double 24 圖標大小
fixedColor Color -- 選中後圖標及字體的顏色。該屬性通常不需要設置,會默認使用主體顏色。
type BottomNavigationBarType -- BottomNavigationBarType.fixed : 自動將所有item都顯示到屏幕上。

22. AppBar 導航欄組件

參數 類型 可選 默認值 說明
title Widget -- 標題
centerTitle bool false 標題是否居中顯示
actions List<Widget> -- 右邊菜單
leading Widget -- 左邊的導航圖標

23. DefaultTabController 組件(實現類似於Android中的TabLayout效果)

要實現類似Android中的LabLayout效果還需要結合另外兩個組件TabBarTabBarView一起使用,以下是這三個組件的具體參數介紹以及代碼示例:

  1. DefaultTabController

    參數 類型 可選 默認值 說明
    length int 必填 -- 設置有多少個tab。
    initialIndex int 0 默認選中第幾個tab,索引從0開始。
    child Widget 必填 -- child通常是一個Scaffold組件,但是需要有特俗的寫法。下面會給出栗子。
    1. TabBar
    參數 類型 可選 默認值 說明
    isScrollable bool false 設置是否開啓滾動,tab過多時建議開啓,否則無法顯示。
    tabs List<Widget> 必填 -- 設置tab,具體有多少個tab由集合的長度決定。
    controller TabController -- 用於自己定義控制器。
    indicatorColor Color -- 設置指示器的顏色,默認會根據主題色做出調整。
    indicatorWeight double 2.0 設置指示器的高度。
    indicatorPadding EdgeInsetsGeometry EdgeInsets.zero 底部指示器的padding,
    indicator Decoration -- 設置Tab的樣式,例如邊框等。
    indicatorSize TabBarIndicatorSize -- 指示器大小的計算方式,TabBarIndicatorSize.label:和文字等寬,TabBarIndicatorSize.tab:和tab等寬。
    labelColor Color -- 統一設置標籤字體顏色,默認與指示器顏色一致。
    lablStyle TextStyle -- 統一設置選中時標籤字體樣式。
    unselectedLabelColor Color -- 設置未選中時標籤字體顏色,默認與指示器顏色一致。
    unselectedLabelStyle TextStyle -- 設置未選中時標籤字體樣式。
    import 'package:flutter/material.dart';
    
    class MovieDetailPage extends StatelessWidget {
      final data;
    
      List<String> get _movies {
        return data['movies'];
      }
    
      int get _index {
        return data['index'];
      }
    
      MovieDetailPage(this.data);
    
      @override
      Widget build(BuildContext context) {
        return DefaultTabController(  // 1.要實現Android中的TabLayout需要使用DefaultTabController組件。
          initialIndex: this._index,
          length: _movies.length,
          child: Scaffold(    // 2.使用Scaffold作爲DefaultTabController組件的child。
            appBar: AppBar(   // 3.設置appBar。
              title: Text('電影專區'),
              centerTitle: true,
              bottom: TabBar(  // 4.設置bottom屬性爲TabBar組件。
                isScrollable: true,  // 5.如果tab過多時需要將isScrollable屬性設置爲true,否者無法顯示。默認爲false。
                tabs: _movies.map((movie) => Tab(text: movie)).toList()  // 6.設置tab的樣式,建議使用Tab組件。
              ),
            ),
            body: TabBarView(  // 7.設置body屬性爲TabBarView組件。
              children: _movies.map((movie) {   // 8.爲TabBarView設置每個頁面的具體內容(繪製每個頁面)。
                return Align(
                    alignment: Alignment.center, child: Text('電影《$movie》的詳情頁面。'));
              }).toList(),
            ),
          ),
        );
      }
    }
    
    
    //調用
    Navigator.push(context, MaterialPageRoute(builder: (context) {
      return MovieDetailPage({
        'index': 0,
        'movies': [
          '天龍八部',
          '別拿村長不當幹部',
          '長征',
          '白衣校花大長腿',
          '死侍',
          'X戰警',
          '007大破天幕殺機',
          '信條',
          '射鵰英雄傳',
          '新白娘子傳奇',
          '三體',
          '鹿鼎記',
          '我和殭屍有個約會',
          '奇異博士',
          '蜘蛛俠',
          '復仇者聯盟-無限戰爭',
          '復仇者聯盟-逆轉無限'
        ]
      });
    }));
    

    他還有一種高級用法,可以自己監聽生命週期,並監聽滾動事件等。具體用法如下:

    import 'package:flutter/material.dart';
    
    class SuperiorTabControllerPage extends StatefulWidget{
      @override
      State<StatefulWidget> createState() {
        return _SuperiorTabControllerPageState();
      }
    }
    
    class _SuperiorTabControllerPageState extends State<SuperiorTabControllerPage> with SingleTickerProviderStateMixin{
    
      TabController _tabController;
    
      @override
      void initState() {
        super.initState();
        _tabController = TabController(
          length: 2,
          vsync: this  //1.固定寫法
        );
        _tabController.addListener(() {
          print(this._tabController.index);
        });
      }
    
      @override
      void dispose() {
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('TabController的高級用法'),
            bottom: TabBar(
              controller: this._tabController,  //2.TabBar必須設置自己定義的TabController
              indicatorSize: TabBarIndicatorSize.label,
              tabs: [
                Tab(text: '科幻'),
                Tab(text: '懸疑')
              ],
            ),
          ),
          body: TabBarView(
            controller: this._tabController,  //3.TabBarView必須設置自己定義的TabController
            children: [
              Center(
                child: Text('科幻'),
              ),
              Center(
                child: Text('懸疑'),
              ),
            ],
          ),
        );
      }
    }
    
    
    //調用
    Navigator.push(context, MaterialPageRoute(
      builder: (context) => SuperiorTabControllerPage()
    ))
    

24. Divider 線條組件

該組件通常用於在一面中繪製一條線。

參數 類型 可選 默認值 說明
height double -- 線條的高度
color Color -- 線條的顏色

25. ListTile 條目組件

該組件可以快速繪製類似於個人中心頁面中的條目,或則手機設置頁面中的條目。

參數 類型 可選 默認值 說明
leading Widget -- 通常用於設置左側的圖標。
title Widget -- 通常用於設置標題。
subtitle Widget -- 通常用於設置子標題。
onTap GestureTapCallback -- 用於監聽點擊事件。

26. Drawer 抽屜組件

參數 類型 可選 默認值 說明
elevation double 16.0 設置陰影高度
child Widget 必填 -- 繪製抽屜中的內容

在使用Drawer組件的時候我們通常要爲抽屜設置頭,否則會有點難看。如果要設置頭就需要用到一下兩個組件:

  1. DrawerHeader

    參數 類型 可選 默認值 說明
    decoration Decoration -- 用於設置樣式,通常使用BoxDecoration。
    margin EdgeInsetsGeometry -- 設置外邊距
    padding EdgeInsetsGeometry -- 設置內邊距
    child Widget 必填 -- 繪製內容視圖
    1. UserAccountsDrawerHeader
    參數 類型 可選 默認值 說明
    accountName Widget 必填 -- 設置用戶名
    accountEmail Widget 必填 -- 設置用戶郵箱
    currentAccountPicture Widget -- 設置當前用戶頭像
    onDetailsPressed VoidCallback -- 用戶信息被點擊後的監聽。
    arrowColor Color -- 當設置了onDetailsPressed後右側會出現一個小的三角箭頭,該參數用於設置箭頭顏色。
    otherAccountsPictures List<Widget> -- 用於設置其他用戶頭像,設置後會在當前頭像的右側出現小的頭像。

    如果需要用代碼關閉抽屜可以使用下面的方式:

    Navigator.pop(context)
    

27. TextField 文本框組件

參數 類型 可選 默認值 說明
decoration InputDecoration InputDecoration() 設置邊框、hint、lable、圖標等
obscureText bool false 是否隱藏明文顯示內容(密碼模式)。
obscuringCharacter String -- 當obscureText爲true時,用於設置顯示的文字。
maxLines int 1 最大行數,當設置大於1時爲多行輸入模式。
minLines int -- 最小輸入行數,主要是用來設置最小高度。
controller TextEditingController -- 用於設置默認顯示內容以及獲取編輯框中的內容。
onChanged ValueChanged<String> -- 監聽內容的改變。
onEditingComplete VoidCallback -- 監聽焦點的丟失(編輯完成)

28. CheckBox 多選框組件

參數 類型 可選 默認值 說明
value bool 必填 -- 設置是否選中
onChanged ValueChanged<bool> 必填 -- 監聽選中狀態的改變

29. CheckboxListTile組件

參數 類型 可選 默認值 說明
value bool 必填 -- 設置是否選中
onChanged ValueChanged<bool> 必填 -- 監聽選中狀態的改變
title Widget -- 通常用於設置標題。
subtitle Widget -- 通常用於設置子標題。
secondary Widget -- 配置圖標或者圖片。
selected bool false 選中的時候文字是否跟着改變。

30. Radio 單選框組件

參數 類型 可選 默認值 說明
value T 必填 -- 設置當前單選框所代表的值。
onChanged ValueChanged<T> 必填 -- 監聽選中狀態的改變,並將當前單選框所代表的值(T)回傳。
groupValue T -- 爲當前單選框分組。

31. RadioListTile組件

參數 類型 可選 默認值 說明
value T 必填 -- 設置當前單選框所代表的值。
onChanged ValueChanged<T> 必填 -- 監聽選中狀態的改變,並將當前單選框所代表的值(T)回傳。
title Widget -- 通常用於設置標題。
subtitle Widget -- 通常用於設置子標題。
secondary Widget -- 配置圖標或者圖片。
selected bool false 選中的時候文字是否跟着改變。
groupValue T -- 爲當前單選框分組。

32. Switch 開關組件

參數 類型 可選 默認值 說明
value bool 必填 -- 設置是否選中
onChanged ValueChanged<bool> 必填 -- 監聽選中狀態的改變

33. 日期組件以及日期和時間戳

  • 日期和時間戳

    日期轉化成時間戳:

    print(DateTime.now().millisecondsSinceEpoch)
    //輸出:1600925003761
    

    時間戳轉換成日期:

    print(DateTime.fromMillisecondsSinceEpoch(1600925003761))
    //輸出:2020-09-24 13:23:23:761
    
  • 使用系統的日期選擇組件:

    showDatePicker(
      context: context,
      initialDate: DateTime.now(), //默認選中的日期
      firstDate: DateTime(2019),  //開始(最早)日期
      lastDate: DateTime(2050)  //結束(最晚)日期
    ).then((value) => setState(() => date = value));
    //showDatePicker方法返回的是Future<DateTime>類型,所以這裏使用then方法接收結果。但也可以使用async結合await的方式。
    
  • 使用系統的時間選擇組件:

    showTimePicker(context: context, initialTime: TimeOfDay.now())
      .then((value) => setState(() => time = value.format(context)));
    //showTimePicker方法返回的是Future<TimeOfDay>類型,所以這裏使用then方法接收結果。但也可以使用async結合await的方式。
    

四、第三方組件庫

雖然Flutter爲我們提供了很多的Widget,但有時Flutter爲我們提供的組件無法滿足我們需求,這時我們就需要使用一些第三方的Widget組件。我們可以通過網站pub.dev來找到我們想要的Widget組件進行使用。

1. Toast 組件

  • 地址:https://pub.dev/packages/toast

  • 使用:

    1. 在pubspec.yaml文件中的dependencies節點下添加toast: ^版本號:

      dependencies:
        flutter:
          sdk: flutter
      
        #Toast
        toast: ^0.1.5  #例如這裏添加0.1.5版本
      
    2. 在代碼中使用:

      import 'package:toast/toast.dart'
        
      Toast.show('我是一個Toast', context, gravity: Toast.CENTER);
      

2. Swiper 輪播圖組件

  • 地址:https://pub.dev/packages/flutter_swiper

  • 使用:

    1. 在pubspec.yaml文件中的dependencies節點下添加flutter_swiper: ^版本號:

      dependencies:
        flutter:
          sdk: flutter
      
        #輪播圖組件
        flutter_swiper: ^1.1.6  #例如這裏添加1.1.6版本
      
    2. 在代碼中使用:

      import 'package:flutter_swiper/flutter_swiper.dart';
      
      Swiper(
        itemCount: images.length,  //設置一共有多少個item
        itemBuilder: (context, index) {  //構建沒給item
          return Container(
            child: Image.network(images[index], fit: BoxFit.cover),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
            ),
            clipBehavior: Clip.antiAlias,
          );
        },
        pagination: SwiperPagination(),//顯示指示器
        autoplay: true,  //是否自動播放
        viewportFraction: 0.8, //當前頁的佔比,如果小於1,那麼剩下的部分將有左右兩邊填充。
        scale: 0.9, //每個Banner的縮放比例
        onTap: (i) => setState(()=> _currentIndex = i),  //條目點擊監聽
      )
      

3. DateFormat 日期格式化組件

  • 地址:https://pub.dev/packages/date_format

  • 在代碼中使用:

    1. 在pubspec.yaml文件中的dependencies節點下添加date_format: ^版本號:

      dependencies:
        flutter:
          sdk: flutter
      
        #日期格式化
        date_format: ^1.0.9  #例如這裏添加1.0.9版本
      
    2. 使用:

      由於該庫使用起來稍微有點麻煩,所以我對他進行了封裝(可能不是很完整,只是提供思路)。

      import 'package:date_format/date_format.dart' as df;
      
      String formatDate(DateTime date, DateType type){
        return df.formatDate(date, _getFormat(type));
      }
      
      _getFormat(DateType type){
        switch (type) {
          case DateType.yyyy_MM_dd:
            return ['yyyy', '-', 'mm', '-', 'dd'];
          case DateType.yyyy_MM_dd_HH:
            return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH'];
          case DateType.yyyy_MM_dd_HH_mm:
            return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn'];
          case DateType.yyyy_MM_dd_HH_mm_ss:
            return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn', ":", 'ss'];
          case DateType.HH_mm_ss:
            return ['HH', ':', 'nn', ":", 'ss'];
          case DateType.HH_mm:
            return ['HH', ':', 'nn'];
        }
      }
      
      enum DateType{
        yyyy_MM_dd,
        yyyy_MM_dd_HH,
        yyyy_MM_dd_HH_mm,
        yyyy_MM_dd_HH_mm_ss,
        HH_mm,
        HH_mm_ss
      }
      
      
      //使用方式如下
      formatDate(date, DateType.yyyy_MM_dd)
      

五、Dialog 彈窗

如果要使用AlertDialog則需要使用showDialog(context, builder)方法。該方法有兩個核心參數如下:

參數 類型 可選 默認值 說明
context BuildContext 必填 -- build方法中的上下文。
builder Widget Function(BuildContext context) 必填 -- 構建Dialog的回調函數。

第一個參數context比較簡單,只要將Widget的build方法中的context或則State中的context成員傳入即可。第二個參數builder則是一個Function。你需要定義一個Function並返回你創建好的Widget,這個Widget可以是以下幾種:

1. AlertDialog

參數 類型 可選 默認值 說明
title Widget -- 設置標題
titlePadding EdgeInsetsGeometry -- 標題的內邊距
titleTextStyle TextStyle -- 標題的字體樣式
content Widget -- 設置內容
contentPadding EdgeInsetsGeometry EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0) 內容的內邊距
contentTextStyle TextStyle -- 內容的字體樣式
actions List<Widget> -- 設置按鈕
showDialog(
        context: context,
        builder: (context){
          return AlertDialog(
            title: Text('提示'),
            content: Text('您覺的這個Demo對您的幫助大嗎?'),
            actions: [
              FlatButton(
                child: Text('是的'),
                onPressed: () {
                  Navigator.pop(context);
                  Toast.show(context, '謝謝您的認可~');
                },
              ),
              FlatButton(
                child: Text('非常大'),
                onPressed: () {
                  Navigator.pop(context);
                  Toast.show(context, '您真是一個好人吶~');
                },
              )
            ],
          );
        }
    );

有些情況下我們需要獲取Dialog操作後的返回值,這時可以分爲以下幾步:

  1. 自定義函數上加上async關鍵字。
  2. showDialog方法前面加上await關鍵字並定義一個變量接收該方法的返回值。
  3. Navigator.pop()退出彈窗時傳入要返回的值。
  4. 拿到showDialog方法的返回結果做相應處理。
_showAlertDialog() async{ //1.加上async關鍵字
    var result = await showDialog(  //2.加上await關鍵字並用變量接收
        context: context,
        builder: (context){
          return AlertDialog(
            title: Text('提示'),
            content: Text('您覺的這個Demo對您的幫助大嗎?'),
            actions: [
              FlatButton(
                child: Text('是的'),
                onPressed: () {
                  Navigator.pop(context, '您點擊了按鈕:是的');  //3.退出彈出時傳入要返回的值。
                  Toast.show(context, '謝謝您的認可~');
                },
              ),
              FlatButton(
                child: Text('非常大'),
                onPressed: () {
                  Navigator.pop(context, '您點擊了按鈕:非常大');  //3.退出彈出時傳入要返回的值。
                  Toast.show(context, '您真是一個好人吶~');
                },
              )
            ],
          );
        }
    );
    print(result);  //4.拿到showDialog方法的返回結果做相應處理。
  }

2. SimpleDialog

參數 類型 可選 默認值 說明
title Widget -- 設置標題
titlePadding EdgeInsetsGeometry EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0) 標題的內邊距
titleTextStyle TextStyle -- 標題的字體樣式
children List<Widget> -- 設置內容,通常使用SimpleDialogOption作爲元素。
contentPadding EdgeInsetsGeometry EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0) 內容的內邊距
backgroundColor Color -- 設置背景顏色
_showSimpleDialog() async{
    var result = await showDialog(
        context: context,
        builder: (context){
          return SimpleDialog(
            title: Center(
              child: Text('請您選擇'),
            ),
            children:[
              SimpleDialogOption(
                child: Text('第一個選項'),
                onPressed: (){
                  Navigator.pop(context, '您點擊了: 第一個選項');
                },
              ),
              Divider(height: 0),
              SimpleDialogOption(
                child: Text('第二個選項'),
                onPressed: (){
                  Navigator.pop(context, '您點擊了: 第二個選項');
                },
              ),
              Divider(height: 0),
              SimpleDialogOption(
                child: Text('第三個選項'),
                onPressed: (){
                  Navigator.pop(context, '您點擊了: 第三個選項');
                },
              )
            ],
          );
        }
    );
    print(result??'您取消了選擇');
  }

3. BottomSheet

如果要使用BottomSheet則需要使用showModalBottomSheet(context, builder)方法。該方法有兩個核心參數如下:

參數 類型 可選 默認值 說明
context BuildContext 必填 -- build方法中的上下文。
builder Widget Function(BuildContext context) 必填 -- 構建你的自定義佈局。
_showBottomSheet() async{
    var result = await showModalBottomSheet(
        context: context,
        isScrollControlled: true,
        builder: (context) {
          return Container(
            height: 290,
            child: Column(
              children: [
                ListTile(
                  title: Text('第一個條目'),
                  onTap: () => Navigator.pop(context, '第一個條目'),
                ),
                Divider(height: 0),
                ListTile(
                  title: Text('第二個條目'),
                  onTap: () => Navigator.pop(context, '第二個條目'),
                ),
                Divider(height: 0),
                ListTile(
                  title: Text('第三個條目'),
                  onTap: () => Navigator.pop(context, '第三個條目'),
                ),
                Divider(height: 0),
                ListTile(
                  title: Text('第四個條目'),
                  onTap: () => Navigator.pop(context, '第四個條目'),
                ),
                Divider(height: 0),
                ListTile(
                  title: Text('第五個條目'),
                  onTap: () => Navigator.pop(context, '第五個條目'),
                ),
              ],
            ),
          );
        }
    );
    print('您點擊了:$result');
  }

4. 自定義Dialog

在Flutter中,如果要自定義Dialog,需要以下幾個步驟:

  • 聲明一個類並繼承自Dialog。
  • 重寫Dialog中的Widget build(BuildContext context)方法。
  • Widget build(BuildContext context)方法中返回的Widget必須以Material組件作爲根節點。
  • 通常情況下我們的Dialog都是有透明背景的,就是Dialog彈出後仍然能看到一部分原來的頁面,所以我們需要給Material組件設置type屬性爲:MaterialType.transparency

下面的栗子就是自定義一個Dialog

class CustomDialog extends Dialog {

  @override
  Widget build(BuildContext context) {
    return Material(
      type: MaterialType.transparency,
      child: Stack(  //從這裏開始就可以寫我們自己想要的界面了。
        alignment: Alignment.center,
        children: [
          Container(
            margin: EdgeInsets.only(left: 40, right: 40),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(8),
                          topRight: Radius.circular(8))),
                  child: Column(
                    children: [
                      SizedBox(height: 12),
                      Text('提示',
                          style: TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 15)),
                      SizedBox(height: 12),
                      Divider(height: 0),
                    ],
                  ),
                ),
                Container(
                  padding: EdgeInsets.all(12),
                  color: Colors.white,
                  constraints: BoxConstraints(minHeight: 100),
                  child: Center(
                    child: Text('這是一個自定義彈出,你知道了嗎?'),
                  ),
                ),
                Divider(height: 0),
                Row(
                  children: [
                    Expanded(
                      child: Container(
                        height: 50,
                        child: FlatButton(
                          color: Colors.white,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.only(
                                  bottomLeft: Radius.circular(8))),
                          child: Text('不知道'),
                          materialTapTargetSize:
                          MaterialTapTargetSize.shrinkWrap,
                          onPressed: () {
                            Navigator.pop(context, '我看你該捱揍了。');
                          },
                        ),
                      ),
                    ),
                    VerticalDivider(width: 0.1),
                    Expanded(
                      child: Container(
                        height: 50,
                        child: FlatButton(
                          color: Colors.white,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.only(
                                  bottomRight: Radius.circular(8))),
                          child: Text('知道了',
                              style: TextStyle(color: Colors.blue)),
                          materialTapTargetSize:
                          MaterialTapTargetSize.shrinkWrap,
                          onPressed: () {
                            Navigator.pop(context, '不錯,你很聰明。');
                          },
                        ),
                      ),
                    )
                  ],
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

使用上面的自定義Dialog和普通的Dialog沒有什麼區別,下面是使用時的代碼:

 _showCustomDialog() async {
    var result = await showDialog(
        context: context,
        builder: (context) {
          return CustomDialog();
        });
    print(result);
  }

如果要實現打開彈窗後的一段時間之後自動關閉彈窗則可以結合Dart中的Timer定時器實現:

Timer.periodic(Duration(seconds: 5), (timer) {
  timer.cancel();
  //do something,such as dismiss the dialog.
});

六、註解

1. @override 重寫

該註解用於表示一個成員是繼承自父類。

2. @protected

該註解只能應用於一個類中的成員,表示該成員只能被子類調用或擴展,也可以在混合類中直接或間接的調用。

3. @mustCallSuper

該註解應用在方法上,表示該方法如果被子類重寫了,那麼子類則必須調用super。

七、路由

Flutter中路由的核心類爲Navigator,如果要打開新的頁面則需要調用push方法,如果要退出頁面則需要調用pop方法。

1. 普通路由

Navigator.push(context, MaterialPageRoute(
  builder:(context){
    return SecondPage();   //這裏返回目標頁面。
  }
));

2. 普通路由傳值

普通路由的傳值方式其實就是在構造要跳轉的頁面時將參數傳入。

Navigator.push(context, MaterialPageRoute(
  builder:(context){
    return SecondPage(title: '第二個頁面');   //這裏構建要跳轉的頁面時直接傳入參數。
  }
));

class SecondPage extends StatelessWidget{

  final String title;
  SecondPage({this.title = ""});  //這裏接受外面傳入的參數

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(this.title),  //這裏使用外面傳入的參數
      ),
      body: Align(
        alignment: Alignment.center,
        child: Text("我是${this.title}"),  //這裏使用外面傳入的參數
      ),
    );
  }
}

3. 命名路由

命名路由的使用分以下兩步:

  1. 在主入口中的MaterialApp中配置routes,routes接受的數據類型時Map,Map的key是路由命名,值是初始具體跳轉到某個頁面的回調。

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Tabs(),
          routes: {  //使用routes參數配置命名路由。
            '/second': (context) => SecondPage()  //這裏定義了一個命名路由‘/second’並制定跳轉到SecondPage頁面。
          },
        );
      }
    }
    
  1. 利用pushNamed方法使用已經定義好的路由。

    Navigator.pushNamed(context, '/second'); //這裏的‘/second’就是我們已經定義好的路由,必須和已經定義好的名稱保持一致。
    

4. 命名路由傳值

命名路由的傳值略微有點複雜,具體代碼如下:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

  //將我們所有的路由定義爲成員變量。
  final routes = {
    '/second': (context, {arguments}) => SecondPage(title: arguments['title'])
  };

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Tabs(),
        onGenerateRoute: (RouteSettings settings){  //利用onGenerateRoute自己處理傳參及跳轉。
          final String name = settings.name;  //獲取路由名稱
          final Function pageContentBuilder = this.routes[name]; //根據名稱獲取具體跳轉的方法
          if(pageContentBuilder != null) { //判斷方法是否爲空,如果空則拋出異常。
            return MaterialPageRoute(
                builder: (context) {
                  if(settings.arguments != null) {  //如果有參數則調用我們自定義的方法傳入參數
                    return pageContentBuilder(context, arguments: settings.arguments);
                  }else {
                    return pageContentBuilder(context);  //沒有參數時則忽略arguments
                  }
                }
            );
          }else {
            throw ArgumentError('route "$name" not implements!');
          }
        },
    );
  }
}

路由已經定義好了,那麼調用的方式如下:

Navigator.pushNamed(context, '/second', arguments: {
  'title': '第二個頁面'
});

代碼抽離:

  1. 入口文件

    import 'package:flutter/material.dart';
    import 'package:flutter_demo_one/pages/route.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            initialRoute: '/',
            onGenerateRoute: generateRoute,
        );
      }
    }
    
  2. Route文件

    import 'package:flutter/material.dart';
    import 'package:flutter_demo_one/pages/hospital/hospitalDetailPage.dart';
    
    import 'pages/main/tabs.dart';
    
    final routes = {
      '/': (context) => Tabs(),
      '/hospital': (context, {arguments}) => HospitalDetailPage(arguments)
    };
    
    Function generateRoute = (RouteSettings settings){
      final String name = settings.name;
      final Function pageContentBuilder = routes[name];
      if(pageContentBuilder != null) {
        return MaterialPageRoute(
            builder: (context) {
              if(settings.arguments != null) {
                return pageContentBuilder(context, arguments: settings.arguments);
              }else {
                return pageContentBuilder(context);
              }
            }
        );
      }else {
        throw ArgumentError('route "$name" not implements!');
      }
    };
    

5. 替換路由

替換路由其實就是用要打開的新的頁面來替換當前頁面,例如:A是第一頁面(主頁面),A正常打開了B頁面,而B又用替換路由打開了C,那麼此時的頁面棧中就只有AC兩個頁面,如果在C頁面中點擊返回按鈕後( Navigator.pop() )就會直接顯示A而不會顯示B。

注意:使用替換路由時,被打開的頁面是否有返回按鈕(左上角的返回箭頭)取決於當前頁面是否有返回按鈕。

Navigator.pushReplacementNamed(context, '/testPage2', arguments: {
    'title':'頁面標題'
});

八、 網絡請求

1. Json 與 Map 相互轉換。

  • Json轉Map

    final String _json = '{"username":"張三","age":23}'; //json字符串數據
    
    print(json.decode(_json)['username'])  //輸出:張三
    
  • Map轉Json

    final Map _map = {
      "username":"張三",
      "age": 23
    };
    print(context, json.encode(_map))  //輸出:{"username":"張三","age":23}
    

2. 使用http庫進行網絡請求

  • 地址:https://pub.dev/packages/http

  • 使用:

    1. 在pubspec.yaml文件中的dependencies節點下添加http: ^版本號:

      dependencies:
        http: ^0.12.2
      
    2. 在代碼中使用:

      get請求

      import 'package:http/http.dart';
      
      _onGetData(apiUrl) async{
        var result = await get(apiUrl);
        if(result.statusCode == 200) {
          print(result.body);
        }else {
          print('接口請求錯誤!');
        }
      }
      

      post請求

      _doLogin(context, username, psw, {url = ''}) async {
        var result = await post(url, body: {'username':username, 'psw':psw});
        if (result.statusCode == 200) {
          print(result.body);
        } else {
          print('接口請求錯誤!');
        }
      }
      

3. dio 庫請求

​ dio是一個強大的Dart Http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載、超時、自定義適配器等...

  • 地址:https://pub.dev/packages/dio github地址:https://github.com/flutterchina/dio

  • 使用:

    1. 在pubspec.yaml文件中的dependencies節點下添加dio: ^版本號:

      dependencies:
        dio: ^3.0.7
      
    2. 簡單使用

      import 'package:dio/dio.dart';
      void getHttp() async {
        try {
          Response response = await Dio().get("http://www.baidu.com");
          print(response);
        } catch (e) {
          print(e);
        }
      }
      
    3. 發起一個 GET 請求 :

      Response response;
      Dio dio = Dio();
      response = await dio.get("/test?id=12&name=wendu")
      print(response.data.toString());
      // 請求參數也可以通過對象傳遞,上面的代碼等同於:
      response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
      print(response.data.toString());
      
    4. 發起一個 POST 請求:

      response = await dio.post("/test", data: {"id": 12, "name": "wendu"});
      
    5. 發起多個併發請求:

      response = await Future.wait([dio.post("/info"), dio.get("/token")]);
      
    6. 下載文件:

      response = await dio.download("https://www.google.com/", "./xx.html");
      
    7. 以流的方式接收響應數據:

      Response<ResponseBody> rs = await Dio().get<ResponseBody>(url,
        options: Options(responseType: ResponseType.stream), //設置接收類型爲stream
      );
      print(rs.data.stream); //響應流
      
    8. 以二進制數組的方式接收響應數據:

      Response<List<int>> rs = await Dio().get<List<int>>(url,
       options: Options(responseType: ResponseType.bytes), //設置接收類型爲bytes
      );
      print(rs.data); //二進制數組
      
    9. 發送 FormData:

      FormData formData = FormData.from({
          "name": "wendux",
          "age": 25,
        });
      response = await dio.post("/info", data: formData);
      
    10. 通過FormData上傳多個文件:

      FormData.fromMap({
          "name": "wendux",
          "age": 25,
          "file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt"),
          "files": [
            await MultipartFile.fromFile("./text1.txt", filename: "text1.txt"),
            await MultipartFile.fromFile("./text2.txt", filename: "text2.txt"),
          ]
       });
      response = await dio.post("/info", data: formData);
      
    11. 監聽發送(上傳)數據進度:

      response = await dio.post(
        "http://www.dtworkroom.com/doris/1/2.0.0/test",
        data: {"aa": "bb" * 22},
        onSendProgress: (int sent, int total) {
          print("$sent $total");
        },
      );
      
    12. 以流的形式提交二進制數據:

      // 二進制數據
      List<int> postData = <int>[...];
      await dio.post(
        url,
        data: Stream.fromIterable(postData.map((e) => [e])), //創建一個Stream<List<int>>
        options: Options(
          headers: {
            Headers.contentLengthHeader: postData.length, // 設置content-length
          },
        ),
      );
      

    注意:如果要監聽提交進度,則必須設置content-length,反之則是可選的。

九、其他

1. 國際化

  • 第一步:找到pubspec.yaml文件,配置flutter_localizations(國際化)。

    dependencies:
      flutter:
        sdk: flutter
    
      #國際化配置
      flutter_localizations:
        sdk: flutter
    
  • 第二步:導入國際化的包flutter_localizations(AS可以自動導包)。

    import 'package:flutter_localizations/flutter_localizations.dart';
    
  • 第三步:在main入口中的MaterialApp組件中配置國際化:

    import 'package:flutter/material.dart';
    import 'package:flutter_demo_one/route.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.blue
          ),
          initialRoute: '/',
          onGenerateRoute: generateRoute,
          
          //以下是國際化配置,配置下面localizationsDelegates和supportedLocales兩個屬性即可實現國際化。
          localizationsDelegates: [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate
          ],
          supportedLocales: [
            const Locale('zh', 'CH'),
            const Locale('en', 'US')
          ],
        );
      }
    }
    

2. 沉浸式狀態欄

只需要在入口處build方法中return前加入下面一行代碼,將狀態欄的顏色設置爲透明色即可:

SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章