转载: http://blog.chengyunfeng.com/?p=1041
Flutter 控件之 Routes 和 Navigator
一个 App 通常会有多个界面,每个界面实现不同的功能,并在多个界面之间跳转。在 Flutter 中多个界面的跳转是通过 Navigator 来实现的。
在 Flutter 中定义了一个 Overlay Widget 用来管理多个界面,Overlay 里面使用 Stack 来显示当前的界面。通常不直接和 Overlay 打交道,而是使用 WidgetsApp 或者 MaterialApp 中的 Navigator 来管理界面。
比如在示例项目中https://github.com/goodev/learn_flutter 9.refactor 使用了 MaterialApp 的 routes 来定义多个界面以及每个界面的 key。然后使用 Navigator.pushNamed(context, routeName);
来跳转到具体的界面。
所以在 MaterialApp 中多页面跳转主要有两个任务。
定义 routes
routes 是 MaterialApp 中的一个属性,定义了全局的界面和每个界面的 key。在 Navigator 中使用 key 来指定跳转到具体的界面。 routes 的类型为 Map<String, WidgetBuilder>
,WidgetBuilder 是一个方法定义,该方法返回一个 Widget。
使用 Navigator
通常使用 Navigator.push 和 Navigator.pop 来显示一个界面和删除一个当前显示的界面。 可以把 Navigator 当做一个堆栈,里面每个 item 都是一个界面,如果要显示一个界面,则使用 Navigator.push 把界面压到堆栈中,最上面的界面就是用户可见的界面;如果要移除最上面的界面,只需要调用 Navigator.pop 从堆栈中移除。
Navigator.push 的参数为 Route,一般在 MaterialApp 中使用 MaterialPageRoute.在 MaterialPageRoute 中定义了 Route 所代表的界面的 Widget 信息。
如果使用 routes 定义了全局的路由信息,则可以使用 Navigator.pushNamed 函数来显示一个具体的界面。
定义各种路由
在 Flutter 中,像 对话框、菜单、Dropdown 下拉选项、BottomSheet 等都是通过显示一个 Route 实现的。在 Flutter 中有三种路由:PopupRoute, ModalRoute, 和 PageRoute。 使用这些 路由可以实现各种弹出界面的情况。
Flutter控件之BottomNavigationBar FloatingActionButton
FloatingActionButton
FloatingActionButton(FAB)控件是一个纸墨设计中定义的FAB按钮。用来显示界面上的主要功能。
FloatingActionButton具有如下主要属性:
- child:FAB中的子Widget
- tooltip:长按FAB所显示的提示文字
- backgroundColor:FAB的背景颜色
- onPressed:点击FAB的回调函数
- heroTag:应用到Hero Widget上的标签,用来做界面切换动画的
- mini:只能FAB样式是默认样式还是迷你样式
- 海拔和突出高度
FAB一般使用在Scaffold的floatingActionButton属性中。在示例应用中默认已经添加了一个FAB控件了。
BottomNavigationBar和BottomNavigationBarItem
BottomNavigationBar是[纸墨设计中的底部导航栏](https://material.io/guidelines/components/bottom-navigation.html)的实现,里面的每个项目都是一个BottomNavigationBarItem控件。
BottomNavigationBar有如下属性:
- items:BottomNavigationBarItem列表,包含了导航栏中的按钮
- onTap:点击里面的按钮的回调函数,参数为当前点击的按钮索引
- currentIndex:当前所高亮的按钮索引
- type:BottomNavigationBarType中定义的类型,有固定和移位两种类型
- fixedColor:如果类型类型为fixed,则通过fixedColor设置选中item的颜色
- iconSize:BottomNavigationBarItem中图标的大小
BottomNavigationBarItem有如下属性:
- icon:图标widget,一般为Icon
- title:标题widget,一般为Text
- backgroundColor:item的背景颜色
BottomNavigationBar一般使用在Scaffold的bottomNavigationBar属性。
Scaffold还要另外一个属性persistentFooterButtons和bottomNavigationBar是有区别的,虽然都是在屏幕下方显示一些按钮,但是是persistentFooterButtons是纸墨设计中定义的固定在界面底部的常用按钮,而不是导航按钮。
persistentFooterButtons中是一个小部件列表,通常使用FlatButton。
下面的示例代码演示和如何使用persistentFooterButtons和bottomNavigationBar :(
请注意,下面只是演示,在一般的项目中,这两个属性不会同时使用!!)
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 |
<br /> final List<FlatButton> footerButtons = <FlatButton>[ new FlatButton( child: new Text("OK"), onPressed: () => {}, ), new FlatButton( child: new Text("CANCEL"), onPressed: () => {}, ), ];
final BottomNavigationBar botNavBar = new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: new Icon(Icons.access_alarm), title: new Text('Alarm'), backgroundColor: Colors.deepPurple[500], ), new BottomNavigationBarItem( icon: new Icon(Icons.save), title: new Text('Save'), backgroundColor: Colors.deepOrange[500], ), new BottomNavigationBarItem( icon: new Icon(Icons.cloud), title: new Text('Cloud'), backgroundColor: Colors.teal[500], ), new BottomNavigationBarItem( icon: new Icon(Icons.favorite), title: new Text('Favorites'), backgroundColor: Colors.indigo[500], ) ], currentIndex: _currentIndex, type: BottomNavigationBarType.shifting, onTap: (int index) { setState(() { _currentIndex = index; }); }, );
// This method is rerun every time setState is called, for instance // as done by the _incrementCounter method above. // The Flutter framework has been optimized to make rerunning // build methods fast, so that you can just rebuild anything that // needs updating rather than having to individually change // instances of widgets. return new Scaffold( bottomNavigationBar: botNavBar, persistentFooterButtons: footerButtons,
|
实现的效果图如下:
Flutter 控件之 Drawer DrawerHeader 和 UserAccountsDrawerHeader
Drawer
在之前介绍 Scaffold 里面的 drawer 属性通常是一个 Drawer 对象。 Drawer 指的是纸墨设计中的导航侧边栏. Drawer 是从应用边缘拉出的一个导航面板。
如果没有设置 AppBar 的 leading 属性,则当使用 Drawer 的时候会自动显示一个 IconButton 来告诉用户有侧边栏(在 Android 上通常是显示为三个横的图标)。
Drawer 有两个属性,一个是设置 z 轴的 elevation;一个是设置 Drawer 内容的 child 属性。
Drawer 的内容通常是一个 ListView,里面包含一些设置项。而在 ListView 最上面通常会有个 DrawerHeader 来设置当前用户的基本信息,最常用的一个具体的 DrawerHeader 是 UserAccountsDrawerHeader 。
DrawerHeader
DrawerHeader 是 Drawer 最上方用来显示基本信息的控件。有如下属性:
- decoration:header 区域的 decoration,通常用来设置背景颜色或者背景图片
- duration 和 curve:如果 decoration 发生了变化,则会使用 curve 设置的变化曲线和 duration 设置的动画时间来做一个切换动画
- child: Header 里面所显示的内容控件
- padding: Header 里面内容控件的 padding 值,如果 child 为null,则这个值无效
如果想在 DrawerHeader 中显示用户账户信息,比如类似于 Gmail 的 联系人头像、用户名、Email 等信息,则可以使用 UserAccountsDrawerHeader 这个特殊的 DrawerHeader。
UserAccountsDrawerHeader
UserAccountsDrawerHeader 可以设置用户头像、用户名、Email 等信息,显示一个符合纸墨设计规范的 drawer header。
- currentAccountPicture:用来设置当前用户的头像
- accountName:当前用户的名字
- accountEmail:当前用户的 Email
- onDetailsPressed: 当 accountName 或者 accountEmail 被点击的时候所触发的回调函数,可以用来显示其他额外的信息
- otherAccountsPictures:用来设置当前用户的其他账号的头像
DrawerItem
Drawer 中每个菜单项。有下面几个属性:
- icon 配置菜单项的图标和图标尺寸、颜色
- child 具体的菜单项内容
- onPressed 当点击菜单项的时候所触发的回调函数
- selected 当前菜单项是否选中了
比如下面的代码设置了 drawer 中的 Header 和 DrawerItem:
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 |
return new Scaffold( drawer: new Drawer( child: new ListView( children: <Widget>[ new UserAccountsDrawerHeader( accountName: new Text('User Name'), accountEmail: new Text('[email protected]'), currentAccountPicture: new CircleAvatar(backgroundImage: new AssetImage(_kAsset0)), otherAccountsPictures: <Widget>[ new CircleAvatar(backgroundImage: new AssetImage(_kAsset1)), new CircleAvatar(backgroundImage: new AssetImage(_kAsset2)), ], onDetailsPressed: () {}, ), new ClipRect( child: new DrawerItem( icon: new CircleAvatar(child: new Text("A")), child: new Text('Drawer item A'), onPressed: () => {}, ), ), ], ), ), |
所实现的效果如下:
Flutter控件之文本和基本的容器
文本
文件控件是用来显示一段文本的,使用方式如下:
1 2 3 4 5 6 7 8 9 |
new Text( 'Flutter is a mobile app SDK .', style: new TextStyle(fontSize: 20.0, color: Colors.teal[500]), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 3, textAlign: TextAlign.left, ),
|
上面展示了文本控件的主要属性:要显示的文字内容,文字的样式,softWrap指定是否可以换行,maxLines指定最多显示多少行,当文字内容超过了maxLines指定的行数的时候,溢出用来指定超出文本的表示方式,是截断文本啊还是用三个点显示等,textAlign可以用来指定文本水平对齐方式。
其中当softWrap为假时候,说明不允许换行,这个时候相当于maxLines为1。
文字的样式TextStyle可以设置文字字体类型,大小,颜色,行高,字间距等和文本相关的选项。
富文本
文本只能显示一种样式的文字,如果你想在一段文字中显示多种样式 - 类似于Android里面的SpannableString - 的话,就需要使用RichText了。
如果你查看Text的源代码的话,会发现Text类的构建函数返回了一个RichText:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@override Widget build(BuildContext context) { final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); TextStyle effectiveTextStyle = style; if (style == null || style.inherit) effectiveTextStyle = defaultTextStyle.style.merge(style); return new RichText( textAlign: textAlign ?? defaultTextStyle.textAlign, softWrap: softWrap ?? defaultTextStyle.softWrap, overflow: overflow ?? defaultTextStyle.overflow, textScaleFactor: textScaleFactor ?? MediaQuery.of(context).textScaleFactor, maxLines: maxLines ?? defaultTextStyle.maxLines, text: new TextSpan( style: effectiveTextStyle, text: data ) ); }
|
所以可以看出,Text只是使用一种样式的RichText而已。
RichText中的文本使用TextSpan来定义,而每个TextSpan中包含了文本内容和文本样式以及一个子TextSpan列表。
比如在示例项目中用Text来显示按钮被点击的次数,则可以修改为用RichText来表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
new RichText( text: new TextSpan( text: 'Button tapped ', style: new TextStyle( inherit: true, fontSize: 32.0, color: Colors.black), children: <TextSpan>[ new TextSpan( text: '$_counter', style: new TextStyle( fontWeight: FontWeight.bold, color: Colors.amber[500])), new TextSpan(text: ' time'), new TextSpan( text: _counter == 1 ? '' : 's', style: new TextStyle( fontWeight: FontWeight.w200, color: Colors.cyan[500])), new TextSpan(text: '.'), ], ), ),
|
请注意上面的RichText的文字属性的取值为一个TextSpan,而这个TextSpan里面又包含了一个children集合。
图片
如果要显示图片,则需要使用图像控件。图像可以显示文件,资源包或者网络图片,当图标加载完后会在界面显示。
图片具有宽度,高度,颜色过滤器(类似于Android中的色调),适合模式,重复模式等属性。和Android中ImageView的属性差不多。
1 2 3 |
// 加载一个网络图片 new Image.network('https://flutter.io/images/flutter-mark-square-100.png')
|
你可能发现了上面的文字或者Image都没有设置Padding(留白)和Margin(边距)值的属性,也没有设置背景颜色的属性。如果你希望设置这些属性则需要使用下面的几个小部件。
填充
Padding是用来给一个控件设置padding值的.padding属性值是EdgeInsets对象,可以指定上下左右的边距值;而child属性是一个Widget对象,是需要留白的那个控件。
比如,下面就用Padding在Text下边设置了8px的边距:
1 2 3 4 5 6 7 8 9 10 11 12 |
new Padding( padding: new EdgeInsets.only(bottom: 8.0), child: new Text( 'Flutter 是一个用一套代码就可以构建高性能安卓和苹果应用的移动应用 SDK。 ', style: new TextStyle(fontSize: 28.0), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 2, textAlign: TextAlign.left, ), );
|
如果要给Widget设置Margin或者背景颜色,就需要使用Container了。
容器
和Padding相比,Container的属性要多一些:
- alignment设置子控件的布局位置
- padding设置子控件的padding值
- margin设置子控件的margin value
- transform设置子控件的矩阵变换,比如在显示的时候,可以旋转一个控件
- decoration设计
子控件背面所绘制的装饰
- constraintsDecoration设置子控件上面所绘制的装饰- constraints设置子控件尺寸的约束条件,比如长宽的最大值,最小值
- child设置子控件对象
例如,下面在Image上设置了边距,背景颜色,旋转以及最小宽度等:
1 2 3 4 5 6 7 8 9 10 11 |
new Container( padding: new EdgeInsets.only(bottom: 8.0), decoration: new BoxDecoration(backgroundColor: Colors.grey[200]), child: new Image.network( 'https://flutter.io/images/flutter-mark-square-100.png'), margin: new EdgeInsets.only(bottom: 8.0), transform: new Matrix4.rotationZ(100.0), constraints: new BoxConstraints(minHeight: 150.0), );
|
注意:(目前)并没有Margin这个控件。在Container中也可以设置padding。如果只是设置一个Widget的padding则可以使用Padding类,如果还需要同时设置背景,margin等属性则可以使用Container类。
中央
如果想把一个Widget居中显示,则可以用中控制。中心控件顾名思义就是把子控件居中显示,可以设置中央控件的宽高因子,如果不设置,则中心会占用尽可能的控件。
列和行
如果要把多个控件放一起显示为一列,则需要使用列类,
如果要把多个控件放一起显示为一行,则需要使用行类。
例如下面使用列把多个文本组合一起显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
new Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ new Text( 'Flutter is a mobile app SDK for building high-performance, high-fidelity, apps for iOS and Android, from a single codebase.', style: new TextStyle(fontSize: 20.0, color: Colors.teal[500]), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 3, textAlign: TextAlign.left, ), new Text( 'Flutter 是一个用一套代码就可以构建高性能安卓和苹果应用的移动应用 SDK。 ', style: new TextStyle(fontSize: 28.0), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 2, textAlign: TextAlign.left, ), ], );
|
Column和Row有一样的属性:
- mainAxisAlignment设置主轴方向上的对齐方式,比如现在列高度为500px,里面包含两个子控件高度分别为100px,那么还剩余300px的空余空间,如果指定为MainAxisAlignment.spaceAround值,则剩余的300px会分为三份,在两个子控件上下和中间各100px的空间。
- mainAxisSize设置主轴方向所占的高度或者宽度(Column就是指高度)
- crossAxisAlignment设置副轴方向上的对齐方式,比如对于列可以使用CrossAxisAlignment.center来设置里面的子控件水平居中对齐
- children是里面的一些子控件列表