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 的當前滾動位置信息,還能夠獲取當前的滾動事件信息。