整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          (五) Flutter入門學習 之 Widget滾動

          (五) Flutter入門學習 之 Widget滾動

          表是移動端經常使用的一種視圖展示方式,在Flutter中提供了ListView和GridView。

          為了可能展示出更好的效果,我這里提供了一段Json數據,所以我們可以先學習一下Json解析。


          一. JSON讀取和解析

          在開發中,我們經常會使用本地JSON或者從服務器請求數據后回去到JSON,拿到JSON后通常會將JSON轉成Model對象來進行后續的操作,因為這樣操作更加的方便,也更加的安全。

          所以學習JSON的相關操作以及讀取JSON后如何轉成Model對象對于Flutter開發也非常重要。

          1.1. JSON資源配置

          JSON也屬于一種資源,所以在使用之前需要先進行相關的配置

          我們之前在學習使用Image組件時,用到了本地圖片,本地圖片必須在pubspec.yaml中進行配置:


          1.2. JSON讀取解析

          JSON資源讀取

          如果我們希望讀取JSON資源,可以使用package:flutter/services.dart包中的rootBundle。

          在rootBundle中有一個loadString方法,可以去加載JSON資源

          • 但是注意,查看該方法的源碼,你會發現這個操作是一個異步的。
          • 關于Future和async,這里就不再展開講解,可以去查看之前的dart語法。
          Future<String> loadString(String key, { bool cache=true }) async {
            ...省略具體代碼,可以自行查看源碼
          }

          代碼如下:(不要試圖拷貝這個代碼去運行,是沒辦法運行的)

          import 'package:flutter/services.dart' show rootBundle;
          
          // 打印讀取的結果是一個字符串
          rootBundle.loadString("assets/yz.json").then((value)=> print(value));

          JSON字符串轉化

          拿到JSON字符串后,我們需要將其轉成成我們熟悉的List和Map類型。

          我們可以通過dart:convert包中的json.decode方法將其進行轉化

          代碼如下:

          // 1.讀取json文件
          String jsonString=await rootBundle.loadString("assets/yz.json");
          
          // 2.轉成List或Map類型
          final jsonResult=json.decode(jsonString);

          對象Model定義

          將JSON轉成了List和Map類型后,就可以將List中的一個個Map轉成Model對象,所以我們需要定義自己的Model

          class Anchor {
            String nickname;
            String roomName;
            String imageUrl;
          
            Anchor({
              this.nickname,
              this.roomName,
              this.imageUrl
            });
          
            Anchor.withMap(Map<String, dynamic> parsedMap) {
              this.nickname=parsedMap["nickname"];
              this.roomName=parsedMap["roomName"];
              this.imageUrl=parsedMap["roomSrc"];
            }
          }

          1.3. JSON解析代碼

          上面我們給出了解析的一個個步驟,下面我們給出完整的代碼邏輯

          這里我單獨創建了一個anchor.dart的文件,在其中定義了所有的相關代碼:

          • 之后外界只需要調用我內部的getAnchors就可以獲取到解析后的數據了
          import 'package:flutter/services.dart' show rootBundle;
          import 'dart:convert';
          import 'dart:async';
          
          class Anchor {
            String nickname;
            String roomName;
            String imageUrl;
          
            Anchor({
              this.nickname,
              this.roomName,
              this.imageUrl
            });
          
            Anchor.withMap(Map<String, dynamic> parsedMap) {
              this.nickname=parsedMap["nickname"];
              this.roomName=parsedMap["roomName"];
              this.imageUrl=parsedMap["roomSrc"];
            }
          }
          
          Future<List<Anchor>> getAnchors() async {
            // 1.讀取json文件
            String jsonString=await rootBundle.loadString("assets/yz.json");
          
            // 2.轉成List或Map類型
            final jsonResult=json.decode(jsonString);
          
            // 3.遍歷List,并且轉成Anchor對象放到另一個List中
            List<Anchor> anchors=new List();
            for (Map<String, dynamic> map in jsonResult) {
              anchors.add(Anchor.withMap(map));
            }
            return anchors;
          }

          二. ListView組件

          移動端數據量比較大時,我們都是通過列表來進行展示的,比如商品數據、聊天列表、通信錄、朋友圈等。

          在Android中,我們可以使用ListView或RecyclerView來實現,在iOS中,我們可以通過UITableView來實現。

          在Flutter中,我們也有對應的列表Widget,就是ListView。

          2.1. ListView基礎

          2.1.1 ListView基本使用

          ListView可以沿一個方向(垂直或水平方向,默認是垂直方向)來排列其所有子Widget。

          一種最簡單的使用方式是直接將所有需要排列的子Widget放在ListView的children屬性中即可。

          我們來看一下直接使用ListView的代碼演練:

          • 為了讓文字之間有一些間距,我使用了Padding Widget
          class MyHomeBody extends StatelessWidget {
            final TextStyle textStyle=TextStyle(fontSize: 20, color: Colors.redAccent);
          
            @override
            Widget build(BuildContext context) {
              return ListView(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text("人的一切痛苦,本質上都是對自己無能的憤怒。", style: textStyle),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text("人活在世界上,不可以有偏差;而且多少要費點勁兒,才能把自己保持到理性的軌道上。", style: textStyle),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text("我活在世上,無非想要明白些道理,遇見些有趣的事。", style: textStyle),
                  )
                ],
              );
            }
          }

          2.2.2. ListTile的使用

          在開發中,我們經常見到一種列表,有一個圖標或圖片(Icon),有一個標題(Title),有一個子標題(Subtitle),還有尾部一個圖標(Icon)。

          這個時候,我們可以使用ListTile來實現:

          class MyHomeBody extends StatelessWidget {
          
            @override
            Widget build(BuildContext context) {
              return ListView(
                children: <Widget>[
                  ListTile(
                    leading: Icon(Icons.people, size: 36,),
                    title: Text("聯系人"),
                    subtitle: Text("聯系人信息"),
                    trailing: Icon(Icons.arrow_forward_ios),
                  ),
                  ListTile(
                    leading: Icon(Icons.email, size: 36,),
                    title: Text("郵箱"),
                    subtitle: Text("郵箱地址信息"),
                    trailing: Icon(Icons.arrow_forward_ios),
                  ),
                  ListTile(
                    leading: Icon(Icons.message, size: 36,),
                    title: Text("消息"),
                    subtitle: Text("消息詳情信息"),
                    trailing: Icon(Icons.arrow_forward_ios),
                  ),
                  ListTile(
                    leading: Icon(Icons.map, size: 36,),
                    title: Text("地址"),
                    subtitle: Text("地址詳情信息"),
                    trailing: Icon(Icons.arrow_forward_ios),
                  )
                ],
              );
            }
          }


          2.2.3. 垂直方向滾動

          我們可以通過設置 scrollDirection 參數來控制視圖的滾動方向。

          我們通過下面的代碼實現一個水平滾動的內容:

          • 這里需要注意,我們需要給Container設置width,否則它是沒有寬度的,就不能正常顯示。
          • 或者我們也可以給ListView設置一個itemExtent,該屬性會設置滾動方向上每個item所占據的寬度。
          class MyHomeBody extends StatelessWidget {
          
            @override
            Widget build(BuildContext context) {
              return ListView(
                scrollDirection: Axis.horizontal,
                itemExtent: 200,
                children: <Widget>[
                  Container(color: Colors.red, width: 200),
                  Container(color: Colors.green, width: 200),
                  Container(color: Colors.blue, width: 200),
                  Container(color: Colors.purple, width: 200),
                  Container(color: Colors.orange, width: 200),
                ],
              );
            }
          }


          2.2. ListView.build

          通過構造函數中的children傳入所有的子Widget有一個問題:默認會創建出所有的子Widget。

          但是對于用戶來說,一次性構建出所有的Widget并不會有什么差異,但是對于我們的程序來說會產生性能問題,而且會增加首屏的渲染時間。

          我們可以ListView.build來構建子Widget,提供性能。

          2.2.1. ListView.build基本使用

          ListView.build適用于子Widget比較多的場景,該構造函數將創建子Widget交給了一個抽象的方法,交給ListView進行管理,ListView會在真正需要的時候去創建子Widget,而不是一開始就全部初始化好。

          該方法有兩個重要參數:

          • itemBuilder:列表項創建的方法。當列表滾動到對應位置的時候,ListView會自動調用該方法來創建對應的子Widget。類型是IndexedWidgetBuilder,是一個函數類型。
          • itemCount:表示列表項的數量,如果為空,則表示ListView為無限列表。

          我們還是通過一個簡單的案例來認識它:

          class MyHomeBody extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return ListView.builder(
                itemCount: 100,
                itemExtent: 80,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text("標題$index"), subtitle: Text("詳情內容$index"));
                }
              );
            }
          }


          2.2.2. ListView.build動態數據

          在之前,我們搞了一個yz.json數據,我們現在動態的來通過JSON數據展示一個列表。

          思考:這個時候是否依然可以使用StatelessWidget:

          答案:不可以,因為當前我們的數據是異步加載的,剛開始界面并不會展示數據(沒有數據),后面從JSON中加載出來數據(有數據)后,再次展示加載的數據。

          • 這里是有狀態的變化的,從無數據,到有數據的變化。
          • 這個時候,我們需要使用StatefulWidget來管理組件。

          展示代碼如下:

          	import 'model/anchor.dart';
          
          ...省略中間代碼
          class MyHomeBody extends StatefulWidget {
            @override
            State<StatefulWidget> createState() {
              return MyHomeBodyState();
            }
          }
          
          class MyHomeBodyState extends State<MyHomeBody> {
            List<Anchor> anchors=[];
          
            // 在初始化狀態的方法中加載數據
            @override
            void initState() {
              getAnchors().then((anchors) {
                setState(() {
                  this.anchors=anchors;
                });
              });
          
              super.initState();
            }
          
            @override
            Widget build(BuildContext context) {
              return ListView.builder(
                itemBuilder: (BuildContext context, int index) {
                  return Padding(
                    padding: EdgeInsets.all(8),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Image.network(
                          anchors[index].imageUrl,
                          fit: BoxFit.fitWidth,
                          width: MediaQuery.of(context).size.width,
                        ),
                        SizedBox(height: 8),
                        Text(anchors[index].nickname, style: TextStyle(fontSize: 20),),
                        SizedBox(height: 5),
                        Text(anchors[index].roomName)
                      ],
                    ),
                  );
                },
              );
            }
          }


          2.2.3. ListView.separated

          ListView.separated可以生成列表項之間的分割器,它除了比ListView.builder多了一個separatorBuilder參數,該參數是一個分割器生成器。

          下面我們看一個例子:奇數行添加一條藍色下劃線,偶數行添加一條紅色下劃線:

          class MySeparatedDemo extends StatelessWidget {
            Divider blueColor=Divider(color: Colors.blue);
            Divider redColor=Divider(color: Colors.red);
          
            @override
            Widget build(BuildContext context) {
              return ListView.separated(
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(
                    leading: Icon(Icons.people),
                    title: Text("聯系人${index+1}"),
                    subtitle: Text("聯系人電話${index+1}"),
                  );
                },
                separatorBuilder: (BuildContext context, int index) {
                  return index % 2==0 ? redColor : blueColor;
                },
                itemCount: 100
              );
            }
          }


          三. GridView組件

          GridView用于展示多列的展示,在開發中也非常常見,比如直播App中的主播列表、電商中的商品列表等等。

          在Flutter中我們可以使用GridView來實現,使用方式和ListView也比較相似。

          3.1. GridView構造函數

          我們先學習GridView構造函數的使用方法

          一種使用GridView的方式就是使用構造函數來創建,和ListView對比有一個特殊的參數:gridDelegate

          gridDelegate用于控制交叉軸的item數量或者寬度,需要傳入的類型是SliverGridDelegate,但是它是一個抽象類,所以我們需要傳入它的子類:

          SliverGridDelegateWithFixedCrossAxisCount

          SliverGridDelegateWithFixedCrossAxisCount({
            @required double crossAxisCount, // 交叉軸的item個數
            double mainAxisSpacing=0.0, // 主軸的間距
            double crossAxisSpacing=0.0, // 交叉軸的間距
            double childAspectRatio=1.0, // 子Widget的寬高比
          })

          代碼演練:

          class MyGridCountDemo extends StatelessWidget {
          
            List<Widget> getGridWidgets() {
              return List.generate(100, (index) {
                return Container(
                  color: Colors.purple,
                  alignment: Alignment(0, 0),
                  child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
                );
              });
            }
          
            @override
            Widget build(BuildContext context) {
              return GridView(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisSpacing: 10,
                  crossAxisSpacing: 10,
                  childAspectRatio: 1.0
                ),
                children: getGridWidgets(),
              );
            }
          }

          SliverGridDelegateWithMaxCrossAxisExtent

          SliverGridDelegateWithMaxCrossAxisExtent({
            double maxCrossAxisExtent, // 交叉軸的item寬度
            double mainAxisSpacing=0.0, // 主軸的間距
            double crossAxisSpacing=0.0, // 交叉軸的間距
            double childAspectRatio=1.0, // 子Widget的寬高比
          })

          代碼演練:

          class MyGridExtentDemo extends StatelessWidget {
          
            List<Widget> getGridWidgets() {
              return List.generate(100, (index) {
                return Container(
                  color: Colors.purple,
                  alignment: Alignment(0, 0),
                  child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
                );
              });
            }
          
            @override
            Widget build(BuildContext context) {
              return GridView(
                gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                  maxCrossAxisExtent: 150,
                  mainAxisSpacing: 10,
                  crossAxisSpacing: 10,
                  childAspectRatio: 1.0
                ),
                children: getGridWidgets(),
              );
            }
          }


          前面兩種方式也可以不設置delegate

          可以分別使用:GridView.count構造函數和GridView.extent構造函數實現相同的效果,這里不再贅述。

          3.2. GridView.build

          和ListView一樣,使用構造函數會一次性創建所有的子Widget,會帶來性能問題,所以我們可以使用GridView.build來交給GridView自己管理需要創建的子Widget。

          我們直接使用之前的數據來進行代碼演練:

          class _GridViewBuildDemoState extends State<GridViewBuildDemo> {
            List<Anchor> anchors=[];
          
            @override
            void initState() {
              getAnchors().then((anchors) {
                setState(() {
                  this.anchors=anchors;
                });
              });
              super.initState();
            }
          
            @override
            Widget build(BuildContext context) {
              return Padding(
                padding: const EdgeInsets.all(8.0),
                child: GridView.builder(
                  shrinkWrap: true,
                  physics: ClampingScrollPhysics(),
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    mainAxisSpacing: 10,
                    crossAxisSpacing: 10,
                    childAspectRatio: 1.2
                  ),
                  itemCount: anchors.length,
                  itemBuilder: (BuildContext context, int index) {
                    return Container(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Image.network(anchors[index].imageUrl),
                          SizedBox(height: 5),
                          Text(anchors[index].nickname, style: TextStyle(fontSize: 16),),
                          Text(anchors[index].roomName, maxLines: 1, overflow: TextOverflow.ellipsis,)
                        ],
                      ),
                    );
                  }
                ),
              );
            }
          }


          四. Slivers

          我們考慮一個這樣的布局:一個滑動的視圖中包括一個標題視圖(HeaderView),一個列表視圖(ListView),一個網格視圖(GridView)。

          我們怎么可以讓它們做到統一的滑動效果呢?使用前面的滾動是很難做到的。

          Flutter中有一個可以完成這樣滾動效果的Widget:CustomScrollView,可以統一管理多個滾動視圖。

          在CustomScrollView中,每一個獨立的,可滾動的Widget被稱之為Sliver。

          補充:Sliver可以翻譯成裂片、薄片,你可以將每一個獨立的滾動視圖當做一個小裂片。

          4.1. Slivers的基本使用

          因為我們需要把很多的Sliver放在一個CustomScrollView中,所以CustomScrollView有一個slivers屬性,里面讓我們放對應的一些Sliver:

          • SliverList:類似于我們之前使用過的ListView;
          • SliverFixedExtentList:類似于SliverList只是可以設置滾動的高度;
          • SliverGrid:類似于我們之前使用過的GridView;
          • SliverPadding:設置Sliver的內邊距,因為可能要單獨給Sliver設置內邊距;
          • SliverAppBar:添加一個AppBar,通常用來作為CustomScrollView的HeaderView;
          • SliverSafeArea:設置內容顯示在安全區域(比如不讓齊劉海擋住我們的內容)

          我們簡單演示一下:SliverGrid+SliverPadding+SliverSafeArea的組合

          class HomeContent extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return CustomScrollView(
                slivers: <Widget>[
                  SliverSafeArea(
                    sliver: SliverPadding(
                      padding: EdgeInsets.all(8),
                      sliver: SliverGrid(
                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 2,
                          crossAxisSpacing: 8,
                          mainAxisSpacing: 8,
                        ),
                        delegate: SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
                            return Container(
                              alignment: Alignment(0, 0),
                              color: Colors.orange,
                              child: Text("item$index"),
                            );
                          },
                          childCount: 20
                        ),
                      ),
                    ),
                  )
                ],
              );
            }
          }
          


          4.2. Slivers的組合使用

          這里我使用官方的示例程序,將SliverAppBar+SliverGrid+SliverFixedExtentList做出如下界面:

          class HomeContent extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return showCustomScrollView();
            }
          
            Widget showCustomScrollView() {
              return new CustomScrollView(
                slivers: <Widget>[
                  const SliverAppBar(
                    expandedHeight: 250.0,
                    flexibleSpace: FlexibleSpaceBar(
                      title: Text('Coderwhy Demo'),
                      background: Image(
                        image: NetworkImage(
                          "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
                        ),
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                  new SliverGrid(
                    gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
                      maxCrossAxisExtent: 200.0,
                      mainAxisSpacing: 10.0,
                      crossAxisSpacing: 10.0,
                      childAspectRatio: 4.0,
                    ),
                    delegate: new SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
                        return new Container(
                          alignment: Alignment.center,
                          color: Colors.teal[100 * (index % 9)],
                          child: new Text('grid item $index'),
                        );
                      },
                      childCount: 10,
                    ),
                  ),
                  SliverFixedExtentList(
                    itemExtent: 50.0,
                    delegate: SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
                        return new Container(
                          alignment: Alignment.center,
                          color: Colors.lightBlue[100 * (index % 9)],
                          child: new Text('list item $index'),
                        );
                      },
                      childCount: 20
                    ),
                  ),
                ],
              );
            }
          }
          


          五. 監聽滾動事件

          對于滾動的視圖,我們經常需要監聽它的一些滾動事件,在監聽到的時候去做對應的一些事情。

          比如視圖滾動到底部時,我們可能希望做上拉加載更多;

          比如滾動到一定位置時顯示一個回到頂部的按鈕,點擊回到頂部的按鈕,回到頂部;

          比如監聽滾動什么時候開始,什么時候結束;

          在Flutter中監聽滾動相關的內容由兩部分組成:ScrollController和ScrollNotification。

          5.1. ScrollController

          在Flutter中,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常這種監聽事件以及相關的信息并不能直接從Widget中獲取,而是必須通過對應的Widget的Controller來實現。

          ListView、GridView的組件控制器是ScrollController,我們可以通過它來獲取視圖的滾動信息,并且可以調用里面的方法來更新視圖的滾動位置。

          另外,通常情況下,我們會根據滾動的位置來改變一些Widget的狀態信息,所以ScrollController通常會和StatefulWidget一起來使用,并且會在其中控制它的初始化、監聽、銷毀等事件。

          我們來做一個案例,當滾動到1000位置的時候,顯示一個回到頂部的按鈕:

          • jumpTo(double offset)、animateTo(double offset,...):這兩個方法用于跳轉到指定的位置,它們不同之處在于,后者在跳轉時會執行一個動畫,而前者不會。
          • ScrollController間接繼承自Listenable,我們可以根據ScrollController來監聽滾動事件。
          class MyHomePage extends StatefulWidget {
            @override
            State<StatefulWidget> createState()=> MyHomePageState();
          }
          
          class MyHomePageState extends State<MyHomePage> {
            ScrollController _controller;
            bool _isShowTop=false;
            
            @override
            void initState() {
              // 初始化ScrollController
              _controller=ScrollController();
              
              // 監聽滾動
              _controller.addListener(() {
                var tempSsShowTop=_controller.offset >=1000;
                if (tempSsShowTop !=_isShowTop) {
                  setState(() {
                    _isShowTop=tempSsShowTop;
                  });
                }
              });
              
              super.initState();
            }
          
            @override
            Widget build(BuildContext context) {
              return Scaffold(
                appBar: AppBar(
                  title: Text("ListView展示"),
                ),
                body: ListView.builder(
                  itemCount: 100,
                  itemExtent: 60,
                  controller: _controller,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(title: Text("item$index"));
                  }
                ),
                floatingActionButton: !_isShowTop ? null : FloatingActionButton(
                  child: Icon(Icons.arrow_upward),
                  onPressed: () {
                    _controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
                  },
                ),
              );
            }
          }


          5.2. NotificationListener

          如果我們希望監聽什么時候開始滾動,什么時候結束滾動,這個時候我們可以通過NotificationListener。

          • NotificationListener是一個Widget,模板參數T是想監聽的通知類型,如果省略,則所有類型通知都會被監聽,如果指定特定類型,則只有該類型的通知會被監聽。
          • NotificationListener需要一個onNotification回調函數,用于實現監聽處理邏輯。
          • 該回調可以返回一個布爾值,代表是否阻止該事件繼續向上冒泡,如果為true時,則冒泡終止,事件停止向上傳播,如果不返回或者返回值為false 時,則冒泡繼續。

          案例: 列表滾動, 并且在中間顯示滾動進度

          class MyHomeNotificationDemo extends StatefulWidget {
            @override
            State<StatefulWidget> createState()=> MyHomeNotificationDemoState();
          }
          
          class MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
            int _progress=0;
          
            @override
            Widget build(BuildContext context) {
              return NotificationListener(
                onNotification: (ScrollNotification notification) {
                  // 1.判斷監聽事件的類型
                  if (notification is ScrollStartNotification) {
                    print("開始滾動.....");
                  } else if (notification is ScrollUpdateNotification) {
                    // 當前滾動的位置和總長度
                    final currentPixel=notification.metrics.pixels;
                    final totalPixel=notification.metrics.maxScrollExtent;
                    double progress=currentPixel / totalPixel;
                    setState(() {
                      _progress=(progress * 100).toInt();
                    });
                    print("正在滾動:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
                  } else if (notification is ScrollEndNotification) {
                    print("結束滾動....");
                  }
                  return false;
                },
                child: Stack(
                  alignment: Alignment(.9, .9),
                  children: <Widget>[
                    ListView.builder(
                      itemCount: 100,
                      itemExtent: 60,
                      itemBuilder: (BuildContext context, int index) {
                        return ListTile(title: Text("item$index"));
                      }
                    ),
                    CircleAvatar(
                      radius: 30,
                      child: Text("$_progress%"),
                      backgroundColor: Colors.black54,
                    )
                  ],
                ),
              );
            }
          }


          原文來自:https://mp.weixin.qq.com/s/rdmfeUECLtnZz6GeQKzOqA(侵權刪除)

          端開發"酷炫技巧--虛擬滾動。

          大家好,今天我們來介紹前端開發中的酷炫技巧--虛擬滾動。什么是虛擬滾動?

          虛擬滾動也稱為窗口化滾動,是一種優化技術,用于在固定大小的界面中顯示大量內容,它只渲染可視區域的內容而不是全部。

          為什么使用虛擬滾動?

          虛擬滾動有很多優點。首先它可以顯著提高網頁性能,特別是對于需要顯示大量數據的應用程序。其次它可以提高用戶體驗,因為用戶不需要等待所有內容都加載完畢才能開始瀏覽。

          如何實現虛擬滾動?實現虛擬滾動主要需要兩步:

          ·首先,需要使用JavaScript來測量視口的大小,并確定需要渲染哪些內容。

          ·然后,需要使用CSS來定位和呈現這些內容。

          常見問題和解決方案在使用虛擬滾動時可能會遇到一些問題,例如如果用戶滾動速度過快,可能會出現內容閃爍的問題。為了解決這個問題,你可以使用防抖(debounce)技術來限制頻繁的渲染。

          虛擬滾動是一個非常強大的前端技術,可以幫助你優化網頁性能并提高用戶體驗。

          希望這個視頻能幫助你了解并實現虛擬滾動!

          | 大澈


          大家好,我是大澈!

          又是好久沒更文了,前陣子駕著新車回了趟老家,很“幸運”的經歷了平原縣地震的余波。

          回想當時,半夜凌晨,房屋晃動,如同身處過山車,一切都很不真實。雖然震感時間很短暫,但是現在依舊讓我記憶猶新,人類在大自然面前真的是太渺小了,很多時候真的是力不從心。

          所以,真心想和大家說一句,生活不易,及時行樂,珍惜身邊人,且行且珍惜。


          ONE

          需求分析,問題描述

          一、需求

          一個可以滾動的菜單,為它添加一個可以下拉滾動的提示。要求滾動到菜單最底部時,隱藏下拉滾動的提示,否則讓其一直顯示。

          二、問題

          1、如何實現滾動條效果?

          2、如何判斷是否滾動到底部?


          TWO

          解決問題,答案速覽

          實現代碼如下,復制粘貼即可直接使用。

          代碼中滾動條的實現使用了element的el-scrollbar組件。組件中包裹的第一個div,指的是需要滾動的視圖。組件中包裹的第二個div,指的是下拉滾動提示的圖標,這里根據需求進行設置,可以更換靜態的或者那種閃爍跳躍的動態提示圖標。

          // 1、模版
          <el-scrollbar max-height="calc(100vh - 84px)" @scroll="handleScroll" ref="myScrollbar">
          <div class="sideBarIn"></div>
          <div class="pcSign pcIcon" v-if="isShowIcon">
          <img class="iconImg" src="../assets/images/common/xiaGery.png"></div>
          </el-scrollbar>
          
          
          // 2、邏輯
          // 滾動條事件
          const handleScroll=(val)=> {
          // 防止Scrollbar實例為空
          if (!myScrollbar.value) {
          return
          }
          
          // 判斷是否滾動到底部
          let isScrollToEnd=Number(myScrollbar.value.wrapRef.scrollTop.toFixed(0)) 
          + Number(myScrollbar.value.wrapRef.clientHeight.toFixed(0))===Number(myScrollbar.value.wrapRef.scrollHeight.toFixed(0));
          
          if (isScrollToEnd) {
          // 滾動到底部的處理邏輯
          isShowIcon.value=false
          } else {
          // 非滾動到底部的處理邏輯
          isShowIcon.value=true
          }
          }
          
          
          // 3、樣式
          .pcIcon {
          width:100%;
          height: 100px;
          position: absolute;
          bottom: -4px;
          left: 0;
          text-align: center;
          line-height: 130px;
          background: linear-gradient(to bottom, rgba(234, 234, 234, 0.5), rgba(234, 234, 234, 1));
          
          .iconImg {
          width: 20px;
          height: 20px;
          }
          }
          .pcSign{
          display: block;
          }


          THREE

          問題解析,知識總結

          一、如何實現滾動條效果?

          實現滾動條效果有兩種實現方式:利用css的overflow: scroll屬性、利用element的el-scrollbar組件。

          1、overflow: scroll屬性

          在div元素上添加 overflow-y: scroll; css屬性,就能顯示出一個滾動條,如果不指定是x或y軸,則水平和垂直都會出現滾動條。

          當然,前提是你需要指定div元素的高度或者最大高度。

          2、el-scrollbar組件

          一般在vue項目中,我們可直接使用element的el-scrollbar組件,因為官方為我們提供了許多API,以及各種適配優化。

          el-scrollbar組件的屬性如下:

          el-scrollbar組件的事件如下:

          el-scrollbar組件的實例屬性如下:


          二、如何判斷是否滾動到底部?

          這里判斷是否滾動到底部的關鍵在于scrollTop+clientHeight是否等于scrollHeight的值。只有當滾動的距離+可視區域的高度,與scrollHeight相等時,才證明滾動條滾動到了底部。

          同樣的,如果scrollHeight與可視區域的高度直接就相等時,又說明元素不可以滾動,也就沒有滾動條。這一點在有此需求時,可以進行實用。

          元素的幾個寬高屬性釋義如下:


          - END -


          主站蜘蛛池模板: 精品国产a∨无码一区二区三区| 亚洲一区二区三区久久| 伊人色综合一区二区三区影院视频| 日本视频一区二区三区| 国产无线乱码一区二三区| 精品人妻一区二区三区毛片| 国产精品亚洲综合一区在线观看 | 亚洲熟妇无码一区二区三区 | 国产福利电影一区二区三区,日韩伦理电影在线福 | 中文字幕一区二区三区有限公司| 国产视频一区在线播放| 日本无卡码一区二区三区| 国产一区二区成人| 激情亚洲一区国产精品| 久久久精品人妻一区二区三区蜜桃 | 精品永久久福利一区二区| 久久久久人妻精品一区三寸蜜桃| 搜日本一区二区三区免费高清视频 | 一区五十路在线中出| 日本一区二区不卡视频 | 无码人妻av一区二区三区蜜臀| 日日摸夜夜添一区| 日本在线电影一区二区三区| 国产精品成人一区二区三区| 一区二区中文字幕| 精品一区二区三区无码视频| 精品人妻中文av一区二区三区 | 熟妇人妻一区二区三区四区| 国产精品亚洲一区二区三区在线观看 | 亚洲国产成人久久一区二区三区| 亚洲国产成人久久综合一区77| 国产精品乱码一区二区三区| 无码精品人妻一区二区三区影院| 午夜性色一区二区三区不卡视频| 日本精品无码一区二区三区久久久 | 国产一区二区三区韩国女主播| 国产99久久精品一区二区| 精品一区二区三区免费毛片爱 | 日韩在线不卡免费视频一区| 久久精品无码一区二区WWW| 男人的天堂av亚洲一区2区|