<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在上一篇文章中,我和你一起學習了文本、圖片和按鈕這 3 大經典組件在 Flutter 中的使用方法,以及如何在實際開發中根據不同的場景,去自定義展示樣式。 文本、圖片和按鈕這些基本元素,需要進行排列組合,才能構成我們看到的 UI 視圖。那么,當這些基本元素的排列布局超過屏幕顯示尺寸(即超過一屏)時,我們就需要引入列表控件來展示視圖的完整內容,并根據元素的多少進行自適應滾動展示。 這樣的需求,在 Android 中是由 ListView 或 RecyclerView 實現的,在 iOS 中是用 UITableView 實現的;而在 Flutter 中,實現這種需求的則是列表控件 ListView。 ## ListView 在 Flutter 中,ListView 可以沿一個方向(垂直或水平方向)來排列其所有子 Widget,因此常被用于需要展示一組連續視圖元素的場景,比如通信錄、優惠券、商家列表等。 我們先來看看 ListView 怎么用。**ListView 提供了一個默認構造函數 ListView**,我們可以通過設置它的 children 參數,很方便地將所有的子 Widget 包含到 ListView 中。 不過,這種創建方式要求提前將所有子 Widget 一次性創建好,而不是等到它們真正在屏幕上需要顯示時才創建,所以有一個很明顯的缺點,就是性能不好。因此,**這種方式僅適用于列表中含有少量元素的場景**。 如下所示,我定義了一組列表項組件,并將它們放在了垂直滾動的 ListView 中: ~~~ ListView( 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')), ]); ~~~ > 備注:ListTile 是 Flutter 提供的用于快速構建列表項元素的一個小組件單元,用于 1~3 行(leading、title、subtitle)展示文本、圖標等視圖元素的場景,通常與 ListView 配合使用。 > 上面這段代碼中用到 ListTile,是為了演示 ListView 的能力。關于 ListTile 的具體使用細節,并不是本篇文章的重點,如果你想深入了解的話,可以參考[官方文檔](https://api.flutter.dev/flutter/material/ListTile-class.html)。 運行效果,如下圖所示: :-: ![](https://img.kancloud.cn/b1/52/b152f47246c851c3c1878564f07de101_828x1792.png) 圖 1 ListView 默認構造函數 除了默認的垂直方向布局外,ListView 還可以通過設置 scrollDirection 參數支持水平方向布局。如下所示,我定義了一組不同顏色背景的組件,將它們的寬度設置為 140,并包在了水平布局的 ListView 中,讓它們可以橫向滾動: ~~~ ListView( scrollDirection: Axis.horizontal, itemExtent: 140, //item 延展尺寸 (寬度) children: <Widget>[ Container(color: Colors.black), Container(color: Colors.red), Container(color: Colors.blue), Container(color: Colors.green), Container(color: Colors.yellow), Container(color: Colors.orange), ]); ~~~ 運行效果,如下圖所示: :-: ![](https://img.kancloud.cn/df/38/df382224daeca7067d3a9c5acc5febac_640x1136.gif) 圖 2 水平滾動的 ListView 在這個例子中,我們一次性創建了 6 個子 Widget。但從圖 2 的運行效果可以看到,由于屏幕的寬高有限,同一時間用戶只能看到 3 個 Widget。也就是說,是否一次性提前構建出所有要展示的子 Widget,與用戶而言并沒有什么視覺上的差異。 所以,考慮到創建子 Widget 產生的性能問題,更好的方法是抽象出創建子 Widget 的方法,交由 ListView 統一管理,在真正需要展示該子 Widget 時再去創建。 **ListView 的另一個構造函數 ListView.builder,則適用于子 Widget 比較多的場景**。這個構造函數有兩個關鍵參數: * itemBuilder,是列表項的創建方法。當列表滾動到相應位置時,ListView 會調用該方法創建對應的子 Widget。 * itemCount,表示列表項的數量,如果為空,則表示 ListView 為無限列表。 同樣地,我通過一個案例,與你說明 itemBuilder 與 itemCount 這兩個參數的具體用法。 我定義了一個擁有 100 個列表元素的 ListView,在列表項的創建方法中,分別將 index 的值設置為 ListTile 的標題與子標題。比如,第一行列表項會展示 title 0 body 0: ~~~ 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 中的相對位置。在滾動發生變化而列表項又很多時,這樣的計算就會非常頻繁。 但如果提前設置好 itemExtent,ListView 則可以提前計算好每一個列表項元素的相對位置,以及自身的視圖高度,省去了無謂的計算。 因此,在 ListView 中,指定 itemExtent 比讓子 Widget 自己決定自身高度會更高效。 運行這個示例,效果如下所示: :-: ![](https://img.kancloud.cn/d6/54/d654c5a28056afc017fe3f085230745a_828x1792.png) 圖 3 ListView.builder 構造函數 可能你已經發現了,我們的列表還缺少分割線。在 ListView 中,有兩種方式支持分割線: * 一種是,在 itemBuilder 中,根據 index 的值動態創建分割線,也就是將分割線視為列表項的一部分; * 另一種是,使用 ListView 的另一個構造方法 ListView.separated,單獨設置分割線的樣式。 第一種方式實際上是視圖的組合,之前的分享中我們已經多次提及,對你來說應該已經比較熟悉了,這里我就不再過多地介紹了。接下來,我和你演示一下**如何使用 ListView.separated 設置分割線。** 與 ListView.builder 抽離出了子 Widget 的構建方法類似,ListView.separated 抽離出了分割線的創建方法 separatorBuilder,以便根據 index 設置不同樣式的分割線。 如下所示,我針對 index 為偶數的場景,創建了綠色的分割線,而針對 index 為奇數的場景,創建了紅色的分割線: ~~~ // 使用 ListView.separated 設置分割線 ListView.separated( itemCount: 100, separatorBuilder: (BuildContext context, int index) => index %2 ==0? Divider(color: Colors.green) : Divider(color: Colors.red),//index 為偶數,創建綠色分割線;index 為奇數,則創建紅色分割線 itemBuilder: (BuildContext context, int index) => ListTile(title: Text("title $index"), subtitle: Text("body $index"))// 創建子 Widget ) ~~~ 運行效果,如下所示: :-: ![](https://img.kancloud.cn/5e/1e/5e1ef0977150346fa95f23232d628e3b_828x1792.png) 圖 4 ListView.separated 構造函數 好了,我已經與你分享完了 ListView 的常見構造函數。接下來,我準備了一張表格,總結了 ListView 常見的構造方法及其適用場景,供你參考,以便理解與記憶: :-: ![](https://img.kancloud.cn/00/e6/00e6c9f8724fcf50757b4a76fa4c9b18_932x340.png) 圖 5 ListView 常見的構造方法及其適用場景 ## CustomScrollView 好了,ListView 實現了單一視圖下可滾動 Widget 的交互模型,同時也包含了 UI 顯示相關的控制邏輯和布局模型。但是,對于某些特殊交互場景,比如多個效果聯動、嵌套滾動、精細滑動、視圖跟隨手勢操作等,還需要嵌套多個 ListView 來實現。這時,各自視圖的滾動和布局模型就是相互獨立、分離的,就很難保證整個頁面統一一致的滑動效果。 那么,**Flutter 是如何解決多 ListView 嵌套時,頁面滑動效果不一致的問題的呢?** 在 Flutter 中有一個專門的控件 CustomScrollView,用來處理多個需要自定義滾動效果的 Widget。在 CustomScrollView 中,**這些彼此獨立的、可滾動的 Widget 被統稱為 Sliver**。 比如,ListView 的 Sliver 實現為 SliverList,AppBar 的 Sliver 實現為 SliverAppBar。這些 Sliver 不再維護各自的滾動狀態,而是交由 CustomScrollView 統一管理,最終實現滑動效果的一致性。 接下來,我通過一個滾動視差的例子,與你演示 CustomScrollView 的使用方法。 **視差滾動**是指讓多層背景以不同的速度移動,在形成立體滾動效果的同時,還能保證良好的視覺體驗。 作為移動應用交互設計的熱點趨勢,越來越多的移動應用使用了這項技術。 以一個有著封面頭圖的列表為例,我們希望封面頭圖和列表這兩層視圖的滾動聯動起來,當用戶滾動列表時,頭圖會根據用戶的滾動手勢,進行縮小和展開。 經分析得出,要實現這樣的需求,我們需要兩個 Sliver:作為頭圖的 SliverAppBar,與作為列表的 SliverList。具體的實現思路是: * 在創建 SliverAppBar 時,把 flexibleSpace 參數設置為懸浮頭圖背景。flexibleSpace 可以讓背景圖顯示在 AppBar 下方,高度和 SliverAppBar 一樣; * 而在創建 SliverList 時,通過 SliverChildBuilderDelegate 參數實現列表項元素的創建; * 最后,將它們一并交由 CustomScrollView 的 slivers 參數統一管理。 具體的示例代碼如下所示: ~~~ 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,// 列表元素個數 ), ), ]); ~~~ 運行一下,視差滾動效果如下所示: :-: 圖 6 CustomScrollView 示例 ## ScrollController 與 ScrollNotification 現在,你應該已經知道如何實現滾動視圖的視覺和交互效果了。接下來,我再與你分享一個更為復雜的問題:在某些情況下,我們希望獲取視圖的滾動信息,并進行相應的控制。比如,列表是否已經滑到底(頂)了?如何快速回到列表頂部?列表滾動是否已經開始,或者是否已經停下來了? 對于前兩個問題,我們可以使用 ScrollController 進行滾動信息的監聽,以及相應的滾動控制;而最后一個問題,則需要接收 ScrollNotification 通知進行滾動事件的獲取。下面我將分別與你介紹。 在 Flutter 中,因為 Widget 并不是渲染到屏幕的最終視覺元素(RenderObject 才是),所以我們無法像原生的 Android 或 iOS 系統那樣,向持有的 Widget 對象獲取或設置最終渲染相關的視覺信息,而必須通過對應的組件控制器才能實現。 ListView 的組件控制器則是 ScrollControler,我們可以通過它來獲取視圖的滾動信息,更新視圖的滾動位置。 一般而言,獲取視圖的滾動信息往往是為了進行界面的狀態控制,因此 ScrollController 的初始化、監聽及銷毀需要與 StatefulWidget 的狀態保持同步。 如下代碼所示,我們聲明了一個有著 100 個元素的列表項,當滾動視圖到特定位置后,用戶可以點擊按鈕返回列表頂部: * 首先,我們在 State 的初始化方法里,創建了 ScrollController,并通過 \_controller.addListener 注冊了滾動監聽方法回調,根據當前視圖的滾動位置,判斷當前是否需要展示“Top”按鈕。 * 隨后,在視圖構建方法 build 中,我們將 ScrollController 對象與 ListView 進行了關聯,并且在 RaisedButton 中注冊了對應的回調方法,可以在點擊按鈕時通過 \_controller.animateTo 方法返回列表頂部。 * 最后,在 State 的銷毀方法中,我們對 ScrollController 進行了資源釋放。 ~~~ 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 MaterialApp( ... // 頂部 Top 按鈕,根據 isToTop 變量判斷是否需要注冊滾動到頂部的方法 RaisedButton(onPressed: (isToTop ? () { if(isToTop) { _controller.animateTo(.0, duration: Duration(milliseconds: 200), curve: Curves.ease );// 做一個滾動到頂部的動畫 } }:null),child: Text("Top"),) ... ListView.builder( controller: _controller,// 初始化傳入控制器 itemCount: 100,// 列表元素總數 itemBuilder: (context, index) => ListTile(title: Text("Index : $index")),// 列表項構造方法 ) ... ); @override void dispose() { _controller.dispose(); // 銷毀控制器 super.dispose(); } } ~~~ ScrollController 的運行效果如下所示: :-: ![](https://img.kancloud.cn/61/53/61533dc0e445bd529879698ad3491b1b_768x1592.gif) 圖 7 ScrollController 示例 介紹完了如何通過 ScrollController 來監聽 ListView 滾動信息,以及怎樣進行滾動控制之后,接下來我們再看看**如何獲取 ScrollNotification 通知,從而感知 ListView 的各類滾動事件**。 在 Flutter 中,ScrollNotification 通知的獲取是通過 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>(// 添加 NotificationListener 作為父容器 onNotification: (scrollNotification) {// 注冊通知回調 if (scrollNotification is ScrollStartNotification) {// 滾動開始 print('Scroll Start'); } else if (scrollNotification is ScrollUpdateNotification) {// 滾動位置更新 print('Scroll Update'); } else if (scrollNotification is ScrollEndNotification) {// 滾動結束 print('Scroll End'); } }, child: ListView.builder( itemCount: 30,// 列表元素個數 itemBuilder: (context, index) => ListTile(title: Text("Index : $index")),// 列表項創建方法 ), ) ) ); } ~~~ 相比于 ScrollController 只能和具體的 ListView 關聯后才可以監聽到滾動信息;通過 NotificationListener 則可以監聽其子 Widget 中的任意 ListView,不僅可以得到這些 ListView 的當前滾動位置信息,還可以獲取當前的滾動事件信息 。 ## 總結 在處理用于展示一組連續、可滾動的視圖元素的場景,Flutter 提供了比原生 Android、iOS 系統更加強大的列表組件 ListView 與 CustomScrollView,不僅可以支持單一視圖下可滾動 Widget 的交互模型及 UI 控制模型,對于某些特殊交互,需要嵌套多重可滾動 Widget 的場景,也提供了統一管理的機制,最終實現體驗一致的滑動效果。這些強大的組件,使得我們不僅可以開發出樣式豐富的界面,更可以實現復雜的交互。 接下來,我們簡單回顧一下今天的內容,以便加深你的理解與記憶。 首先,我們認識了 ListView 組件。它同時支持垂直方向和水平方向滾動,不僅提供了少量一次性創建子視圖的默認構造方式,也提供了大量按需創建子視圖的 ListView.builder 機制,并且支持自定義分割線。為了節省性能,對于定高的列表項視圖,提前指定 itemExtent 比讓子 Widget 自己決定要更高效。 隨后,我帶你學習了 CustomScrollView 組件。它引入了 Sliver 的概念,將多重嵌套的可滾動視圖的交互與布局進行統一接管,使得像視差滾動這樣的高級交互變得更加容易。 最后,我們學習了 ScrollController 與 NotificationListener,前者與 ListView 綁定,進行滾動信息的監聽,進行相應的滾動控制;而后者,通過將 ListView 納入子 Widget,實現滾動事件的獲取。 ## 思考題 最后,我給你留下兩個小作業吧: 1. 在 ListView.builder 方法中,ListView 根據 Widget 是否將要出現在可視區域內,按需創建。對于一些場景,為了避免 Widget 渲染時間過長(比如圖片下載),我們需要提前將可視區域上下一定區域內的 Widget 提前創建好。那么,在 Flutter 中,如何才能實現呢? 2. 請你使用 NotificationListener,來實現圖 7 ScrollController 示例中同樣的功能。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看