![image]()
Flutter
一切皆Widget
的核心思想, 为我们提供了两种主题风格
CupertinoApp
: 一个封装了很多iOS
风格的小部件,一般作为顶层widget
使用
MaterialApp
: 一个封装了很多安卓风格的小部件,一般作为顶层widget
使用, 下面我们先看下这个Widget
MaterialApp
这里我们先看看MaterialApp
的构造函数和相关函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| const MaterialApp({ 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.theme, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const <Locale>[Locale('en', 'US')], this.debugShowMaterialGrid = false, this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, })
|
- 如果
home
首页指定了,routes
里面就不能有'/'
的根路由了,会报错,/
指定的根路由就多余了
- 如果没有
home
指定具体的页面,那routes
里面就有/
来指定根路由
- 路由的顺序按照下面的规则来:
- 1、如果有
home
,就会从home
进入
- 2、如果没有
home
,有routes
,并且routes
指定了入口'/'
,就会从routes
的/
进入
- 3、如果上面两个都没有,或者路由达不到,如果有
onGenerateRoute
,就会进入生成的路由
- 4、如果连上面的生成路由也没有,就会走到
onUnknownRoute
,不明所以的路由,比如网络连接失败,可以进入断网的页面
routes
- 声明程序中有哪个通过
Navigation.of(context).pushNamed
跳转的路由
- 参数以键值对的形式传递
1 2 3 4
| routes: { '/home': (BuildContext content) => Home(), '/mine': (BuildContext content) => Mine(), },
|
initialRoute
- 初始化路由, 当用户进入程序时,自动打开对应的路由(home还是位于一级)
- 传入的是上面
routes
的key
, 跳转的是对应的Widget
(如果该Widget
有Scaffold.AppBar
,并不做任何修改,左上角有返回键)
1 2 3 4 5
| routes: { '/home': (BuildContext content) => Home(), '/mine': (BuildContext content) => Mine(), }, initialRoute: '/mine',
|
onGenerateRoute
当通过Navigation.of(context).pushNamed
跳转路由时,
在routes
查找不到时,会调用该方法
1 2 3 4 5 6
| onGenerateRoute: (RouteSettings setting) { return MaterialPageRoute( settings: setting, builder: (BuildContext content) => Text('生成一个路由') ); },
|
onUnknownRoute
未知路由, 效果跟onGenerateRoute
一样, 在未设置onGenerateRoute
的情况下, 才会去调用onUnknownRoute
1 2 3 4 5 6
| onUnknownRoute: (RouteSettings setting) { return MaterialPageRoute( settings: setting, builder: (BuildContext content) => Text('这是一个未知路由') ); },
|
navigatorObservers
- 路由观察器,当调用
Navigator
的相关方法时,会回调相关的操作
- 比如
push
,pop
,remove
,replace
是可以拿到当前路由和后面路由的信息
- 获取路由的名字:
route.settings.name
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
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
, 而不会在走路由
1
| builder: (BuildContext content, Widget widget) => Text('builder'),
|
title
- 设备用于识别用户的应用程序的单行描述
- 在
Android
上,标题显示在任务管理器的应用程序快照上方,当用户按下“最近的应用程序”按钮时会显示这些快照
- 在
iOS
上,无法使用此值。来自应用程序的Info.plist
的CFBundleDisplayName
在任何时候都会被引用,否则就会引用CFBundleName
- 要提供初始化的标题,可以用
onGenerateTitle
CupertinoApp
用于创建iOS
风格应用的顶层组件, 相关属性和MaterialApp
相比只是少了theme
和debugShowMaterialGrid
, 其他属性都一样, 如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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, })
|
使用示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
风格的页面的基本布局结构。包含内容和导航栏
1 2 3 4 5 6 7 8 9 10
| const CupertinoPageScaffold({ Key key, // 设置导航栏, 后面会详解 this.navigationBar, // 设置内容页面的背景色 this.backgroundColor = CupertinoColors.white, // 子widget是否应该自动调整自身大小以适应底部安全距离 this.resizeToAvoidBottomInset = true, @required this.child, })
|
navigationBar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const CupertinoNavigationBar({ Key key, this.leading, this.automaticallyImplyLeading = true, this.automaticallyImplyMiddle = true, this.previousPageTitle, this.middle, this.backgroundColor = _kDefaultNavBarBackgroundColor, this.padding, this.actionsForegroundColor = CupertinoColors.activeBlue, this.transitionBetweenRoutes = true, this.heroTag = _defaultHeroTag, })
|
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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
等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const Scaffold({ Key key, this.appBar, this.body, this.floatingActionButton, this.floatingActionButtonLocation, this.floatingActionButtonAnimator, this.persistentFooterButtons, this.drawer, this.endDrawer, this.bottomNavigationBar, this.bottomSheet, this.backgroundColor, this.resizeToAvoidBottomPadding = true, this.primary = true, })
|
appBar
设置导航栏, 接受一个抽象类PreferredSizeWidget
, 这里使用其子类AppBar
进行设置, 后面会详解
- 设置一个悬浮按钮, 默认在右下角位置显示, 这里使用
FloatingActionButton
设置
FloatingActionButton
是Material
设计规范中的一种特殊Button
,通常悬浮在页面的某一个位置作为某种常用动作的快捷入口, 后面会详解
设置悬浮按钮的位置, 接受一个抽象类FloatingActionButtonLocation
1 2 3 4 5 6 7 8
| static const FloatingActionButtonLocation endFloat = _EndFloatFabLocation();
static const FloatingActionButtonLocation centerFloat = _CenterFloatFabLocation();
static const FloatingActionButtonLocation endDocked = _EndDockedFloatingActionButtonLocation();
static const FloatingActionButtonLocation centerDocked = _CenterDockedFloatingActionButtonLocation();
|
在Material Design
中,一般用来处理界面中最常用,最基础的用户动作。它一般出现在屏幕内容的前面,通常是一个圆形,中间有一个图标, 有以下几种构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const FloatingActionButton({ Key key, this.child, this.tooltip, this.foregroundColor, this.backgroundColor, this.heroTag = const _DefaultHeroTag(), this.elevation = 6.0, this.highlightElevation = 12.0, @required this.onPressed, this.mini = false, this.shape = const CircleBorder(), this.clipBehavior = Clip.none, this.materialTapTargetSize, this.isExtended = false, })
|
mini
- 是否为
mini
类型,默认为false
FloatingActionButton
分为三种类型:regular
, mini
, extended
regular
和mini
两种类型通过默认的构造方法实现, 只有图片
- 大小限制如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
构造函数创建该类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| AppBar({ Key key, this.leading, this.automaticallyImplyLeading = true, this.title, this.actions, this.flexibleSpace, this.bottom, this.elevation = 4.0, this.backgroundColor, this.brightness, this.iconTheme, this.textTheme, this.primary = true, this.centerTitle, this.titleSpacing = NavigationToolbar.kMiddleSpacing, this.toolbarOpacity = 1.0, this.bottomOpacity = 1.0, })
|
leading
导航栏左侧weidget
1 2 3 4
| final Widget leading;
leading: Icon(Icons.home),
|
actions
导航栏右侧按钮, 接受一个数组
1 2 3 4 5 6 7
| final List<Widget> actions;
actions: <Widget>[ Icon(Icons.add), Icon(Icons.home), ],
|
brightness
状态栏的颜色, 黑白两种
1 2 3 4
| brightness: Brightness.dark,
brightness: Brightness.light,
|
iconTheme
设置导航栏上图标的颜色、透明度、和尺寸信息
1 2 3 4
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class TabBar extends StatefulWidget implements PreferredSizeWidget { const TabBar({ Key key, @required this.tabs, this.controller, this.isScrollable = false, this.indicatorColor, this.indicatorWeight = 2.0, this.indicatorPadding = EdgeInsets.zero, this.indicator, this.indicatorSize, this.labelColor, this.labelStyle, this.labelPadding, this.unselectedLabelColor, this.unselectedLabelStyle, }) }
const Tab({ Key key, this.text, this.icon, this.child, })
|
效果如下
![image]()
相关代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| void main(List<String> args) => runApp(NewApp());
class NewApp extends StatefulWidget {
@override State<StatefulWidget> 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风格的底部导航栏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| BottomNavigationBar({ Key key, @required this.items, this.onTap, this.currentIndex = 0, BottomNavigationBarType type, this.fixedColor, this.iconSize = 24.0, })
|
items
包含所有子Widget
的数组
1 2 3 4 5 6 7 8 9 10 11 12
| final List<BottomNavigationBarItem> items;
const BottomNavigationBarItem({ @required this.icon, this.title, Widget activeIcon, this.backgroundColor, })
|
参考文献