Flutter中App的主題和導航

- Flutter和Dart系列文章 和 程式碼GitHub地址
-
Flutter
一切皆Widget
的核心思想, 為我們提供了兩種主題風格 -
CupertinoApp
: 一個封裝了很多iOS
風格的小部件,一般作為頂層widget
使用 -
MaterialApp
: 一個封裝了很多安卓風格的小部件,一般作為頂層widget
使用, 下面我們先看下這個Widget
MaterialApp
這裡我們先看看 MaterialApp
的建構函式和相關函式
const MaterialApp({ Key key, // 導航主鍵, GlobalKey<NavigatorState> this.navigatorKey, // 主頁, Widget this.home, // 路由 this.routes = const <String, WidgetBuilder>{}, // 初始化路由, String this.initialRoute, // 構造路由, RouteFactory this.onGenerateRoute, // 為止路由, RouteFactory this.onUnknownRoute, // 導航觀察器 this.navigatorObservers = const <NavigatorObserver>[], // widget的構建 this.builder, // APP的名字 this.title = '', // GenerateAppTitle, 每次在WidgetsApp構建時都會重新生成 this.onGenerateTitle, // 背景顏色 this.color, // 主題, ThemeData this.theme, // app語言支援, Locale this.locale, // 多語言代理, Iterable<LocalizationsDelegate<dynamic>> this.localizationsDelegates, // flutter.widgets.widgetsApp.localeListResolutionCallback this.localeListResolutionCallback, // flutter.widgets.widgetsApp.localeResolutionCallback this.localeResolutionCallback, // 支援的多語言, Iterable<Locale> this.supportedLocales = const <Locale>[Locale('en', 'US')], // 是否顯示網格 this.debugShowMaterialGrid = false, // 是否開啟效能監控,覆蓋在螢幕最上面 this.showPerformanceOverlay = false, // 是否開啟柵格快取影象的檢查板 this.checkerboardRasterCacheImages = false, // 是否開啟顯示到螢幕外點陣圖的圖層的檢查面板 this.checkerboardOffscreenLayers = false, // 是否開啟覆蓋圖,顯示框架報告的可訪問性資訊 顯示邊框 this.showSemanticsDebugger = false, // 是否顯示右上角的Debug標籤 this.debugShowCheckedModeBanner = true, }) 複製程式碼
需要注意的幾點
- 如果
home
首頁指定了,routes
裡面就不能有'/'
的根路由了,會報錯,/
指定的根路由就多餘了 - 如果沒有
home
指定具體的頁面,那routes
裡面就有/
來指定根路由 - 路由的順序按照下面的規則來:
- 1、如果有
home
,就會從home
進入 - 2、如果沒有
home
,有routes
,並且routes
指定了入口'/'
,就會從routes
的/
進入 - 3、如果上面兩個都沒有,或者路由達不到,如果有
onGenerateRoute
,就會進入生成的路由 - 4、如果連上面的生成路由也沒有,就會走到
onUnknownRoute
,不明所以的路由,比如網路連線失敗,可以進入斷網的頁面
- 1、如果有
routes
- 宣告程式中有哪個通過
Navigation.of(context).pushNamed
跳轉的路由 - 引數以鍵值對的形式傳遞
-
key
:路由名字 -
value
:對應的Widget
-
routes: { '/home': (BuildContext content) => Home(), '/mine': (BuildContext content) => Mine(), }, 複製程式碼
initialRoute
- 初始化路由, 當用戶進入程式時,自動開啟對應的路由(home還是位於一級)
- 傳入的是上面
routes
的key
, 跳轉的是對應的Widget
(如果該Widget
有Scaffold.AppBar
,並不做任何修改,左上角有返回鍵)
routes: { '/home': (BuildContext content) => Home(), '/mine': (BuildContext content) => Mine(), }, initialRoute: '/mine', 複製程式碼
onGenerateRoute
當通過 Navigation.of(context).pushNamed
跳轉路由時, 在 routes
查詢不到時,會呼叫該方法
onGenerateRoute: (RouteSettings setting) { return MaterialPageRoute( settings: setting, builder: (BuildContext content) => Text('生成一個路由') ); }, 複製程式碼
onUnknownRoute
未知路由, 效果跟 onGenerateRoute
一樣, 在未設定 onGenerateRoute
的情況下, 才會去呼叫 onUnknownRoute
onUnknownRoute: (RouteSettings setting) { return MaterialPageRoute( settings: setting, builder: (BuildContext content) => Text('這是一個未知路由') ); }, 複製程式碼
navigatorObservers
- 路由觀察器,當呼叫
Navigator
的相關方法時,會回撥相關的操作 - 比如
push
,pop
,remove
,replace
是可以拿到當前路由和後面路由的資訊 - 獲取路由的名字:
route.settings.name
// navigatorObservers: [HomeObserver()], // 繼承NavigatorObserver class HomeObserver extends NavigatorObserver { @override void didPush(Route route, Route previousRoute) { super.didPush(route, previousRoute); // 獲取路由的名字 print('name = ${route.settings.name}'); // 獲取返回的內容 print('reaule = ${route.currentResult}'); } } 複製程式碼
builder
如果設定了這個引數, 那麼將會優先渲染這個 builder
, 而不會在走路由
builder: (BuildContext content, Widget widget) => Text('builder'), 複製程式碼
title
- 裝置用於識別使用者的應用程式的單行描述
- 在
Android
上,標題顯示在工作管理員的應用程式快照上方,當用戶按下“最近的應用程式”按鈕時會顯示這些快照 - 在
iOS
上,無法使用此值。來自應用程式的Info.plist
的CFBundleDisplayName
在任何時候都會被引用,否則就會引用CFBundleName
- 要提供初始化的標題,可以用
onGenerateTitle
CupertinoApp
用於建立 iOS
風格應用的頂層元件, 相關屬性和 MaterialApp
相比只是少了 theme
和 debugShowMaterialGrid
, 其他屬性都一樣, 如下所示
const CupertinoApp({ Key key, this.navigatorKey, this.home, this.routes = const <String, WidgetBuilder>{}, this.initialRoute, this.onGenerateRoute, this.onUnknownRoute, this.navigatorObservers = const <NavigatorObserver>[], this.builder, this.title = '', this.onGenerateTitle, this.color, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const <Locale>[Locale('en', 'US')], this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, }) 複製程式碼
使用示例如下
return CupertinoApp( title: 'Cupertino App', color: Colors.red, home: CupertinoPageScaffold( backgroundColor: Colors.yellow, resizeToAvoidBottomInset: true, navigationBar: CupertinoNavigationBar( middle: Text('Cupertino App Bar'), backgroundColor: Colors.blue, ), child: Center( child: Container( child: Text('Hello World'), ), ), ), ); 複製程式碼
CupertinoPageScaffold
一個 iOS
風格的頁面的基本佈局結構。包含內容和導航欄
const CupertinoPageScaffold({ Key key, // 設定導航欄, 後面會詳解 this.navigationBar, // 設定內容頁面的背景色 this.backgroundColor = CupertinoColors.white, // 子widget是否應該自動調整自身大小以適應底部安全距離 this.resizeToAvoidBottomInset = true, @required this.child, }) 複製程式碼
navigationBar
const CupertinoNavigationBar({ Key key, //導航欄左側元件 this.leading, //是否顯示左邊元件, 好像無效 this.automaticallyImplyLeading = true, //是否顯示中間元件, 好像無效 this.automaticallyImplyMiddle = true, //導航欄左側元件的右邊的文字, 好像無效 this.previousPageTitle, // 導航欄中間元件 this.middle, // 導航欄右側元件 this.backgroundColor = _kDefaultNavBarBackgroundColor, // 設定左右元件的內邊距, EdgeInsetsDirectional this.padding, //左側預設元件和左側元件右邊文字的顏色 this.actionsForegroundColor = CupertinoColors.activeBlue, this.transitionBetweenRoutes = true, this.heroTag = _defaultHeroTag, }) 複製程式碼
使用示例
return CupertinoApp( title: 'Cupertino App', color: Colors.red, debugShowCheckedModeBanner: false, home: CupertinoPageScaffold( backgroundColor: Colors.yellow, resizeToAvoidBottomInset: true, navigationBar: CupertinoNavigationBar( leading: Icon(Icons.person), automaticallyImplyLeading: false, automaticallyImplyMiddle: false, previousPageTitle: '返回', middle: Text('Cupertino App Bar'), trailing: Icon(Icons.money_off), border: Border.all(), backgroundColor: Colors.white, padding: EdgeInsetsDirectional.fromSTEB(10, 10, 10, 10), actionsForegroundColor: Colors.red, transitionBetweenRoutes: false, heroTag: Text('data'), ), child: Center( child: Container( child: Text('Hello World'), ), ), ), ); 複製程式碼
Scaffold
-
Scaffold
通常被用作MaterialApp
的子Widget
(安卓風格),它會填充可用空間,佔據整個視窗或裝置螢幕 -
Scaffold
提供了大多數應用程式都應該具備的功能,例如頂部的appBar
,底部的bottomNavigationBar
,隱藏的側邊欄drawer
等
const Scaffold({ Key key, // 顯示在介面頂部的一個AppBar this.appBar, // 當前介面所顯示的主要內容Widget this.body, // 懸浮按鈕, 預設在右下角位置顯示 this.floatingActionButton, // 設定懸浮按鈕的位置 this.floatingActionButtonLocation, // 懸浮按鈕出現消失的動畫 this.floatingActionButtonAnimator, // 在底部呈現一組button,顯示於[bottomNavigationBar]之上,[body]之下 this.persistentFooterButtons, // 一個垂直面板,顯示於左側,初始處於隱藏狀態 this.drawer, // 一個垂直面板,顯示於右側,初始處於隱藏狀態 this.endDrawer, // 出現於底部的一系列水平按鈕 this.bottomNavigationBar, // 底部的持久化提示框 this.bottomSheet, // 背景色 this.backgroundColor, // 重新計算佈局空間大小 this.resizeToAvoidBottomPadding = true, // 是否顯示到底部, 預設為true將顯示到頂部狀態列 this.primary = true, }) 複製程式碼
appBar
設定導航欄, 接受一個抽象類 PreferredSizeWidget
, 這裡使用其子類 AppBar
進行設定, 後面會詳解
floatingActionButton
- 設定一個懸浮按鈕, 預設在右下角位置顯示, 這裡使用
FloatingActionButton
設定 -
FloatingActionButton
是Material
設計規範中的一種特殊Button
,通常懸浮在頁面的某一個位置作為某種常用動作的快捷入口, 後面會詳解
floatingActionButtonLocation
設定懸浮按鈕的位置, 接受一個抽象類 FloatingActionButtonLocation
// 右下角, 距離底部有一點距離, 預設值 static const FloatingActionButtonLocation endFloat = _EndFloatFabLocation(); // 中下方, 距離底部有一點距離 static const FloatingActionButtonLocation centerFloat = _CenterFloatFabLocation(); // 右下角, 距離底部沒有間距 static const FloatingActionButtonLocation endDocked = _EndDockedFloatingActionButtonLocation(); // 中下方, 距離底部沒有間距 static const FloatingActionButtonLocation centerDocked = _CenterDockedFloatingActionButtonLocation(); 複製程式碼
FloatingActionButton
在 Material Design
中,一般用來處理介面中最常用,最基礎的使用者動作。它一般出現在螢幕內容的前面,通常是一個圓形,中間有一個圖示, 有以下幾種建構函式
const FloatingActionButton({ Key key, this.child, // 文字解釋, 按鈕唄長按時顯示 this.tooltip, // 前景色 this.foregroundColor, // 背景色 this.backgroundColor, // hero效果使用的tag,系統預設會給所有FAB使用同一個tag,方便做動畫效果 this.heroTag = const _DefaultHeroTag(), // 未點選時陰影值,預設6.0 this.elevation = 6.0, // 點選時陰影值,預設12.0 this.highlightElevation = 12.0, // 點選事件監聽 @required this.onPressed, // 是否為“mini”型別,預設為false this.mini = false, // 設定陰影, 設定shape時,預設的elevation將會失效,預設為CircleBorder this.shape = const CircleBorder(), // 剪下樣式 this.clipBehavior = Clip.none, // 設定點選區域大小的樣式, MaterialTapTargetSize的列舉值 this.materialTapTargetSize, // 是否為”extended”型別 this.isExtended = false, }) 複製程式碼
mini
- 是否為
mini
型別,預設為false
-
FloatingActionButton
分為三種類型:regular
,mini
,extended
-
regular
和mini
兩種型別通過預設的構造方法實現, 只有圖片 - 大小限制如下
const BoxConstraints _kSizeConstraints = const BoxConstraints.tightFor( width: 56.0, height: 56.0, ); const BoxConstraints _kMiniSizeConstraints = const BoxConstraints.tightFor( width: 40.0, height: 40.0, ); const BoxConstraints _kExtendedSizeConstraints = const BoxConstraints( minHeight: 48.0, maxHeight: 48.0, ); 複製程式碼
isExtended
- 是否為
extended
型別, 設定為true
即可 - 除此之外, 還可以使用
extended
建構函式建立該型別
FloatingActionButton.extended({ Key key, this.tooltip, this.foregroundColor, this.backgroundColor, this.heroTag = const _DefaultHeroTag(), this.elevation = 6.0, this.highlightElevation = 12.0, @required this.onPressed, this.shape = const StadiumBorder(), this.isExtended = true, this.materialTapTargetSize, this.clipBehavior = Clip.none, // 設定圖片 @required Widget icon, // 設定文字 @required Widget label, }) 複製程式碼
從引數上看差異並不大,只是把預設構造方法中的 child
換成了 icon
和 label
,不過通過下面的程式碼可以看到,傳入的 label
和 icon
也是用來構建 child
的,不過使用的是 Row
來做一層包裝而已
AppBar
AppBar
是一個 Material
風格的導航欄,它可以設定標題、導航欄選單、底部 Tab
等
AppBar({ Key key, // 導航欄左側weidget this.leading, // 如果leading為null,是否自動實現預設的leading按鈕 this.automaticallyImplyLeading = true, // 導航欄標題 this.title, // 導航欄右側按鈕, 接受一個數組 this.actions, // 一個顯示在AppBar下方的控制元件,高度和AppBar高度一樣,可以實現一些特殊的效果,該屬性通常在SliverAppBar中使用 this.flexibleSpace, // 一個AppBarBottomWidget物件, 設定TabBar this.bottom, //中控制元件的z座標順序,預設值為4,對於可滾動的SliverAppBar,當 SliverAppBar和內容同級的時候,該值為0,當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改elevation的值 this.elevation = 4.0, // 背景顏色,預設值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用 this.backgroundColor, // 狀態列的顏色, 黑白兩種, 取值: Brightness.dark this.brightness, // 設定導航欄上圖示的顏色、透明度、和尺寸資訊 this.iconTheme, // 設定導航欄上文字樣式 this.textTheme, // 導航欄的內容是否顯示在頂部, 狀態列的下面 this.primary = true, // 標題是否居中顯示,預設值根據不同的作業系統,顯示方式不一樣 this.centerTitle, // 標題間距,如果希望title佔用所有可用空間,請將此值設定為0.0 this.titleSpacing = NavigationToolbar.kMiddleSpacing, // 應用欄的工具欄部分透明度 this.toolbarOpacity = 1.0, // 底部導航欄的透明度設定 this.bottomOpacity = 1.0, }) 複製程式碼
leading
導航欄左側 weidget
final Widget leading; // 示例 leading: Icon(Icons.home), 複製程式碼
actions
導航欄右側按鈕, 接受一個數組
final List<Widget> actions; // 示例 actions: <Widget>[ Icon(Icons.add), Icon(Icons.home), ], 複製程式碼
brightness
狀態列的顏色, 黑白兩種
// 狀態列白色 brightness: Brightness.dark, // 狀態列黑色 brightness: Brightness.light, 複製程式碼
iconTheme
設定導航欄上圖示的顏色、透明度、和尺寸資訊
const IconThemeData({this.color, double opacity, this.size}) // 示例 iconTheme: IconThemeData(color: Colors.white, opacity: 0.56, size: 30), 複製程式碼
TabBar
- 在
AppBar
中通過bottom
屬性來新增一個導航欄底部tab
按鈕組, 接受一個PreferredSizeWidget
型別 -
PreferredSizeWidget
是一個抽象類, 這裡我們使用TabBar
class TabBar extends StatefulWidget implements PreferredSizeWidget { const TabBar({ Key key, // 陣列,顯示的標籤內容,一般使用Tab物件,當然也可以是其他的Widget @required this.tabs, // TabController物件 this.controller, // 是否可滾動 this.isScrollable = false, // 指示器顏色 this.indicatorColor, // 指示器高度 this.indicatorWeight = 2.0, // 指示器內邊距 this.indicatorPadding = EdgeInsets.zero, // 設定選中的樣式decoration,例如邊框等 this.indicator, // 指示器大小, 列舉值TabBarIndicatorSize this.indicatorSize, // 選中文字顏色 this.labelColor, // 選中文字樣式 this.labelStyle, // 文字內邊距 this.labelPadding, // 未選中文字顏色 this.unselectedLabelColor, // 未選中文字樣式 this.unselectedLabelStyle, }) } // Tab的建構函式 const Tab({ Key key, // 文字 this.text, // 圖示 this.icon, // 子widget this.child, }) 複製程式碼
效果如下

相關程式碼如下
void main(List<String> args) => runApp(NewApp()); class NewApp extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return App(); } } class App extends State<NewApp> with SingleTickerProviderStateMixin { List tabs = ['語文', '數學', '英語', '政治', '歷史', '地理', '物理', '化學', '生物']; TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(initialIndex: 0, length: tabs.length, vsync: this); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('CoderTitan'), backgroundColor: Colors.blueAccent, brightness: Brightness.dark, centerTitle: true, bottom: TabBar( controller: _tabController, tabs: tabs.map((e) => Tab(text: e)).toList(), isScrollable: true, indicatorColor: Colors.red, indicatorWeight: 2, indicatorSize: TabBarIndicatorSize.label, labelColor: Colors.orange, unselectedLabelColor: Colors.white, labelStyle: TextStyle(fontSize: 18, color: Colors.orange), unselectedLabelStyle: TextStyle(fontSize: 15, color: Colors.white), ), ), body: TabBarView( controller: _tabController, children: tabs.map((e) { return Container( alignment: Alignment.center, child: Text(e, style:TextStyle(fontSize: 50)), ); }).toList(), ), ), debugShowCheckedModeBanner: false, ); } } 複製程式碼
BottomNavigationBar
- 在
Scaffold
中有一個屬性bottomNavigationBar
用於設定最底部的tabbar
導航欄 - 使用
Material
元件庫提供的BottomNavigationBar
和BottomNavigationBarItem
兩個Widget
來實現Material風格的底部導航欄
BottomNavigationBar({ Key key, // 子widget陣列 @required this.items, // 每一個item的點選事件 this.onTap, // 當前選中的索引 this.currentIndex = 0, // 型別 BottomNavigationBarType type, // 文字顏色 this.fixedColor, // 圖片大小 this.iconSize = 24.0, }) 複製程式碼
items
包含所有子 Widget
的陣列
final List<BottomNavigationBarItem> items; const BottomNavigationBarItem({ // 未選中圖片 @required this.icon, // 標題 this.title, // 選中的圖片 Widget activeIcon, // 背景色 this.backgroundColor, }) 複製程式碼