Text 支持两种类型的文本展现,一个是默认的展现单同样式文本 Text,另外一个是支持多种混合样式的富文本 Text.rich。算法
单同样式文本 Text 的初始化,是要传入须要展现的字符串。而这个字符串的具体展现效果,受构造函数中的其余参数控制。这些参数大体能够分为两类:缓存
示例代码 - 定义了一段剧中布局、20号红色粗体展现样式的字符串:网络
Text( '文本是视图系统中的常见空间,用来显示一段特定样式的字符串,就好比 Android 里的 TextView,或是 iOS 中的 UILabel。', textAlign: TextAlign.center, // 居中显示 style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red),// 20 号,红色粗体展现 );
混合展现样式与单同样式的关键区别在于分片,即如何把一段字符串分为几个片断来管理,给每一个片断单独设置样式。在 Flutter 中可使用 TextSpan。app
TextSpan 定义来一个字符串片断该如何控制其展现样式,而将这些有着独立展现样式的字符串组装在一块儿,则能够支持混合样式的富文本展现。异步
示例代码 - 分别定义黑色与红色两种展现样式ide
TextStyle blackStyle = TextStyle(fontWeight: FontWeight.normal, fontSize: 20, color: Colors.black); // 黑色样式 TextStyle redStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red); // 红色样式 Text.rich( TextSpan( children: <TextSpan>[ TextSpan(text: '文本是视图系统中的常见空间,用来显示一段特定样式的字符串,就好比', style: redStyle), // 第 1 个片断,红色样式 TextSpan(text: 'Android', style: blackStyle), // 第 1 个片断,黑色样式 TextSpan(text: '中的', style: redStyle), // 第 1 个片断,红色样式 TextSpan(text: 'TextView', style: blackStyle), // 第 1 个片断,黑色样式 ] ), textAlign: TextAlign.center, );
在 Flutter 中有多张方式,用来加载不一样形式、支持不一样格式的图片:函数
除了能够根据图片的显示方式设置不一样的图片源以外,图片的构造方法还提供了填充模式 fit、拉伸模式 centerSlice、重复模式 repeat 等属性,能够针对图片与目标区域的宽高比差别制定排版模式。布局
在加载网络图片的时候,为了提高用户的等待体验,每每会加入占位图、加载动画等元素,可是默认的 Image.network 构造方法并不支持这些高级功能,这时候 FadeInImage 控件就派上用场了。性能
FadeInImage 控件提供了图片占位的功能,而且支持在图片加载完成时淡入淡出的视觉效果。此外,因为 Image 支持 gif 格式,甚至还能够将一些炫酷的加载动画做为占位图。字体
示例代码 - loading 的 gif 做为占位图展现:
FadeInImage.assetNetwork( placeholder: 'asssets/loading.gif', // gif 占位 image: 'https://xxx/xxx/xxx.jpg', fit: BoxFit.cover, // 图片拉伸模式 width: 200, height: 200, );
Image 控件须要根据图片资源异步加载的状况,决定自身的显示效果,所以是一个 StatefulWidget。图片加载过程由 ImageProvider 触发,而 ImageProvider 表示异步获取图片数据的操做,能够从资源、文件和网络等不一样的渠道获取图片。
首先,ImageProvider 根据 _imageSate 中传递的图片配置生成对应的图片缓存 key;而后,去 ImageCache 中查找是否有对应的图片缓存,若是有,则通知 _ImageState 刷新 UI;若是没有,则启动 ImageStream 开始异步加载,加载完毕后,更新缓存;最后通知 _imageSate 刷新 UI。
值得注意的是,ImageCache 使用 LRU(Least Recently Used,最近最少使用)算法进行缓存更新策略,而且默认最多存储 1000 张图片,最大缓存限制为 100 MB,当限定的空间已经存满数据时,把最久没有被访问到的图片清除。图片 缓存只会在运行期间生效,也就是只缓存在内存中。若是想要支持缓存到文件系统,可使用第三方的 CachedNetworkImage
控件。
经过按钮,能够相应用户的交互事件。Flutter 提供了三个基本的按钮空间,即 FloatingActionButton、FlatButton 和 RaisedButton。
既然是按钮,所以除了控制基本样式以外,还须要响应用户点击行为。这就对应着按钮空间中的两个最重要的参数:
除此以外,还能够进行样式定制(以 FlatButton 为例):
FlatButton(
color: Colors.yellow, // 设置背景色为黄色 shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(20.0)), // 设置斜角矩形边框 colorBrightness: Brightness.light, // 确保文字按钮为深色 onPressed: () => print('FlatButton pressed'), child: Row(children: <Widget>[Icon(Icons.add), Text("Add")],), );
若相关基本元素的排列布局超过屏幕显示尺寸(即超过一屏)时,就须要引入列表控件来展现视图的完整内容,并根据元素的多少进行自适应滚动展现。
这样的需求,在 Android 中是由 ListView 或 RecyclerView 实现的,在 iOS 中是用 UITableView 实现的;而在 Flutter 中,实现这种需求的则是列表空间 ListView。
在 Flutter 中,ListView 能够沿着一个方向(垂直或水平方向)来排列其全部子 Widget,所以经常使用于须要展现一组连续视图元素的场景,好比通信录、优惠劵、商家列表等。
ListView 提供了一个默认构造函数 ListView,能够经过设置它的 children 参数,很方便地将全部的子 Widget 包含到 ListView 中。
可是,这种建立方式要求提早将全部的子 Widget 一次性建立好,而不是等到它们真正在屏幕上须要显示时才建立,因此有一个很明显的缺点,就是性能很差。所以,这种方式仅适用于列表中含有少许元素的场景。
ListView(
scrollDirection: Axis.horizontal, // 设置滚动方向 children: <Widget>[ // 设置 ListTile 组件的标题与图标 ListTile(leading: Icon(Icons.map), title: Text('Map'),), ListTile(leading: Icon(Icons.mail), title: Text('Mail'),), ListTile(leading: Icon(Icons.message), title: Text('Message'),), ], );
**ListView 的另一个构造函数 ListView.builder,则适用于子 Widget 比较多的场景。这个构造函数有两个关键参数:
具体用法以下,定义一个拥有 100 个列表元素的 ListView:
ListView.builder( itemCount: 100, // 元素个数 itemExtent: 50.0, // 列表项高度 itemBuilder: (BuildContext context, int index) => ListTile(title: Text("title $index"), subtitle: Text("body $index"),), );
须要注意的是,itemExtent并非一个必填参数。但,对于定高的列表项元素,强烈建议提早设置好这个参数的值。
由于若是这个参数为 null,ListView 会动态地根据子 Widget 建立完成的结果,决定自身的视图高度,以及子 Widget 在 ListView 中的相对位置。在滚动发生变化而列表项又不少时,这样的计算就会很是频繁。
在 ListView 中,有两种方式支持分割线:
总结 ListView 常见的构造方法及适用场景以下:
构造函数名 | 特色 | 适用场景 | 使用频次 |
---|---|---|---|
ListView | 一次性建立好所有子 Widget | 适用于展现少许连续子 Widget 的场景 | 中 |
List.builder | 提供了子 Widget 建立方法,仅在须要展现时才建立 | 适用于子 Widget 较多,且视觉效果呈现某种规律性的场景 | 高 |
ListView.separated | 与 ListView.builder 相似,并提供了自定义分割线的功能 | 与 ListView.builder 场景相似 | 中 |
ListView 实现了单一视图下可滚动 Widget 的交互模式,同时也包含了 UI 显示相关的控制逻辑和布局模型。可是,对于某些特殊交互场景,好比多个效果联动、嵌套滚动、精细滑动、视图跟随手势操做等,还须要嵌套多个 ListView 来实现。这时,各自视图的滚动和布局模型就是相互独立、分离的,就很难保证整个页面统一一致的滑动效果。
在 Flutter 中有一个专门的控件 CustomScrollView,用来处理多个须要自定义滚动效果的 Widget。在 CustomScrollView 中,这些彼此独立的、可滚动的 Widget 被统称为 Sliver。
好比,ListView 的 Sliver 实现为 SliverList,AppBar 的 Sliver 实现为 SliverAppBar。这些 Sliver 再也不维护各自的滚动状态,而是交由 CustomScrollView 统一管理,最终实现滑动效果的一致性。
能够经过一个滚动视差的例子,演示 CustomScrollView 的使用方法。
视差滚动是指让多层背景以不一样的速度移动,在造成立体滚动效果的同时,还能保证良好的视觉体验。做为移动应用交互设计的热点趋势,愈来愈多的移动应用使用来这项技术。
以一个有着封面头图的列表为例,封面头图和列表这两层视图的滚动联动起来,当用户滚动列表时,头图会根据用户的滚动手势,进行缩小和展开。
通过分析得出,要实现这样的需求,须要两个 Sliver:做为头图的 SliverAppBar,做为列表的 SliverList。思路以下:
具体的示例代码以下:
CustomScrollView ( slivers: <Widget>[ SliverAppBar( // SliverAppBar 做为头图控件 title: Text('CustomScrollView Demo'), // 标题 floating: true, // 设置悬浮样式 flexibleSpace: Image.network("https://xx.jpg", fit: BoxFit.cover,), // 设置悬浮头图背景 expandedHeight: 300, // 头图控件高度 ), SliverList( // SliverList 做为列表控件 delegate: SliverChildBuilderDelegate( (context, index) => ListTile(title: Text('Item #$index'),), //列表项建立方法 childCount: 100, // 列表元素个数 ), ) ], );
使用 ScrollController 进行滚动信息的监听,以及相应的滚动控制;ScrollNotifiCation 通知进行滚动事件的获取。
在 Flutter 中,由于 Widget 并非渲染到屏幕的最终视觉元素(RenderObject 才是),因此没法像原生的 Android 或 iOS 系统那样,向持有的 Widget 对象获取或者设置最终渲染相关的视觉信息,而必须经过对应的组件控制器才能实现。
ListView 的组件控制器则是 ScrollController,咱们能够经过它来获取视图的滚动信息,更新视图的滚动位置。
通常而言,获取视图的滚动信息每每是为了进行界面的状态控制,所以 ScrollController 的初始化、监听及销毁须要与 StatefulWidget 的状态保持同步。
代码示例所示,声明一个有着 100 个元素的列表项,当滚动视图到特定位置后,用户能够点击按钮返回列表顶部:
class _MyAppState extends State<MyApp> { ScrollController _controller; // ListView 控制器 bool isToTop = false; // 标示目前是否须要启用 "Top" 按钮 @override void initState() { _controller = ScrollController(); _controller.addListener(() { // 为控制器注册滚动监听方法 if (_controller.offset > 1000) { // 若是 ListView 已经向下滚动了 1000,则启用 Top 按钮 setState(() { isToTop = true; }); } else if (_controller.offset < 300) { // 若是 ListView 向下滚动距离不足 300,则禁用 Top 按钮 setState(() { isToTop = false; }); super.initState(); } }); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Scroll Controller Widget"),), body: Column( children: <Widget>[ Container( height: 40.0, child: RaisedButton(onPressed: (isToTop ? () { if (isToTop) { _controller.animateTo(.0, duration: Duration(milliseconds: 200), curve: Curves.ease); } } : null), child: Text("Top"),), ), Expanded( child: ListView.builder( controller: _controller, // 初始化传入控制器 itemCount: 100, // 列表元素总和 itemBuilder: (context, index) => ListTile(title: Text("Index : $index"),) // 列表项构造方法 ), ) ], ), ); } @override void dispose() { _controller.dispose(); // 销毁控制器 super.dispose(); } }
在 Flutter 中, ScroNotification 通知的获取是经过 NotificationListener 来实现的。与 ScrollController 不一样的是,NotificationListener 是一个 Widget,为了监听滚动类型的事件,咱们须要将 NotificationListener 添加为 ListView 的父容器,从而捕获 ListView 中的通知。而这些通知,须要经过 onNotification 回调函数实现监听逻辑:
Widget build(BuildContext context) {
return MaterialApp( title: 'ScrollController Demo', home: Scaffold( appBar: AppBar(title: Text('ScrollController Demo')), body: NotificationListener<ScrollNotification>( onNotification: (scrollNotification) { if (scrollNotification is ScrollStartNotification) { // 开始滚动 } else if ( scrollNotification is ScrollUpdateNotification) { // 滚动位置更新 } else if (ScrollStartNotification is ScrollEndNotification) { // 滚动结束 } }, child: ListView.builder(itemBuilder: (context, index) => ListTile(title: Text("Index : $index"),)); ), ), ); }
相比于 ScrollController 只能和具体的 ListView 关联后才能够监听到滚动信息;经过 NotificationListener 则能够监听其子 Widget 中的任意 ListView,不只能够获得这些 ListView 的当前滚动位置信息,还能够获取当前的滚动事件信息。