flutter系列之:Material主題的基礎-MaterialApp

簡介
為了簡化大家的使用,雖然flutter推薦所有的widget都有自己來進行搭建,但是在大框架上面,flutter提供了Material和Cupertino兩種主題風格的Widgets集合,大家可以在這兩種風格的繼承上進行個性化訂製和開發。

這兩種風格翻譯成中文就是:材料和庫比蒂諾?什麼鬼….我們還是使用默認的英文名來稱呼它們吧。

本文我們將會深入講解Material主題的基礎-MaterialApp。

MaterialApp初探
如果你使用最新的android Studio創建一個flutter項目的話,android Studio會自動為你創建一個基於flutter的應用程式。

我們來看下自動創建的main.dart文件:

Widget build(BuildContext context) {
return MaterialApp(
title: ‘Flutter Demo’,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: ‘Flutter Demo Home Page’),
)
}
這個build方法返回的widget就是這個flutter應用程式的根Widget。可以看到,默認情況下是返回一個MaterialApp。

在上面的樣例程式碼中,為MaterialApp設置了tile,theme和home屬性。

title是MaterialApp的標題,theme是整個MaterialApp的主題,home表示的是app進入時候應該展示的主頁面。

默認情況下MyHomePage會返回一個類似下面程式碼的Scaffold:

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: const <Widget>[
        Text(
          'home page',
        ),
      ],
    ),
  ),
);

這樣我們可以得到常見的MaterialApp介面:

MaterialApp詳解
有了上面的框架,我們就可以在home中構建自己的組件,從而開始flutter的愉快app之旅。

那麼MaterialApp還有其他的什麼功能嗎?它的底層原理是怎麼樣的呢?一起來看看吧。

首先是MaterialApp的定義:

class MaterialApp extends StatefulWidget
可以看到MaterialApp是一個StatefulWidget,表示MaterialApp可能會根據用戶的輸入不同,重新build子組件,因為通常來說MaterialApp表示一個應用程式總體,所以它需要考慮很多複雜的交互情況,使用StatefulWidget是很合理的。

MaterialApp中的theme
接下來我們看下MaterialApp可以配置的主題。

MaterialApp中有下面幾種theme:

final ThemeData? theme;

final ThemeData? darkTheme;

final ThemeData? highContrastTheme;

final ThemeData? highContrastDarkTheme;

final ThemeMode? themeMode;
ThemeData用來定義widget的主題樣式,ThemeData包括colorScheme和textTheme兩部分。

為了簡單起見,flutter提供了兩個簡潔的Theme創建方式,分別是ThemeData.light和ThemeData.dark。 當然你也可以使用ThemeData.from從ColorScheme中創建新的主題。

那麼問題來了,一個app為什麼有這麼多ThemeData呢?

默認情況下theme就是app將會使用的theme,但是考慮到現在流行的theme切換的情況,所以也提供了darkTheme這個選項。

如果theme和darkTheme都設置的話,那麼將會根據themeMode來決定具體到底使用哪個主題。

注意,默認的主題是ThemeData.light()

highContrastTheme和highContrastDarkTheme的存在也是因為在某些系統中需要high contrast和dark的主題版本,這些ThemeData是可選的。

themeMode這個欄位,如果取ThemeMode.system,那麼默認會使用系統的主題配置,具體而言,是通過調用MediaQuery.platformBrightnessOf來查詢系統到底是Brightness.light還是Brightness.dark.

雖然默認是ThemeMode.system,但是你也可以指定其為ThemeMode.light或者ThemeMode.dark.

MaterialApp中的routes
和web頁面的首頁一樣,在MaterialApp中,我們也需要定義一些頁面跳轉的路由資訊。

在講解routes之前,我們需要明白flutter中有兩個和路由相關的定義,分別是routes和Navigator。

其中routes是路由的定義,它表示的是不同路徑對應的widget地址,比如下面的routers的定義:

routes: <String, WidgetBuilder> {
‘/a’: (BuildContext context) => MyPage(title: ‘page A’),
‘/b’: (BuildContext context) => MyPage(title: ‘page B’),
‘/c’: (BuildContext context) => MyPage(title: ‘page C’),
},
routers的類型是Map<String, WidgetBuilder>,對應的key就是路由地址,value就是路由地址對應的WidgetBuilder。

Navigator是一個Widget,用來對routers進行管理。

Navigator可以通過是用Navigator.pages、Navigator.push或者Navigator.pop來對routers進行管理。舉個例子:

push:

Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(‘My Page’)),
body: Center(
child: TextButton(
child: Text(‘POP’),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));
pop:

Navigator.pop(context);
對於MaterialApp來說,如果是
/
route,那麼將會查找MaterialApp中的home屬性對應的Widget,如果home對應的Widget不存在,那麼會使用routers裡面配置的。

如果上面的資訊都沒有,則說明需要創建router,則會調用onGenerateRoute方法來創建新的routers。

所以說onGenerateRoute是用來處理home和routers方法中沒有定義的路由。你也可以將其看做是一種創建動態路由的方法。

最後,如果所有的route規則都不匹配的話,則會調用onUnknownRoute。

如果home,routes,onGenerateRoute,onUnknownRoute全都為空,並且builder不為空的話,那麼將不會創建任何Navigator。

MaterialApp中的locale
local是什麼呢?local在國際化中表示的是一種語言,通過使用Local,你不用再程式中硬編碼要展示的文本,從而做到APP的國際化支援。

dart中的local可以這樣使用:

const Locale swissFrench = Locale(‘fr’, ‘CH’);
const Locale canadianFrench = Locale(‘fr’, ‘CA’);
在MaterialApp中,需要同時配置localizationsDelegates和supportedLocales:

MaterialApp(
localizationsDelegates: [
// … app-specific localization delegate[s] here
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale(‘en’, ‘US’), // English
const Locale(‘he’, ‘IL’), // Hebrew
// … other locales the app supports
],
// …
)
supportedLocales中配置的是支援的locales,localizationsDelegates用來生成WidgetsLocalizations和MaterialLocalizations.

有關locale的具體使用,可以關注後續的文章。

MaterialApp和WidgetsApp
MaterialApp是一個StatefulWidget,那麼和它綁定的State叫做:_MaterialAppState, _MaterialAppStatez中有個build方法,返回的widget到底是什麼呢?

return ScrollConfiguration(
  behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
  child: HeroControllerScope(
    controller: _heroController,
    child: result,
  ),
);

可以看到,最終返回的是一個ScrollConfiguration,它的本質是返回一個包裝在HeroControllerScope中的result。

什麼是Hero呢?Hero在flutter中是一個組件,用來表示在路由切換的過程中,可以從老的路由fly到新的路由中。這樣的一個飛行的動畫,也叫做Hero動畫。

而這個result其實是一個WidgetsApp。

WidgetsApp就是MaterialApp底層的Widget,它包裝了應用程式通常需要的許多小部件。

WidgetsApp的一個主要功能就是將系統後退按鈕綁定到彈出導航器或退出應用程式。

從實現上講,MaterialApp 和 CupertinoApp 都使用它來實現應用程式的基本功能。

總結
MaterialApp作為Material風格的第一入口,希望大家能夠熟練掌握它的用法。

本文的例子://github.com/ddean2009/learn-flutter.git

更多內容請參考 //www.flydean.com/06-flutter-material-materialapp/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!