亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Flutter之?ListView組件使用示例詳解

 更新時間:2022年10月31日 16:32:07   作者:風雨_83  
這篇文章主要為大家介紹了Flutter之?ListView組件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

ListView的默認構(gòu)造函數(shù)定義

ListView是最常用的可滾動組件之一,它可以沿一個方向線性排布所有子組件,并且它也支持列表項懶加載(在需要時才會創(chuàng)建)。我們看看ListView的默認構(gòu)造函數(shù)定義:

ListView({
  ...  
  //可滾動widget公共參數(shù)
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController? controller,
  bool? primary,
  ScrollPhysics? physics,
  EdgeInsetsGeometry? padding,
  //ListView各個構(gòu)造函數(shù)的共同參數(shù)  
  double? itemExtent,
  Widget? prototypeItem, //列表項原型,后面解釋
  bool shrinkWrap = false,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double? cacheExtent, // 預渲染區(qū)域長度
  //子widget列表
  List<Widget> children = const <Widget>[],
})

上面參數(shù)分為兩組:第一組是可滾動組件的公共參數(shù);第二組是ListView各個構(gòu)造函數(shù)ListView有多個構(gòu)造函數(shù)的共同參數(shù),我們重點來看看這些參數(shù),:

  • itemExtent:該參數(shù)如果不為null,則會強制children的“長度”為itemExtent的值;這里的“長度”是指滾動方向上子組件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子組件的高度;如果滾動方向為水平方向,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會有更好的性能,這是因為指定itemExtent后,滾動系統(tǒng)可以提前知道列表的長度,而無需每次構(gòu)建子組件時都去再計算一下,尤其是在滾動位置頻繁變化時(滾動系統(tǒng)需要頻繁去計算列表高度)。
  • prototypeItem:如果我們知道列表中的所有列表項長度都相同但不知道具體是多少,這時我們可以指定一個列表項,該列表項被稱為 prototypeItem(列表項原型)。指定 prototypeItem 后,可滾動組件會在 layout 時計算一次它延主軸方向的長度,這樣也就預先知道了所有列表項的延主軸方向的長度,所以和指定 itemExtent 一樣,指定 prototypeItem 會有更好的性能。注意,itemExtent 和prototypeItem 互斥,不能同時指定它們。
  • shrinkWrap:該屬性表示是否根據(jù)子組件的總長度來設置ListView的長度,默認值為false 。默認情況下,ListView的會在滾動方向盡可能多的占用空間。當ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true。
  • addRepaintBoundaries:該屬性表示是否將列表項(子組件)包裹在RepaintBoundary組件中。RepaintBoundary 讀者可以先簡單理解為它是一個”繪制邊界“,將列表項包裹在RepaintBoundary中可以避免列表項不必要的重繪,但是當列表項重繪的開銷非常?。ㄈ缫粋€顏色塊,或者一個較短的文本)時,不添加RepaintBoundary反而會更高效。如果列表項自身來維護是否需要添加繪制邊界組件,則此參數(shù)應該指定為 false。

注意:上面這些參數(shù)并非ListView特有,其它可滾動組件也可能會擁有這些參數(shù),它們的含義是相同的。

默認構(gòu)造函數(shù)

默認構(gòu)造函數(shù)有一個children參數(shù),它接受一個Widget列表(List)。這種方式適合只有少量的子組件數(shù)量已知且比較少的情況,反之則應該使用ListView.builder 按需動態(tài)構(gòu)建列表項。

注意,雖然這種方式將所有children一次性傳遞給 ListView,但子組件)仍然是在需要時才會加載(build(如有)、布局、繪制),也就是說通過默認構(gòu)造函數(shù)構(gòu)建的 ListView 也是基于 Sliver 的列表懶加載模型。

下面是一個例子:

可以看到,雖然使用默認構(gòu)造函數(shù)創(chuàng)建的列表也是懶加載的,但我們還是需要提前將 Widget 創(chuàng)建好,等到真正需要加載的時候才會對 Widget 進行布局和繪制。 

shrinkWrap: true 效果,ListView根據(jù)子視圖計算高度:

shrinkWrap: false的效果,ListView的會在滾動方向盡可能多的占用空間。

ListView.builder

ListView.builder適合列表項比較多或者列表項不確定的情況,下面看一下ListView.builder的核心參數(shù)列表

    ListView.builder({
  // ListView公共參數(shù)已省略  
  ...
  required IndexedWidgetBuilder itemBuilder,
  int itemCount,
  ...
})

itemBuilder:它是列表項的構(gòu)建器,類型為IndexedWidgetBuilder,返回值為一個widget。當列表滾動到具體的index位置時,會調(diào)用該構(gòu)建器構(gòu)建列表項。

itemCount:列表項的數(shù)量,如果為null,則為無限列表。

下面看一個例子:

      return ListView.builder(
        itemCount: 100,
        itemExtent: 50,//強制高度為50.0
        itemBuilder: (BuildContext context,int index){
      return ListTile(
        leading: const Icon(Icons.person),
        title: Text('$index'),
      );
    });

運行效果“

 ListView.separated

ListView.separated可以在生成的列表項之間添加一個分割組件,它比ListView.builder多了一個separatorBuilder參數(shù),該參數(shù)是一個分割組件生成器。

下面我們看一個例子:奇數(shù)行添加一條藍色下劃線,偶數(shù)行添加一條綠色下劃線。

 //下劃線widget預定義以供復用。
    Widget divider1=Divider(color: Colors.blue,);
    Widget divider2=Divider(color: Colors.green);
    return ListView.separated(
      //列表項構(gòu)造器
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        },
        //分割器構(gòu)造器
        separatorBuilder: (BuildContext context, int index) {
          return index%2==0?divider1:divider2;
        },
        itemCount: 100);
  }    

運行效果:

固定高度列表

前面說過,給列表指定 itemExtentprototypeItem 會有更高的性能,所以當我們知道列表項的高度都相同時,強烈建議指定 itemExtentprototypeItem 。

下面看一個示例:

ListView.builder(
        prototypeItem: const ListTile(
          title: Text('1'),
        ),
        itemBuilder: (BuildContext context, int index) {
          return Center(child: Text('$index'),);
        }); 

因為列表項都是一個 ListTile,高度相同,但是我們不知道 ListTile 的高度是多少,所以指定了prototypeItem ,每個item高度根據(jù)prototypeItem來定。

ListView 原理

ListView 內(nèi)部組合了 Scrollable、Viewport 和 Sliver,需要注意:

  • ListView 中的列表項組件都是 RenderBox,并不是 Sliver, 這個一定要注意。
  • 一個 ListView 中只有一個Sliver,對列表項進行按需加載的邏輯是 Sliver 中實現(xiàn)的。
  • ListView 的 Sliver 默認是 SliverList,如果指定了 itemExtent ,則會使用 SliverFixedExtentList;如果 prototypeItem 屬性不為空,則會使用 SliverPrototypeExtentList,無論是是哪個,都實現(xiàn)了子組件的按需加載模型。

實例:無限加載列表

假設我們要從數(shù)據(jù)源異步分批拉取一些數(shù)據(jù),然后用ListView展示,當我們滑動到列表末尾時,判斷是否需要再去拉取數(shù)據(jù),如果是,則去拉取,拉取過程中在表尾顯示一個loading,拉取成功后將數(shù)據(jù)插入列表;如果不需要再去拉取,則在表尾提示"沒有更多"。

代碼如下:

 class MyListViewPage extends StatefulWidget {
  const MyListViewPage({Key? key}) : super(key: key);
  @override
  _MyListViewPageState createState() => _MyListViewPageState();
}
class _MyListViewPageState extends State<MyListViewPage> {
  static const loadingTag = "##loading##"; //表尾標記
  final _words = <String>[loadingTag];
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _retrieveData();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar('ListView'),
      body: Container(
        color: Colors.black.withOpacity(0.2),
        child: _buildInfinite(),
      ),
    );
  }
  _buildDefault() {
    return ListView(
      shrinkWrap: false,
      padding: const EdgeInsets.all(20.0),
      children: const <Widget>[
        Text('I\'m dedicating every day to you'),
        Text('Domestic life was never quite my style'),
        Text('When you smile, you knock me out, I fall apart'),
        Text('And I thought I was so smart'),
      ],
    );
  }
  _buildBuilder() {
    return ListView.builder(
        itemCount: 100,
        itemExtent: 50, //強制高度為50.0
        itemBuilder: (BuildContext context, int index) {
          return ListTile(
            leading: const Icon(Icons.person),
            title: Text('$index'),
          );
        });
  }
  _buildSeparated() {
    //下劃線widget預定義以供復用。
    Widget divider1 = Divider(
      color: Colors.blue,
    );
    Widget divider2 = Divider(color: Colors.green);
    return ListView.separated(
        scrollDirection: Axis.vertical,
        //列表項構(gòu)造器
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        },
        //分割器構(gòu)造器
        separatorBuilder: (BuildContext context, int index) {
          return index % 2 == 0 ? divider1 : divider2;
        },
        itemCount: 100);
  }
  _buildExtent() {
    return ListView.builder(
        prototypeItem: const ListTile(
          title: Text('1'),
        ),
        itemBuilder: (BuildContext context, int index) {
          //LayoutLogPrint是一個自定義組件,在布局時可以打印當前上下文中父組件給子組件的約束信息
          return Center(child: Text('$index'),);
        });
  }
   //無限加載列表
  _buildInfinite(){
     return ListView.separated(
         itemBuilder: (context,index){
           //如果到了表尾
           if(_words[index] ==loadingTag) {
             //如果數(shù)據(jù)不足100條
             if (_words.length <= 100) {
               //拉去數(shù)據(jù)
               _retrieveData();
               //加載顯示loading
               return Container(
                 padding: const EdgeInsets.all(16),
                 alignment: Alignment.center,
                 child: const SizedBox(
                   width: 24,
                   height: 24,
                   child: CircularProgressIndicator(strokeWidth: 2,),
                 ),
               );
             } else {
               //已經(jīng)加載100不再獲取數(shù)據(jù)
               return Container(
                 alignment: Alignment.center,
                 padding: const EdgeInsets.all(16),
                 child: const Text('沒有更多了',
                   style: TextStyle(color: Colors.grey),),
               );
             }
           }
           return ListTile(title: Text(_words[index]),);
         },
         separatorBuilder:(context,index)=>Divider(height:1,color: Colors.black,),
         itemCount: _words.length);
  }
  void _retrieveData(){
    Future.delayed(Duration(seconds: 5)).then((value){
      setState(() {
        _words.insertAll(_words.length-1,
            //每次生成20個單詞
            List.generate(20, (index){
              return 'words $index';
            }));
      });
    });
  }
}    

運營效果:

添加固定列表頭

很多時候我們需要給列表添加一個固定表頭,比如我們想實現(xiàn)一個商品列表,需要在列表頂部添加一個“商品列表”標題,期望的效果如圖 6-6 所示:

我們按照之前經(jīng)驗,寫出如下代碼:

@override
Widget build(BuildContext context) {
  return Column(children: <Widget>[
    ListTile(title:Text("商品列表")),
    ListView.builder(itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("$index"));
    }),
  ]);
}    

然后運行,發(fā)現(xiàn)并沒有出現(xiàn)我們期望的效果,相反觸發(fā)了一個異常;

Vertical viewport was given unbounded height.

======== Exception caught by rendering library =====================================================
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. In this case, consider using a Column instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size the height of the viewport to the sum of the heights of its children.    

從異常信息中我們可以看到是因為ListView高度邊界無法確定引起,所以解決的辦法也很明顯,我們需要給ListView指定邊界,我們通過SizedBox指定一個列表高度看看是否生效:

Column(
      children: [
        ListTile(title: Text('商品列表'),),
        SizedBox(height: 400,//指定高度
        child: ListView.builder(itemBuilder: (BuildContext context,int index){
          return ListTile(title: Text('$index'),);
        }),
        )
      ],
    )    

可以看到,現(xiàn)在沒有觸發(fā)異常并且列表已經(jīng)顯示出來了,但是我們的手機屏幕高度要大于 400,所以底部會有一些空白。那如果我們要實現(xiàn)列表鋪滿除表頭以外的屏幕空間應該怎么做?直觀的方法是我們?nèi)討B(tài)計算,用屏幕高度減去狀態(tài)欄、導航欄、表頭的高度即為剩余屏幕高度,代碼如下:

... //省略無關代碼
SizedBox(
  //Material設計規(guī)范中狀態(tài)欄、導航欄、ListTile高度分別為24、56、56 
  height: MediaQuery.of(context).size.height-24-56-56,
  child: ListView.builder(itemBuilder: (BuildContext context, int index) {
    return ListTile(title: Text("$index"));
  }),
)
...      

可以看到,我們期望的效果實現(xiàn)了,但是這種方法并不優(yōu)雅,如果頁面布局發(fā)生變化,比如表頭布局調(diào)整導致表頭高度改變,那么剩余空間的高度就得重新計算。那么有什么方法可以自動拉伸ListView以填充屏幕剩余空間的方法嗎?當然有!答案就是Flex。在彈性布局中,可以使用Expanded自動拉伸組件大小,并且我們也說過Column是繼承自Flex的,所以我們可以直接使用Column + Expanded來實現(xiàn),代碼如下:

 @override
Widget build(BuildContext context) {
  return Column(children: <Widget>[
    ListTile(title:Text("商品列表")),
    Expanded(
      child: ListView.builder(itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("$index"));
      }),
    ),
  ]);
}   

運行后,和上圖一樣,完美實現(xiàn)了!

總結(jié)

本節(jié)主要介紹了ListView 常用的的使用方式和要點,但并沒有介紹ListView.custom方法,它需要實現(xiàn)一個SliverChildDelegate 用來給 ListView 生成列表項組件,更多詳情請參考 API 文檔。

demo完整代碼:gitee.com/wywinstonwy…

以上就是Flutter之 ListView組件使用示例詳解的詳細內(nèi)容,更多關于Flutter之 ListView 組件的資料請關注腳本之家其它相關文章!

相關文章

最新評論