鑑於Flutter的高性能渲染、跨平臺、多端一致性等優點,閃點清單在移動端APP上,使用了完整的Flutter框架來開發。既然是完整APP,架構搭建徹底不受歷史Native APP的影響,沒有歷史包袱的沉澱,設計也能更靈活和健壯。bash
全局BuildContext
,幾乎是全部Flutter開發者的一個痛點。這個痛點有多痛呢?咱們來列舉一下場景:markdown
在Android中,咱們能夠用getApplicationContext
解決全局context問題,Flutter官方並無提供建議的方案,不過社區有一些推薦的解決方案,好比使用GlobalKey的方案:架構
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: globalNavigatorKey, // GlobalKey()
)
}
globalNavigatorKey.currentState.push(
MaterialPageRoute(builder: (context) => SomePage()),
);
複製代碼
首先咱們定義一個GlobalKey
,而後在初始化MaterialApp
的時候傳入navigatorKey
,而後咱們在須要使用路由跳轉的地方,不使用原始的方式,而使用navigatorKey來調用:框架
globalNavigatorKey.currentState.push(...)
複製代碼
看起來上述方案好像能夠解決問題,可是目前只能解決頁面路由跳轉問題,而若是使用Overlays(好比Dialog)、MediaQuery等就會出現問題了,error提示context不合法:less
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
複製代碼
而直接使用navigatorKey.currentState.context
獲取全局context也會出現一樣的error。ide
在嘗試衆多方案都失敗後,咱們仍然在繼續尋找更好的方案,最終找到了OneContext方案,倉庫地址: one_context。函數
OneContext是一個很是新的庫,2020年5月初才發第一個版本,目前還未發1.0版本。不過API的完成度仍是很高的。工具
使用OneContext,首先咱們須要在MaterialApp中配置OneContext:性能
MaterialApp(
builder: (BuildContext context, Widget child) {
return OneContext().builder(context, child, initialRoute: 'home');
},
/// builder: OneContext().builder, /// 若是不須要initialRoute,可使用這種方式
navigatorKey: OneContext().key,
)
複製代碼
而後,須要使用context的地方,所有經過OneContext來調用:ui
OneContext().pushNamed('calendar');
OneContext().showModalBottomSheet(
builder: (BuildContext context) {
return Container();
},
);
OneContext().showDialog(...);
OneContext().addOverlay(...);
複製代碼
OneContext().pushNamed('/second');
OneContext().push(MaterialPageRoute(builder: (_) => SecondPage()));
OneContext().pop();
複製代碼
/// 展現ModalBottomSheet
OneContext().showModalBottomSheet(
builder: (BuildContext context) {
return Container();
},
);
/// 添加移除覆蓋物
OneContext().addOverlay(
overlayId: myCustomAndAwesomeOverlayId,
builder: (_) => MyCustomAndAwesomeOverlay()
);
OneContext().removeOverlay(myCustomAndAwesomeOverlayId);
/// 加載提示
OneContext().showProgressIndicator();
OneContext().showProgressIndicator(
backgroundColor: Colors.blue.withOpacity(.3),
circularProgressIndicatorColor: Colors.white
);
OneContext().hideProgressIndicator();
複製代碼
print('Platform: ' + OneContext().theme.platform);
print('Orientation: ' + OneContext().mediaQuery.orientation);
複製代碼
OneContext().oneTheme.toggleMode();
OneContext().oneTheme.changeDarkThemeData(
ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.dark
)
);
複製代碼
從OneContext配置中,能夠看出來,OneContext最關鍵的一句配置是OneContext().builder
,咱們點進去看源碼:
Widget builder(BuildContext context, Widget widget,
{Key key,
MediaQueryData mediaQueryData,
String initialRoute,
Route<dynamic> Function(RouteSettings) onGenerateRoute,
Route<dynamic> Function(RouteSettings) onUnknownRoute,
List<NavigatorObserver> observers = const <NavigatorObserver>[]}) =>
ParentContextWidget(
child: widget,
mediaQueryData: mediaQueryData,
initialRoute: initialRoute,
onGenerateRoute: onGenerateRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
);
class ParentContextWidget extends StatelessWidget {
/// ...
@override
Widget build(BuildContext context) {
return MediaQuery(
data: mediaQueryData ?? MediaQuery.of(context),
child: Navigator(
initialRoute: initialRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
onGenerateRoute: onGenerateRoute ??
(settings) => MaterialPageRoute(
builder: (context) => OneContextWidget(
child: child,
)),
),
);
}
}
複製代碼
從源碼中咱們能夠看到:
OneContextWidget
,而後就能夠在OneContextWidget拿到內層context,這個context能夠用於絕大部分場景。Overlay
的經常使用方法,並綁定了內部的context對象,從而解決Overlay的context獲取問題。import 'package:flutter/material.dart';
import 'package:one_context/src/controllers/one_context.dart';
class OneContextWidget extends StatefulWidget {
final Widget child;
OneContextWidget({Key key, this.child}) : super(key: key);
_OneContextWidgetState createState() => _OneContextWidgetState();
}
class _OneContextWidgetState extends State<OneContextWidget> {
@override
void initState() {
super.initState();
OneContext().registerDialogCallback(
showDialog: _showDialog,
showSnackBar: _showSnackBar,
showModalBottomSheet: _showModalBottomSheet,
showBottomSheet: _showBottomSheet);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (innerContext) {
OneContext().context = innerContext;
return widget.child;
},
),
);
}
Future<T> _showDialog<T>(...){...}
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> _showSnackBar(...){ ... }
Future<T> _showModalBottomSheet<T>(...){ ... }
PersistentBottomSheetController<T> _showBottomSheet<T>(...) { ... }
}
複製代碼
OneContextWidget
在每次build時,會更新全局context:@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (innerContext) {
OneContext().context = innerContext;
return widget.child;
},
),
);
}
複製代碼
Navigator.pop
沒法正確關閉Dialog
)OneContext().popDialog()
代替Navigator.pop
,切記切記。到目前咱們解決了Flutter全局BuildContext的問題,但這其實並不該該是最終的方案,OneContext
是一個侵入性比較高的方案,Flutter官方應該提供更好的方案來解決這個問題。
講到這裏,還並無完成基礎框架的搭建,後面咱們會講解更多的Flutter架構設計內容,好比:通知、分享、UI設計等等。
持續分享閃點清單在Flutter上的開發經驗。閃點清單,一款懸浮清單軟件: