從一開始接觸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,也是沒有問題的。
好了,本次分享到此結束,希望上述內容能夠幫到你。