Flutter中App的主題和導航

image

  • Flutter和Dart系列文章代碼GitHub地址
  • Flutter一切皆Widget的核心思想, 爲咱們提供了兩種主題風格
  • CupertinoApp: 一個封裝了不少iOS風格的小部件,通常做爲頂層widget使用
  • MaterialApp: 一個封裝了不少安卓風格的小部件,通常做爲頂層widget使用, 下面咱們先看下這個Widget

MaterialApp

這裏咱們先看看MaterialApp的構造函數和相關函數html

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,
})
複製代碼

須要注意的幾點git

  • 若是home首頁指定了,routes裏面就不能有'/'的根路由了,會報錯,/指定的根路由就多餘了
  • 若是沒有home指定具體的頁面,那routes裏面就有/來指定根路由
  • 路由的順序按照下面的規則來:
    • 一、若是有home,就會從home進入
    • 二、若是沒有home,有routes,而且routes指定了入口'/',就會從routes/進入
    • 三、若是上面兩個都沒有,或者路由達不到,若是有onGenerateRoute,就會進入生成的路由
    • 四、若是連上面的生成路由也沒有,就會走到onUnknownRoute,不明因此的路由,好比網絡鏈接失敗,能夠進入斷網的頁面

routes

  • 聲明程序中有哪一個經過Navigation.of(context).pushNamed跳轉的路由
  • 參數以鍵值對的形式傳遞
    • key:路由名字
    • value:對應的Widget
routes: {
  '/home': (BuildContext content) => Home(),
  '/mine': (BuildContext content) => Mine(),
},
複製代碼

initialRoute

  • 初始化路由, 當用戶進入程序時,自動打開對應的路由(home仍是位於一級)
  • 傳入的是上面routeskey, 跳轉的是對應的Widget(若是該WidgetScaffold.AppBar,並不作任何修改,左上角有返回鍵)
routes: {
  '/home': (BuildContext content) => Home(),
  '/mine': (BuildContext content) => Mine(),
},
initialRoute: '/mine',
複製代碼

onGenerateRoute

當經過Navigation.of(context).pushNamed跳轉路由時, 在routes查找不到時,會調用該方法github

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的相關方法時,會回調相關的操做
  • 好比pushpopremovereplace是能夠拿到當前路由和後面路由的信息
  • 獲取路由的名字: 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.plistCFBundleDisplayName在任什麼時候候都會被引用,不然就會引用CFBundleName
  • 要提供初始化的標題,能夠用onGenerateTitle

CupertinoApp

用於建立iOS風格應用的頂層組件, 相關屬性和MaterialApp相比只是少了themedebugShowMaterialGrid, 其餘屬性都同樣, 以下所示安全

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,
})
複製代碼

使用示例以下bash

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,
})
複製代碼

使用示例markdown

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設置
  • FloatingActionButtonMaterial設計規範中的一種特殊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
  • regularmini兩種類型經過默認的構造方法實現, 只有圖片
  • 大小限制以下
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換成了iconlabel,不過經過下面的代碼能夠看到,傳入的labelicon也是用來構建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,
})
複製代碼

效果以下

image

相關代碼以下

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組件庫提供的BottomNavigationBarBottomNavigationBarItem兩個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,
})
複製代碼

參考文獻


歡迎您掃一掃下面的微信公衆號,訂閱個人博客!

微信公衆號
相關文章
相關標籤/搜索