Flutter ModalBottomSheet 指導

會煮咖啡的貓發表於2021-05-18

老鐵記得 轉發 ,貓哥會呈現更多 Flutter 好文~~~~

微信 flutter 研修群 ducafecat

原文

evandrmb.medium.com/flutter-mod…

程式碼

github.com/evandrmb/bo…

參考

正文

根據材質設計指南,底部表是一個小工具,用於顯示錨定在螢幕底部的附加內容。雖然瞭解使用這個的設計規則很好,但這不是本文的目標。要了解更多關於底板設計原則的詳細資訊,請參閱“Sheets: bottom — Material Design”。

現在你知道了 BottomSheet,你可能會問自己: 什麼是 ModalBottomSheet?我們如何使用他們在 Flutter?

好的,第一個問題,有兩種底層表: 模態的和持久的。當使用者與螢幕互動時,持久化保持可見。谷歌地圖應用就是一個例子。

另一方面,模式化的操作會阻止使用者在應用程式中做其他動作。您可以使用它們來確認某些操作,或者請求額外的資料,比如詢問使用者在電子商務應用程式中訂購時需要多少交換,等等。

在本文中,我們將通過建立一個簡單的體重跟蹤應用程式來展示如何使用它,在這個應用程式中我們可以提交我們的體重並檢視我們之前的體重。我們不會輸入應用程式的詳細資訊,而是直接進入 ModalBottomSheet 實現。

要顯示它,您需要從具有 Scaffold 的上下文呼叫 showModalBottomSheet,否則,您將得到一個錯誤。也就是說,讓我們開始構建我們的表格。

首先要知道的是 ModalBottomSheets 的高度預設為螢幕的一半,為了改變它,必須傳遞 true 給 isScrollControlled 引數,並返回一個與我們期望的大小相匹配的小部件,所以讓我們這樣做。

void addWeight() {
    showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      builder: (context) {
        var date = DateTime.now();

		    return Container(
          height: 302,
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [

            ],
          ),
        );
      },
    );
  }
複製程式碼

現在,我們需要新增一些東西,以便我們的使用者可以輸入他們的權重讓我們新增一個 TextInput 並給它一個 TextEditingController (這種方式即使我們的工作表意外關閉時,使用者再次開啟它,它的值仍然存在)。

void addWeight() {
    showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      builder: (context) {
        var date = DateTime.now();

		    return Container(
          height: 302,
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
				      padding: EdgeInsets.only(bottom: 24.0),
                child: Text(
                  'Register Weight',
                  style: Styles.titleStyle,
                ),
              ),
              TextField(
                controller: weightInputController,
                keyboardType: TextInputType.number,
                decoration: InputDecoration(
                  labelText: 'Weight (KG)',
                  border: OutlineInputBorder(
                    borderRadius: Styles.borderRadius,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
複製程式碼

看起來不錯,但現在我們有麻煩了。當使用者點選我們的 TextField 鍵盤在它上面,為什麼?當鍵盤開啟時,我們的工作表不會調整位置,我們可以把工作表做得更大,但這不能解決我們的問題,因為我們仍然需要新增一個欄位,使用者可以在其中輸入他們記錄重量的日期。那麼解決方案是什麼呢?這很簡單,如果開啟鍵盤,我們讓我們的工作表在它上面,我們可以實現這一點,給我們的容器一個邊距的邊緣。在 viewinset.bottom 中,我們將得到以下結果:

它開始看起來很漂亮,但是你不認為如果我們在紙上加一些半徑會更平滑嗎?讓我們通過新增如下所示的 shapeproperty 來實現。

showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(8),
            topRight: Radius.circular(8),
          )),
複製程式碼

酷,現在讓我們做我們的小工具來選擇一個日期。通常,您會建立一個小部件來處理這個邏輯,並使用 ValueChanged 函式公開選定的值,但是為了說明您將來可能面臨的問題,讓我們在工作表本身內部建立所有邏輯。

void addWeight() {
    showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.only(
        topLeft: Radius.circular(8),
        topRight: Radius.circular(8),
      )),
      builder: (context) {
        return Container(
          height: 360,
          width: MediaQuery.of(context).size.width,
          margin:
              EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
          padding: const EdgeInsets.all(16.0),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Padding(
                padding: EdgeInsets.only(bottom: 24.0),
                child: Text(
                  'Register Weight',
                  style: Styles.titleStyle,
                ),
              ),
              TextField(
                controller: weightInputController,
                keyboardType: TextInputType.number,
                decoration: InputDecoration(
                  labelText: 'Weight (KG)',
                  border: OutlineInputBorder(
                    borderRadius: Styles.borderRadius,
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: Row(
                  children: [
                    Expanded(
                      child: Text(
                        'Select a date',
                        style: TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 4),
                      margin: const EdgeInsets.symmetric(vertical: 8.0),
                      height: 36,
                      decoration: BoxDecoration(
                        borderRadius: Styles.borderRadius,
                      ),
                      child: OutlinedButton(
                        onPressed: () async {
                          final now = DateTime.now();

                          final result = await showDatePicker(
                              context: context,
                              initialDate: now,
                              firstDate: now.subtract(
                                const Duration(
                                  days: 90,
                                ),
                              ),
                              lastDate: now);

                          if (result != null) {
                            setState(() {
                              selectedDate = result;
                            });
                          }
                        },
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Padding(
                              padding: const EdgeInsets.only(right: 16.0),
                              child:
                                  Text('${formatDateToString(selectedDate)}'),
                            ),
                            Icon(Icons.calendar_today_outlined),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              )

            ],
          ),
        );
      },
    );
  }
複製程式碼

需要注意的是,我已經在我們的主頁中新增了 selectedDatevariable,你可以在我最後提供的儲存庫連結中看到這一點。但是現在我們遇到了一個問題,儘管我們正在使用 setstateoutlinebutton 更新 selectedDate 的值,但是在重新開啟工作表之前,仍然會顯示舊的值,如下所示。

為了解決這個問題,我們需要將 OutlinedButton 傳遞給 StatefulBuilder (或者您可以建立一個新的小部件並使用回撥公開更改,正如我前面所說的,順便說一下,這是更正確的方法)。

void addWeight() {
    showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.only(
        topLeft: Radius.circular(8),
        topRight: Radius.circular(8),
      )),
      builder: (context) {
        return Container(
          height: 360,
          width: MediaQuery.of(context).size.width,
          margin:
              EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
          padding: const EdgeInsets.all(16.0),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Padding(
                padding: EdgeInsets.only(bottom: 24.0),
                child: Text(
                  'Register Weight',
                  style: Styles.titleStyle,
                ),
              ),
              TextField(
                controller: weightInputController,
                keyboardType: TextInputType.number,
                decoration: InputDecoration(
                  labelText: 'Weight (KG)',
                  border: OutlineInputBorder(
                    borderRadius: Styles.borderRadius,
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: Row(
                  children: [
                    Expanded(
                      child: Text(
                        'Select a date',
                        style: TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                    Container(
                        padding: const EdgeInsets.symmetric(horizontal: 4),
                        margin: const EdgeInsets.symmetric(vertical: 8.0),
                        height: 36,
                        decoration: BoxDecoration(
                          borderRadius: Styles.borderRadius,
                        ),
                        child: StatefulBuilder(
                          builder: (context, setState) {
                            return OutlinedButton(
                              onPressed: () async {
                                final now = DateTime.now();

                                final result = await showDatePicker(
                                    context: context,
                                    initialDate: now,
                                    firstDate: now.subtract(
                                      const Duration(
                                        days: 90,
                                      ),
                                    ),
                                    lastDate: now);

                                if (result != null) {
                                  setState(() {
                                    selectedDate = result;
                                  });
                                }
                              },
                              child: Row(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceBetween,
                                children: [
                                  Padding(
                                    padding: const EdgeInsets.only(right: 16.0),
                                    child: Text(
                                        '${formatDateToString(selectedDate)}'),
                                  ),
                                  Icon(Icons.calendar_today_outlined),
                                ],
                              ),
                            );
                          },
                        )),
                  ],
                ),
              ),
              Expanded(child: Container()),
              ButtonBar(
                children: [
                  ElevatedButton(
                    onPressed: () => Navigator.pop(context),
                    child: Text('Cancel',
                        style: TextStyle(
                          color: Theme.of(context).primaryColor,
                        )),
                    style: ElevatedButton.styleFrom(
                      primary: Colors.white,
                      // minimumSize: Size(96, 48),
                    ),
                  ),
                  ElevatedButton(
                      onPressed: () {
                        setState(() {
                          weights.insert(
                              0,
                              WeightModel(
                                value: double.parse(weightInputController.text),
                                date: selectedDate,
                              ));
                        });
                        Navigator.pop(context);
                      },
                      child: const Text('Register')),
                ],
              ),
            ],
          ),
        );
      },
    );
  }
複製程式碼

這是我們的 ModalBottomSheet 的最終版本!

github.com/evandrmb/bo…


© 貓哥

ducafecat.tech/

github.com/ducafecat

往期

開源

GetX Quick Start

github.com/ducafecat/g…

新聞客戶端

github.com/ducafecat/f…

strapi 手冊譯文

getstrapi.cn

微信討論群 ducafecat

系列集合

譯文

ducafecat.tech/categories/…

開源專案

ducafecat.tech/categories/…

Dart 程式語言基礎

space.bilibili.com/404904528/c…

Flutter 零基礎入門

space.bilibili.com/404904528/c…

Flutter 實戰從零開始 新聞客戶端

space.bilibili.com/404904528/c…

Flutter 元件開發

space.bilibili.com/404904528/c…

Flutter Bloc

space.bilibili.com/404904528/c…

Flutter Getx4

space.bilibili.com/404904528/c…

Docker Yapi

space.bilibili.com/404904528/c…

相關文章