Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 久久手机精品视频,婷婷色在线视频,日韩不卡一区二区三区

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          (五) Flutter入門學(xué)習(xí) 之 Widget滾動(dòng)

          (五) Flutter入門學(xué)習(xí) 之 Widget滾動(dòng)

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

          為了可能展示出更好的效果,我這里提供了一段Json數(shù)據(jù),所以我們可以先學(xué)習(xí)一下Json解析。


          一. JSON讀取和解析

          在開發(fā)中,我們經(jīng)常會(huì)使用本地JSON或者從服務(wù)器請(qǐng)求數(shù)據(jù)后回去到JSON,拿到JSON后通常會(huì)將JSON轉(zhuǎn)成Model對(duì)象來進(jìn)行后續(xù)的操作,因?yàn)檫@樣操作更加的方便,也更加的安全。

          所以學(xué)習(xí)JSON的相關(guān)操作以及讀取JSON后如何轉(zhuǎn)成Model對(duì)象對(duì)于Flutter開發(fā)也非常重要。

          1.1. JSON資源配置

          JSON也屬于一種資源,所以在使用之前需要先進(jìn)行相關(guān)的配置

          我們之前在學(xué)習(xí)使用Image組件時(shí),用到了本地圖片,本地圖片必須在pubspec.yaml中進(jìn)行配置:


          1.2. JSON讀取解析

          JSON資源讀取

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

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

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

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

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

          JSON字符串轉(zhuǎn)化

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

          我們可以通過dart:convert包中的json.decode方法將其進(jìn)行轉(zhuǎn)化

          代碼如下:

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

          對(duì)象Model定義

          將JSON轉(zhuǎn)成了List和Map類型后,就可以將List中的一個(gè)個(gè)Map轉(zhuǎn)成Model對(duì)象,所以我們需要定義自己的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解析代碼

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

          這里我單獨(dú)創(chuàng)建了一個(gè)anchor.dart的文件,在其中定義了所有的相關(guān)代碼:

          • 之后外界只需要調(diào)用我內(nèi)部的getAnchors就可以獲取到解析后的數(shù)據(jù)了
          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.轉(zhuǎn)成List或Map類型
            final jsonResult=json.decode(jsonString);
          
            // 3.遍歷List,并且轉(zhuǎn)成Anchor對(duì)象放到另一個(gè)List中
            List<Anchor> anchors=new List();
            for (Map<String, dynamic> map in jsonResult) {
              anchors.add(Anchor.withMap(map));
            }
            return anchors;
          }

          二. ListView組件

          移動(dòng)端數(shù)據(jù)量比較大時(shí),我們都是通過列表來進(jìn)行展示的,比如商品數(shù)據(jù)、聊天列表、通信錄、朋友圈等。

          在Android中,我們可以使用ListView或RecyclerView來實(shí)現(xiàn),在iOS中,我們可以通過UITableView來實(shí)現(xiàn)。

          在Flutter中,我們也有對(duì)應(yīng)的列表Widget,就是ListView。

          2.1. ListView基礎(chǔ)

          2.1.1 ListView基本使用

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

          一種最簡(jiǎn)單的使用方式是直接將所有需要排列的子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("人的一切痛苦,本質(zhì)上都是對(duì)自己無(wú)能的憤怒。", style: textStyle),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text("人活在世界上,不可以有偏差;而且多少要費(fèi)點(diǎn)勁兒,才能把自己保持到理性的軌道上。", style: textStyle),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text("我活在世上,無(wú)非想要明白些道理,遇見些有趣的事。", style: textStyle),
                  )
                ],
              );
            }
          }

          2.2.2. ListTile的使用

          在開發(fā)中,我們經(jīng)常見到一種列表,有一個(gè)圖標(biāo)或圖片(Icon),有一個(gè)標(biāo)題(Title),有一個(gè)子標(biāo)題(Subtitle),還有尾部一個(gè)圖標(biāo)(Icon)。

          這個(gè)時(shí)候,我們可以使用ListTile來實(shí)現(xiàn):

          class MyHomeBody extends StatelessWidget {
          
            @override
            Widget build(BuildContext context) {
              return ListView(
                children: <Widget>[
                  ListTile(
                    leading: Icon(Icons.people, size: 36,),
                    title: Text("聯(lián)系人"),
                    subtitle: Text("聯(lián)系人信息"),
                    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. 垂直方向滾動(dòng)

          我們可以通過設(shè)置 scrollDirection 參數(shù)來控制視圖的滾動(dòng)方向。

          我們通過下面的代碼實(shí)現(xiàn)一個(gè)水平滾動(dòng)的內(nèi)容:

          • 這里需要注意,我們需要給Container設(shè)置width,否則它是沒有寬度的,就不能正常顯示。
          • 或者我們也可以給ListView設(shè)置一個(gè)itemExtent,該屬性會(huì)設(shè)置滾動(dòng)方向上每個(gè)item所占據(jù)的寬度。
          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

          通過構(gòu)造函數(shù)中的children傳入所有的子Widget有一個(gè)問題:默認(rèn)會(huì)創(chuàng)建出所有的子Widget。

          但是對(duì)于用戶來說,一次性構(gòu)建出所有的Widget并不會(huì)有什么差異,但是對(duì)于我們的程序來說會(huì)產(chǎn)生性能問題,而且會(huì)增加首屏的渲染時(shí)間。

          我們可以ListView.build來構(gòu)建子Widget,提供性能。

          2.2.1. ListView.build基本使用

          ListView.build適用于子Widget比較多的場(chǎng)景,該構(gòu)造函數(shù)將創(chuàng)建子Widget交給了一個(gè)抽象的方法,交給ListView進(jìn)行管理,ListView會(huì)在真正需要的時(shí)候去創(chuàng)建子Widget,而不是一開始就全部初始化好。

          該方法有兩個(gè)重要參數(shù):

          • itemBuilder:列表項(xiàng)創(chuàng)建的方法。當(dāng)列表滾動(dòng)到對(duì)應(yīng)位置的時(shí)候,ListView會(huì)自動(dòng)調(diào)用該方法來創(chuàng)建對(duì)應(yīng)的子Widget。類型是IndexedWidgetBuilder,是一個(gè)函數(shù)類型。
          • itemCount:表示列表項(xiàng)的數(shù)量,如果為空,則表示ListView為無(wú)限列表。

          我們還是通過一個(gè)簡(jiǎn)單的案例來認(rèn)識(shí)它:

          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("標(biāo)題$index"), subtitle: Text("詳情內(nèi)容$index"));
                }
              );
            }
          }


          2.2.2. ListView.build動(dòng)態(tài)數(shù)據(jù)

          在之前,我們搞了一個(gè)yz.json數(shù)據(jù),我們現(xiàn)在動(dòng)態(tài)的來通過JSON數(shù)據(jù)展示一個(gè)列表。

          思考:這個(gè)時(shí)候是否依然可以使用StatelessWidget:

          答案:不可以,因?yàn)楫?dāng)前我們的數(shù)據(jù)是異步加載的,剛開始界面并不會(huì)展示數(shù)據(jù)(沒有數(shù)據(jù)),后面從JSON中加載出來數(shù)據(jù)(有數(shù)據(jù))后,再次展示加載的數(shù)據(jù)。

          • 這里是有狀態(tài)的變化的,從無(wú)數(shù)據(jù),到有數(shù)據(jù)的變化。
          • 這個(gè)時(shí)候,我們需要使用StatefulWidget來管理組件。

          展示代碼如下:

          	import 'model/anchor.dart';
          
          ...省略中間代碼
          class MyHomeBody extends StatefulWidget {
            @override
            State<StatefulWidget> createState() {
              return MyHomeBodyState();
            }
          }
          
          class MyHomeBodyState extends State<MyHomeBody> {
            List<Anchor> anchors=[];
          
            // 在初始化狀態(tài)的方法中加載數(shù)據(jù)
            @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可以生成列表項(xiàng)之間的分割器,它除了比ListView.builder多了一個(gè)separatorBuilder參數(shù),該參數(shù)是一個(gè)分割器生成器。

          下面我們看一個(gè)例子:奇數(shù)行添加一條藍(lán)色下劃線,偶數(shù)行添加一條紅色下劃線:

          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("聯(lián)系人${index+1}"),
                    subtitle: Text("聯(lián)系人電話${index+1}"),
                  );
                },
                separatorBuilder: (BuildContext context, int index) {
                  return index % 2==0 ? redColor : blueColor;
                },
                itemCount: 100
              );
            }
          }


          三. GridView組件

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

          在Flutter中我們可以使用GridView來實(shí)現(xiàn),使用方式和ListView也比較相似。

          3.1. GridView構(gòu)造函數(shù)

          我們先學(xué)習(xí)GridView構(gòu)造函數(shù)的使用方法

          一種使用GridView的方式就是使用構(gòu)造函數(shù)來創(chuàng)建,和ListView對(duì)比有一個(gè)特殊的參數(shù):gridDelegate

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

          SliverGridDelegateWithFixedCrossAxisCount

          SliverGridDelegateWithFixedCrossAxisCount({
            @required double crossAxisCount, // 交叉軸的item個(gè)數(shù)
            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(),
              );
            }
          }


          前面兩種方式也可以不設(shè)置delegate

          可以分別使用:GridView.count構(gòu)造函數(shù)和GridView.extent構(gòu)造函數(shù)實(shí)現(xiàn)相同的效果,這里不再贅述。

          3.2. GridView.build

          和ListView一樣,使用構(gòu)造函數(shù)會(huì)一次性創(chuàng)建所有的子Widget,會(huì)帶來性能問題,所以我們可以使用GridView.build來交給GridView自己管理需要?jiǎng)?chuàng)建的子Widget。

          我們直接使用之前的數(shù)據(jù)來進(jìn)行代碼演練:

          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

          我們考慮一個(gè)這樣的布局:一個(gè)滑動(dòng)的視圖中包括一個(gè)標(biāo)題視圖(HeaderView),一個(gè)列表視圖(ListView),一個(gè)網(wǎng)格視圖(GridView)。

          我們?cè)趺纯梢宰屗鼈冏龅浇y(tǒng)一的滑動(dòng)效果呢?使用前面的滾動(dòng)是很難做到的。

          Flutter中有一個(gè)可以完成這樣滾動(dòng)效果的Widget:CustomScrollView,可以統(tǒng)一管理多個(gè)滾動(dòng)視圖。

          在CustomScrollView中,每一個(gè)獨(dú)立的,可滾動(dòng)的Widget被稱之為Sliver。

          補(bǔ)充:Sliver可以翻譯成裂片、薄片,你可以將每一個(gè)獨(dú)立的滾動(dòng)視圖當(dāng)做一個(gè)小裂片。

          4.1. Slivers的基本使用

          因?yàn)槲覀冃枰押芏嗟腟liver放在一個(gè)CustomScrollView中,所以CustomScrollView有一個(gè)slivers屬性,里面讓我們放對(duì)應(yīng)的一些Sliver:

          • SliverList:類似于我們之前使用過的ListView;
          • SliverFixedExtentList:類似于SliverList只是可以設(shè)置滾動(dòng)的高度;
          • SliverGrid:類似于我們之前使用過的GridView;
          • SliverPadding:設(shè)置Sliver的內(nèi)邊距,因?yàn)榭赡芤獑为?dú)給Sliver設(shè)置內(nèi)邊距;
          • SliverAppBar:添加一個(gè)AppBar,通常用來作為CustomScrollView的HeaderView;
          • SliverSafeArea:設(shè)置內(nèi)容顯示在安全區(qū)域(比如不讓齊劉海擋住我們的內(nèi)容)

          我們簡(jiǎn)單演示一下: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
                    ),
                  ),
                ],
              );
            }
          }
          


          五. 監(jiān)聽滾動(dòng)事件

          對(duì)于滾動(dòng)的視圖,我們經(jīng)常需要監(jiān)聽它的一些滾動(dòng)事件,在監(jiān)聽到的時(shí)候去做對(duì)應(yīng)的一些事情。

          比如視圖滾動(dòng)到底部時(shí),我們可能希望做上拉加載更多;

          比如滾動(dòng)到一定位置時(shí)顯示一個(gè)回到頂部的按鈕,點(diǎn)擊回到頂部的按鈕,回到頂部;

          比如監(jiān)聽滾動(dòng)什么時(shí)候開始,什么時(shí)候結(jié)束;

          在Flutter中監(jiān)聽滾動(dòng)相關(guān)的內(nèi)容由兩部分組成:ScrollController和ScrollNotification。

          5.1. ScrollController

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

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

          另外,通常情況下,我們會(huì)根據(jù)滾動(dòng)的位置來改變一些Widget的狀態(tài)信息,所以ScrollController通常會(huì)和StatefulWidget一起來使用,并且會(huì)在其中控制它的初始化、監(jiān)聽、銷毀等事件。

          我們來做一個(gè)案例,當(dāng)滾動(dòng)到1000位置的時(shí)候,顯示一個(gè)回到頂部的按鈕:

          • jumpTo(double offset)、animateTo(double offset,...):這兩個(gè)方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時(shí)會(huì)執(zhí)行一個(gè)動(dòng)畫,而前者不會(huì)。
          • ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動(dòng)事件。
          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();
              
              // 監(jiān)聽滾動(dòng)
              _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

          如果我們希望監(jiān)聽什么時(shí)候開始滾動(dòng),什么時(shí)候結(jié)束滾動(dòng),這個(gè)時(shí)候我們可以通過NotificationListener。

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

          案例: 列表滾動(dòng), 并且在中間顯示滾動(dòng)進(jìn)度

          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.判斷監(jiān)聽事件的類型
                  if (notification is ScrollStartNotification) {
                    print("開始滾動(dòng).....");
                  } else if (notification is ScrollUpdateNotification) {
                    // 當(dāng)前滾動(dòng)的位置和總長(zhǎng)度
                    final currentPixel=notification.metrics.pixels;
                    final totalPixel=notification.metrics.maxScrollExtent;
                    double progress=currentPixel / totalPixel;
                    setState(() {
                      _progress=(progress * 100).toInt();
                    });
                    print("正在滾動(dòng):${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
                  } else if (notification is ScrollEndNotification) {
                    print("結(jié)束滾動(dòng)....");
                  }
                  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(侵權(quán)刪除)

          端開發(fā)"酷炫技巧--虛擬滾動(dòng)。

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

          虛擬滾動(dòng)也稱為窗口化滾動(dòng),是一種優(yōu)化技術(shù),用于在固定大小的界面中顯示大量?jī)?nèi)容,它只渲染可視區(qū)域的內(nèi)容而不是全部。

          為什么使用虛擬滾動(dòng)?

          虛擬滾動(dòng)有很多優(yōu)點(diǎn)。首先它可以顯著提高網(wǎng)頁(yè)性能,特別是對(duì)于需要顯示大量數(shù)據(jù)的應(yīng)用程序。其次它可以提高用戶體驗(yàn),因?yàn)橛脩舨恍枰却袃?nèi)容都加載完畢才能開始瀏覽。

          如何實(shí)現(xiàn)虛擬滾動(dòng)?實(shí)現(xiàn)虛擬滾動(dòng)主要需要兩步:

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

          ·然后,需要使用CSS來定位和呈現(xiàn)這些內(nèi)容。

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

          虛擬滾動(dòng)是一個(gè)非常強(qiáng)大的前端技術(shù),可以幫助你優(yōu)化網(wǎng)頁(yè)性能并提高用戶體驗(yàn)。

          希望這個(gè)視頻能幫助你了解并實(shí)現(xiàn)虛擬滾動(dòng)!

          | 大澈


          大家好,我是大澈!

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

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

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


          ONE

          需求分析,問題描述

          一、需求

          一個(gè)可以滾動(dòng)的菜單,為它添加一個(gè)可以下拉滾動(dòng)的提示。要求滾動(dòng)到菜單最底部時(shí),隱藏下拉滾動(dòng)的提示,否則讓其一直顯示。

          二、問題

          1、如何實(shí)現(xiàn)滾動(dòng)條效果?

          2、如何判斷是否滾動(dòng)到底部?


          TWO

          解決問題,答案速覽

          實(shí)現(xiàn)代碼如下,復(fù)制粘貼即可直接使用。

          代碼中滾動(dòng)條的實(shí)現(xiàn)使用了element的el-scrollbar組件。組件中包裹的第一個(gè)div,指的是需要滾動(dòng)的視圖。組件中包裹的第二個(gè)div,指的是下拉滾動(dòng)提示的圖標(biāo),這里根據(jù)需求進(jìn)行設(shè)置,可以更換靜態(tài)的或者那種閃爍跳躍的動(dòng)態(tài)提示圖標(biāo)。

          // 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、邏輯
          // 滾動(dòng)條事件
          const handleScroll=(val)=> {
          // 防止Scrollbar實(shí)例為空
          if (!myScrollbar.value) {
          return
          }
          
          // 判斷是否滾動(dòng)到底部
          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) {
          // 滾動(dòng)到底部的處理邏輯
          isShowIcon.value=false
          } else {
          // 非滾動(dòng)到底部的處理邏輯
          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

          問題解析,知識(shí)總結(jié)

          一、如何實(shí)現(xiàn)滾動(dòng)條效果?

          實(shí)現(xiàn)滾動(dòng)條效果有兩種實(shí)現(xiàn)方式:利用css的overflow: scroll屬性、利用element的el-scrollbar組件。

          1、overflow: scroll屬性

          在div元素上添加 overflow-y: scroll; css屬性,就能顯示出一個(gè)滾動(dòng)條,如果不指定是x或y軸,則水平和垂直都會(huì)出現(xiàn)滾動(dòng)條。

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

          2、el-scrollbar組件

          一般在vue項(xiàng)目中,我們可直接使用element的el-scrollbar組件,因?yàn)楣俜綖槲覀兲峁┝嗽S多API,以及各種適配優(yōu)化。

          el-scrollbar組件的屬性如下:

          el-scrollbar組件的事件如下:

          el-scrollbar組件的實(shí)例屬性如下:


          二、如何判斷是否滾動(dòng)到底部?

          這里判斷是否滾動(dòng)到底部的關(guān)鍵在于scrollTop+clientHeight是否等于scrollHeight的值。只有當(dāng)滾動(dòng)的距離+可視區(qū)域的高度,與scrollHeight相等時(shí),才證明滾動(dòng)條滾動(dòng)到了底部。

          同樣的,如果scrollHeight與可視區(qū)域的高度直接就相等時(shí),又說明元素不可以滾動(dòng),也就沒有滾動(dòng)條。這一點(diǎn)在有此需求時(shí),可以進(jìn)行實(shí)用。

          元素的幾個(gè)寬高屬性釋義如下:


          - END -


          主站蜘蛛池模板: 一区二区三区91| 少妇精品无码一区二区三区| 一区二区视频传媒有限公司| 日韩一区二区电影| 韩国精品福利一区二区三区 | 精品无码综合一区二区三区 | 日韩成人无码一区二区三区| 国产一区三区二区中文在线| 日韩一区二区三区电影在线观看| 亚洲av无码不卡一区二区三区| 无码人妻一区二区三区在线水卜樱| 国产精品亚洲一区二区麻豆 | 无码人妻精品一区二| 亚洲av永久无码一区二区三区| 久久99精品波多结衣一区| 亚洲国产成人精品久久久国产成人一区二区三区综 | 天美传媒一区二区三区| 日韩av无码一区二区三区| 99久久精品费精品国产一区二区| 国产大秀视频一区二区三区| 亚洲av日韩综合一区二区三区 | 一区二区三区无码被窝影院| 无码人妻精品一区二区三区66 | 熟女少妇精品一区二区| 人成精品视频三区二区一区| 日韩一区二区三区免费体验| 久久久久人妻精品一区三寸| 竹菊影视欧美日韩一区二区三区四区五区 | 白丝爆浆18禁一区二区三区| 国产一区二区不卡老阿姨| 国产微拍精品一区二区| 国产怡春院无码一区二区| 国产av一区最新精品| 婷婷亚洲综合一区二区| 免费萌白酱国产一区二区| 国产三级一区二区三区| 乱中年女人伦av一区二区| 成人区人妻精品一区二区不卡网站| 538国产精品一区二区在线| 色妞AV永久一区二区国产AV| 无码一区二区三区|