Flutter常用Widget詳解(二)

Flutter程式設計指南發表於2019-02-21

前言

相信不少同學已經通過線上直播觀看了本週Google舉辦的Flutter Live 2018,在本次活動中Google正式釋出了Flutter 1.0版本,這對於正在學習Flutter或已經使用Flutter進行應用開發的我們都是一個好訊息,1.0版本中增加了一些新的特性,並且是目前最穩定的版本,沒有了解本次活動內容的同學可以通過如下連結檢視。

Flutter 1.0正式版:Google的便攜UI工具包

Flutter Live 2018舉辦後也在本週掀起了一波Flutter普及小熱潮,本公眾號將一如既往的給大家分享學習Flutter過程的心得體會、經驗總結和開發實戰。希望給初學Flutter的同學一點兒幫助,同時也可以和正在使用Flutter進行應用開發的同學一起交流學習。

話不多說,下面我們進入本篇主題。上一篇文章給大家分享了一部分Flutter日常開發中常用的Widget和其屬性的使用介紹,其中包括文字、圖片、按鈕、輸入控制元件和選擇控制元件等,這些都是應用開發中最基本的UI展示控制元件,接下來我們將繼續向大家詳細介紹其他一些常用Widget。

常用Widget介紹

日期、時間選擇器和通用選擇器

選擇器對應的Widget在Flutter中也有兩種風格的實現,具體用法和使用例子如下。

Material design風格的日期選擇器
showDatePicker(
  context: context,
  initialDate: DateTime.parse("20181209"), //初始選中日期
  firstDate: DateTime.parse("20181109"), //可選日期範圍第一個日期
  lastDate: DateTime.parse("20190109"), //可選日期範圍最後一個日期
  selectableDayPredicate: (dateTime) { //通過此方法可以過濾掉可選範圍內不可選的特定日期
    if(dateTime.day == 10 || dateTime.day == 20 || dateTime.day == 30) {
      //此處表示10號、20號、30號不可選
      return false;
    }
    return true;
  },
  initialDatePickerMode: DatePickerMode.day, //初始化選擇模式,有day和year兩種
).then((dateTime) { //選擇日期後點選OK拿到的日期結果
  print('當前選擇了:${dateTime.year}年${dateTime.month}月${dateTime.day}日');
});
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

Material design風格的時間選擇器
showTimePicker(
  context: context,
  initialTime: TimeOfDay.now(), //初始化顯示時間
).then((timeOfDay) { //選擇時間後點選OK拿到的時間結果
  if(timeOfDay == null) {
    return;
  }
  print('當前選擇了:${timeOfDay.hour}時${timeOfDay.minute}分');
});
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

Cupertino風格的日期選擇器CupertinoDatePicker
CupertinoDatePicker(
  mode: CupertinoDatePickerMode.date, //日期時間模式,此處為日期模式
  onDateTimeChanged: (dateTime) { //日期改變時呼叫的方法
    if (dateTime == null) {
      return;
    }
    print('當前選擇了:${dateTime.year}年${dateTime.month}月${dateTime.day}日');
  },
  initialDateTime: DateTime.now(), //初始化展示時的日期時間
  minimumYear: 2018, //最小年份,只有mode為date時有效
  maximumYear: 2019, //最大年份,只有mode為date時有效
),

CupertinoDatePicker(
  mode: CupertinoDatePickerMode.dateAndTime, //日期時間模式,此處為日期和時間模式
  onDateTimeChanged: (dateTime) {
    if (dateTime == null) {
      return;
    }
    print('當前選擇了:${dateTime.year}年${dateTime.month}月${dateTime.day}日 ${dateTime.hour}時${dateTime.minute}分${dateTime.second}秒');
  },
  initialDateTime: DateTime.now(),
  minimumDate: DateTime.parse("20181109"), //最小日期時間,只有mode為dateAndTime時有效
  maximumDate: DateTime.parse("20190109"), //最大日期時間,只有mode為dateAndTime時有效
  use24hFormat: false, // 是否使用24小時格式,此處不使用,則選擇時可以選擇AM和PM值
),

CupertinoDatePicker(
  mode: CupertinoDatePickerMode.time, //日期時間模式,此處為時間模式
  onDateTimeChanged: (dateTime) {
    if (dateTime == null) {
      return;
    }
    print('當前選擇了:${dateTime.hour}時${dateTime.minute}分${dateTime.second}秒');
  },
  initialDateTime: DateTime.now(),
  use24hFormat: true, // 是否使用24小時格式,此處使用
),
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

Cupertino風格的時間選擇器CupertinoTimerPicker
CupertinoTimerPicker(
  mode: CupertinoTimerPickerMode.hms, //可以設定時分、時分秒和分秒三種模式
  initialTimerDuration: Duration(hours: 1, minutes: 35, seconds: 50), // 預設顯示的時間值
  minuteInterval: 5, // 分值間隔,必須能夠被initialTimerDuration.inMinutes整除
  secondInterval: 10, // 秒值間隔,必須能夠被initialTimerDuration.inSeconds整除,此時設定為10,則選擇項為0、10、20、30、40、50六個值
  onTimerDurationChanged: (duration) {
    print('當前選擇了:${duration.inHours}時${duration.inMinutes-duration.inHours*60}分${duration.inSeconds-duration.inMinutes*60}秒');
  },
)
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

Cupertino風格的通用選擇器CupertinoPicker
CupertinoPicker(
  backgroundColor: Colors.white, //選擇器背景色
  itemExtent: 30, //item的高度
  onSelectedItemChanged: (index) { //選中item的位置索引
    print("index = $index}");
  },
  children: <Widget>[ //所有的選擇項
    Text('Apple'),
    Text('Banana'),
    Text('Orange'),
  ],
)
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

常用彈框和資訊提示浮層

常見的彈框主要包含Material design風格的SimpleDialog、AlertDialog、BottomSheet等,和Cupertino風格的CupertinoDialog、CupertinoAlertDialog、CupertinoActionSheet等。

SimppleDialog和AlertDialog

SimpleDialog和AlertDialog本身都是一個Widget,使用時需要通過showDialog方法來展示。

// 展示SimpleDialog
showDialog( //展示Dialog的方法
  context: context,
  builder: (context) {
    return SimpleDialog(
      title: Text('評價一下'), //標題
      titlePadding: EdgeInsets.all(20), //標題的padding值
      children: <Widget>[ //彈框中的選項
        SimpleDialogOption( //每一個選項都是一個SimpleDialogOption Widget
          onPressed: (){
            print('給個好評');
            Navigator.pop(context);
          },
          child: Text('給好評'), //選項提示文案
        ),
        SimpleDialogOption(
          onPressed: (){
            print('殘忍拒絕');
            Navigator.pop(context);
          },
          child: Text('殘忍拒絕'),
        ),
        SimpleDialogOption(
          onPressed: (){
            print('我有意見');
            Navigator.pop(context);
          },
          child: Text('我有意見'),
        ),
      ],
      contentPadding: EdgeInsets.all(0),
    );
  },
);

//展示AlertDialog
showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: Text('提示'), //標題
      titlePadding: EdgeInsets.all(20), //標題的padding值
      content: Text('是否想放棄學習Flutter'), //彈框展示主要內容
      contentPadding: EdgeInsets.only(left: 20, right: 20), //內容的padding值
      actions: <Widget>[ //操作按鈕陣列
        FlatButton(
          onPressed: () {
            print("取消");
            Navigator.pop(context);
          },
          child: Text('取消'),
        ),
        FlatButton(
          onPressed: () {
            print('確定');
            Navigator.pop(context);
          },
          child: Text('確定'),
        ),
      ],
    );
  },
);
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

永續性BottomSheet和模態BottomSheet

BottomSheet一般不會直接建立,通常是通過ScaffoldState.showBottomSheet方法來建立永續性BottomSheet,通過showModalBottomSheet方法來建立模態BottomSheet。

// 建立永續性BottomSheet
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
  return Scaffold(
    key: _scaffoldKey, //設定key值以便獲取ScaffoldState
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: _buildBottomSheet(context)
  );
}

Widget _buildBottomSheet(BuildContext context) {
  return Container(
    child: RaisedButton(
        child: Text("BottomSheet"),
        onPressed: () {
          print("彈出BottomSheet");
          //通過獲取當前ScaffoldState來展示BottomSheet
          _scaffoldKey.currentState.showBottomSheet<void>((context){
            return Container(
                decoration: BoxDecoration(
                    border: Border(top: BorderSide(color: Colors.grey))
                ),
                child: Padding(
                    padding: const EdgeInsets.all(20),
                    child: Text('This is a Material persistent bottom sheet. Drag downwards to dismiss it.',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                            color: Colors.blueAccent,
                            fontSize: 22
                        )
                    )
                )
            );
          });
        }
    ),
  );
}

//建立模態BottomSheet
Widget _buildModalBottomSheet(BuildContext context) {
  return Container(
      child: RaisedButton(
          child: Text("ModalBottomSheet"),
          onPressed: () {
            print("ModalBottomSheet");
            //直接使用showModalBottomSheet方法建立模態BottomSheet
            showModalBottomSheet(
              context: context,
              builder: (context) {
                return Container(
                    decoration: BoxDecoration(
                        border: Border(top: BorderSide(color: Colors.grey))
                    ),
                    child: Padding(
                        padding: const EdgeInsets.all(20),
                        child: Text('This is a Material modal bottom sheet. Drag downwards to dismiss it.',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                                color: Colors.blueAccent,
                                fontSize: 22
                            )
                        )
                    )
                );
              },
            );
          }
      )
  );
}
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

CupertinoAlertDialog

由於CupertinoDialog已經被標記為過時的Widget,這裡就只介紹CupertinoAlertDialog的用法。

showDialog( //通過showDialog方法展示alert彈框
  context: context,
  builder: (context) {
    return CupertinoAlertDialog(
      title: Text('提示'), //彈框標題
      content: Text('是否想放棄學習Flutter'), //彈框內容
      actions: <Widget>[ //操作控制元件
        CupertinoDialogAction(
          onPressed: () { //控制元件點選監聽
            print("我不會放棄的");
            Navigator.pop(context);
          },
          textStyle: TextStyle(fontSize: 18, color: Colors.blueAccent), //按鈕上的文字風格
          child: Text('取消'), //控制元件顯示內容
        ),
        CupertinoDialogAction(
          onPressed: () {
            print("我投降");
            Navigator.pop(context);
          },
          textStyle: TextStyle(fontSize: 18, color: Colors.grey),
          child: Text('確定'),
        ),
      ],
    );
  },
);
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

CupertinoActionSheet

該Widget通常作為子Widget傳遞給showCupertinoModalPopup方法,由該方法將其通過從螢幕底部向上滑動來顯示。

showCupertinoModalPopup(
  context: context,
  builder: (context) {
    return CupertinoActionSheet(
      title: Text('提示', style: TextStyle(fontSize: 22),), //標題
      message: Text('麻煩抽出幾分鐘對該軟體進行評價,謝謝!'), //提示內容
      actions: <Widget>[ //操作按鈕集合
        CupertinoActionSheetAction(
          onPressed: (){
            Navigator.pop(context);
          },
          child: Text('給個好評'),
        ),
        CupertinoActionSheetAction(
          onPressed: (){
            Navigator.pop(context);
          },
          child: Text('我要吐槽'),
        ),
      ],
      cancelButton: CupertinoActionSheetAction( //取消按鈕
        onPressed: () {
          Navigator.pop(context);
        },
        child: Text('取消'),
      ),
    );
  },
);
複製程式碼

顯示效果如下圖:

Flutter常用Widget詳解(二)

導航欄和標籤欄

導航欄和標籤欄是頁面框架搭建常用的控制元件,Flutter中主要包含的對應Widget有Material design風格的BottomNavigationBar、Tabbar和Cupertino風格的CupertinoNavigationBar、CupertinoTabBar等等。

BottomNavigationBar

該Widget通常在Material design風格的頁面框架Widget Scaffold中使用,作為Scaffold的一個bottomNavigationBar屬性值。具體使用方法如下:

  int selectedIndex = 1;
  final widgetOptions = [
    Text('This is Home Page'),
    Text('This is Product Page'),
    Text('This is More Page'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //應用欄
        title: Text(widget.title),
      ),
      body: widgetOptions[selectedIndex], //頁面內容
      bottomNavigationBar: BottomNavigationBar( //底部導航欄
        items: <BottomNavigationBarItem>[ //導航欄選項集合
          BottomNavigationBarItem( //底部單個導航欄選項
            icon: Icon(Icons.home), //圖示
            title: Text('首頁'), //標題
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text('產品'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.more_horiz),
            title: Text('更多'),
          ),
        ],
        currentIndex: selectedIndex, //當前導航欄選中的索引
        fixedColor: Colors.redAccent, //選中項的標題顏色
        onTap: (index) { //導航欄項點選後的處理方法
          setState(() {
            selectedIndex = index;
          });
        },
      ),
    );
  }
複製程式碼
Tabbar

Tabbar通常建立為AppBar的AppBar.bottom部分,使用方式如下:

  TabController _controller;
  int _selectedIndex = 0;
  final List<Widget> _tabViews = [
    Container(
      child: Text('This is hot page'),
    ),
    Container(
      child: Text('This is tech page'),
    ),
    Container(
      child: Text('This is financial page'),
    ),
  ];
  final List<Tab> _tabs = [
    Tab(
      text: '熱門', //標題,和child不能同時存在,只能設定一個
//      child: Text('熱門'), //標題,和text不能同時存在
      icon: Icon(Icons.home), //標題對應的圖示
    ),
    Tab(
      text: '科技',
//      child: Text('科技'),
      icon: Icon(Icons.list),
    ),
    Tab(
      text: '金融',
//      child: Text('金融'),
      icon: Icon(Icons.more),
    ),
  ];

  @override
  void initState() {
    super.initState();
    _controller = TabController(vsync: this, length: _tabs.length);
    _controller.addListener(_handleTabSelection);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _controller.dispose();
  }

  void _handleTabSelection() {
    setState(() {
      _selectedIndex = _controller.index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //應用欄
        title: Text(widget.title),
        bottom: TabBar(
          controller: _controller, //TabBar控制器,通過給controller物件新增addListener方法來監聽切換動作
          tabs: _tabs, //標籤欄顯示項集合
        ),
      ),
      body: _tabViews[_selectedIndex], //頁面顯示的內容
    );
  }
複製程式碼

BottomNavigationBar和Tabbar顯示效果如下圖:

Flutter常用Widget詳解(二)

CupertinoNavigationBar

Cupertino風格的頂部導航欄,通常與CupertinoPageScaffold一起使用。

CupertinoPageScaffold(
  navigationBar: CupertinoNavigationBar(
    middle: Center(child: Text('詳情', style: TextStyle(color: Colors.white),),), //導航欄中間控制元件
    leading: Icon(Icons.arrow_back_ios, size: 18,), //導航欄左邊控制元件
    trailing: Text('退出'), //導航欄右邊控制元件
    backgroundColor: Colors.blueAccent, //導航欄背景顏色
    actionsForegroundColor: Colors.white, //leading和trailing圖示或文字顏色
  ),
  child: SafeArea(
    top: false,
    bottom: false,
    child: Container(
      child: Text('This is a cupertino style page', style: TextStyle(fontSize: 16, color: Colors.black),),
    ),
  ),
);
複製程式碼
CupertinoTabBar

Cupertino風格的標籤欄,通常與CupertinoTabScaffold一起使用,作為CupertinoTabScaffold的tabBar屬性值。具體使用方法如下:

  final List<String> _titles = ['首頁', '產品', '更多'];
  final List<Text> _pageContents = [Text('This is Home page'), Text('This is Product page'), Text('This is More page')];
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar( //作為整個頁面框架的底部標籤欄
        currentIndex: _selectedIndex, //當前定位的索引
        onTap: (index) { //點選標籤欄的事件監聽方法
          setState(() {
            _selectedIndex = index;
          });
        },
        items: <BottomNavigationBarItem> [ //標籤欄項集合
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text(_titles[0]),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text(_titles[1]),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.more_horiz),
            title: Text(_titles[2]),
          ),
        ],
      ),
      tabBuilder: (BuildContext context, int index) { //標籤欄對應的頁面建立
        return CupertinoTabView(
          builder: (BuildContext context) {
            return CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(
                middle: Text(_titles[index]),
              ),
              child: Center(
                child: _pageContents[index],
              ),
            );
          },
        );
      },
    );
  }
複製程式碼

CupertinoNavigationBar和CupertinoTabBar顯示效果如下圖:

Flutter常用Widget詳解(二)

總結

本篇我們對Flutter開發中常見的選擇器、彈框和標題欄、標籤欄進行了介紹,相信大家通過閱讀例子程式碼已經有了一個直觀的瞭解,後續我們將繼續介紹常用Widget之佈局Widget,敬請期待。

說明:

文章轉載自對應的“Flutter程式設計指南”微信公眾號,更多Flutter相關技術文章請關注微信公眾號獲取。

相關文章