一切皆元件的Flutter,安能辨我是雄雌

蕭文翰發表於2020-07-27

從一開始接觸Flutter,相信讀者都會銘記一句話,那就是——一切皆元件。今天我們就來體會一下這句話的神奇魔力,我們先從實際的產品需求說起。
我們先來看一個簡化的執行圖:

我們要實現如上圖所示的日期選擇器,App是iOS風格。
Flutter SDK自身有類似上圖的日期選擇器,但是Material Design的,於是我到Flutter庫中找到了一個名為flutter_date_pickers的三方庫,版本為0.1.4(https://pub.flutter-io.cn/packages/flutter_date_pickers)。
接下來就是整合這個庫了,具體程式碼按照文件直接複製:

@override
Widget build(BuildContext context) {
    DatePickerRangeStyles styles = DatePickerRangeStyles(
        selectedPeriodLastDecoration: BoxDecoration(
            color: Colors.red,
            borderRadius: BorderRadius.only(
                topRight: Radius.circular(10.0),
                bottomRight: Radius.circular(10.0))),
        selectedPeriodStartDecoration: BoxDecoration(
        color: Colors.green,
        borderRadius: BorderRadius.only(
            topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0)),
        ),
        selectedPeriodMiddleDecoration:
            BoxDecoration(color: Colors.yellow, shape: BoxShape.rectangle),
    );
    return CupertinoPageScaffold(
                child: WeekPicker(
                    selectedDate: DateTime.now(),
                    onChanged: (dateRange) {},
                    firstDate: DateTime.now().subtract(Duration(days: 10)),
                    lastDate: DateTime.now().add(Duration(minutes: 10)),
                    datePickerStyles: styles)
    );
}

本來以為可以正常執行的,結果整個App崩潰了。報錯堆疊資訊如下:

The following NoSuchMethodError was thrown building WeekPicker(dirty, dependencies: [_LocalizationsScope-[GlobalKey#678bc]]):
The getter 'firstDayOfWeekIndex' was called on null.
Receiver: null
Tried calling: firstDayOfWeekIndex

接著,根據堆疊資訊找到程式碼出錯位置,發現是這個庫中week_picker.dart檔案中出現問題,下面的程式碼是問題所在:

MaterialLocalizations localizations = MaterialLocalizations.of(context);

ISelectablePicker<DatePeriod> weekSelectablePicker = WeekSelectable(
    selectedDate,
    datePickerStyles.firstDayOfeWeekIndex ?? localizations.firstDayOfWeekIndex,
    firstDate,
    lastDate,
    selectableDayPredicate: selectableDayPredicate
);

很明顯,這裡使用了MaterialLocalizations物件localizations,而MaterialLocalizations.of(context);方法返回了null,所以在接下來的程式碼中丟擲了空指標異常。
解決的方法很簡單,只要修改原始碼,如果通過MaterialLocalizations來初始化localizations得到null,那麼就通過CupertinoLocalizations來初始化它就行了。具體程式碼如下:

CupertinoLocalizations localizations = CupertinoLocalizations.of(context);

在接下來的使用時,替換為:

localizations.datePickerDateOrder.index

即可。
但是,這畢竟需要改第三方庫的原始碼,有沒有辦法不改原始碼呢?答案是肯定的。
我們一開始就提到一切皆元件的概念,那麼,有沒有可能App依然使用iOS風格,然後把MaterialApp巢狀到CupertinoPageScaffold中呢?換一種說法,我們可不可以把MaterialApp和與之相關的Scaffold當做普通的元件,被CupertinoPageScaffold所包含呢?來看下面的程式碼:

@override
Widget build(BuildContext context) {
DatePickerRangeStyles styles = DatePickerRangeStyles(
    selectedPeriodLastDecoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.only(
            topRight: Radius.circular(10.0),
            bottomRight: Radius.circular(10.0))),
    selectedPeriodStartDecoration: BoxDecoration(
    color: Colors.green,
    borderRadius: BorderRadius.only(
        topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0)),
    ),
    selectedPeriodMiddleDecoration:
        BoxDecoration(color: Colors.yellow, shape: BoxShape.rectangle),
);
return CupertinoPageScaffold(
    child: MaterialApp(
        home: Scaffold(
        	appBar: CupertinoNavigationBar(middle: Text('Incredible Flutter')),
            body: WeekPicker(
                selectedDate: DateTime.now(),
                onChanged: (dateRange) {},
                firstDate: DateTime.now().subtract(Duration(days: 10)),
                lastDate: DateTime.now().add(Duration(minutes: 10)),
                datePickerStyles: styles))));
}

仔細閱讀上述程式碼,可見:我們只是在return語句中增加了MaterialApp和Scaffold元件,重新執行程式,結果可以正常執行了。
另外還要注意,Scaffold中的appBar,我們經常給定的是AppBar物件,但為了實現iOS的介面風格,我們將其值改為CupertinoNavigationBar,也是沒有問題的。
好了,本次分享到此結束,希望上述內容能夠幫到你。

相關文章