【Flutter】可滾動組件之ListView


前言

它可以沿一個方向線性排布所有子組件,並且它也可以支持基於Sliver的延遲構建模型。

接口描述

ListView({
    Key key,
    // 可滾動widget公共參數
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    EdgeInsetsGeometry padding,
    // ListView各個構造函數的共同參數
    // 該參數如果不為null,則會強制children的“長度”為itemExtent的值;這里的“長度”是指滾動方向上子組件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子組件的高度;如果滾動方向為水平方向,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會更高效,這是因為指定itemExtent后,滾動系統可以提前知道列表的長度,而無需每次構建子組件時都去再計算一下,尤其是在滾動位置頻繁變化時(滾動系統需要頻繁去計算列表高度)。
    this.itemExtent,
    // 該屬性表示是否根據子組件的總長度來設置ListView的長度,默認值為false 。默認情況下,ListView的會在滾動方向盡可能多的占用空間。當ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true。
    bool shrinkWrap = false,
    // 該屬性表示是否將列表項(子組件)包裹在AutomaticKeepAlive 組件中;典型地,在一個懶加載列表中,如果將列表項包裹在AutomaticKeepAlive中,在該列表項滑出視口時它也不會被GC(垃圾回收),它會使用KeepAliveNotification來保存其狀態。如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
    bool addAutomaticKeepAlives = true,
    // 該屬性表示是否將列表項(子組件)包裹在RepaintBoundary組件中。當可滾動組件滾動時,將列表項包裹在RepaintBoundary中可以避免列表項重繪,但是當列表項重繪的開銷非常小(如一個顏色塊,或者一個較短的文本)時,不添加RepaintBoundary反而會更高效。和addAutomaticKeepAlive一樣,如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    // 子widget列表
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  })

代碼示例

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';

// ListView 默認構造函數
/* 默認構造函數有一個children參數,它接受一個Widget列表(List)。這種方式適合只有少量的子組件的情況,因為這種方式需要將所有children都提前創建好(這需要做大量工作),而不是等到子widget真正顯示的時候再創建,也就是說通過默認構造函數構建的ListView沒有應用基於Sliver的懶加載模型。實際上通過此方式創建的ListView和使用SingleChildScrollView+Column的方式沒有本質的區別。*/
class ListViewTest1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView 默認構造函數'),
      ),
      body: ListView(
        shrinkWrap: true,
        padding: const EdgeInsets.all(20.0),
        children: <Widget>[
          const Text('I\'m haha'),
          const Text('biubiubiu`'),
          const Text('qiuqiuqiu`'),
          const Text('dindindin`'),
          const Text('I\'m haha'),
          const Text('biubiubiu`'),
          const Text('qiuqiuqiu`'),
          const Text('dindindin`'),
          const Text('I\'m haha'),
          const Text('biubiubiu`'),
          const Text('qiuqiuqiu`'),
          const Text('dindindin`'),
        ],
      ),
    );
  }
}


// ListView.builder
/* ListView.builder適合列表項比較多(或者無限)的情況,因為只有當子組件真正顯示的時候才會被創建,也就說通過該構造函數創建的ListView是支持基於Sliver的懶加載模型的。*/
class ListViewTest2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView 默認構造函數'),
      ),
      body: ListView.builder(
          // 列表項的數量,如果為null,則為無限列表
          itemCount: 100,
          itemExtent: 50.0,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("$index"),);
          }
      )
    );
  }
}


// ListView.separated
/*ListView.separated可以在生成的列表項之間添加一個分割組件,它比ListView.builder多了一個separatorBuilder參數,該參數是一個分割組件生成器。 */
class ListViewTest3 extends StatelessWidget {

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


// 實例:無限加載列表
class InfiniteListView extends StatefulWidget {
  @override
  _InfiniteListViewState createState() => _InfiniteListViewState();
}

class _InfiniteListViewState extends State<InfiniteListView> {
  // 表尾標記
  static const loadingTag = "##loadding##";
  var _words = <String>[loadingTag];

  @override
  void initState() {
    super.initState();
    _retrieveData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('ListView.separated'),
        ),
        body: ListView.separated(
            itemBuilder: (context, index) {
              // 如果到了表尾
              if (_words[index] == loadingTag) {
                // 不足100條,繼續獲取數據
                if (_words.length - 1 < 100) {
                  // 獲取數據
                  _retrieveData();
                  // 加載時顯示loading
                  return Container(
                    padding: const EdgeInsets.all(16.0),
                    alignment: Alignment.center,
                    child: SizedBox(
                      width: 24.0,
                      height: 24.0,
                      child: CircularProgressIndicator(strokeWidth: 2.0,),
                    ),
                  );
                } else {
                  // 已經加載了100條數據,不再獲取數據
                  return Container(
                    alignment: Alignment.center,
                    padding: EdgeInsets.all(16.0),
                    child: Text('已經到我的底線了ฅʕ•̫͡•ʔฅ'),
                  );
                }
              }
              // 顯示單詞列表項
              return ListTile(title: Text(_words[index]),);
            },
            separatorBuilder: (context, index) => Divider(height: .0,),
            itemCount: _words.length,
        ),
    );
  }

  // 模擬從數據源異步獲取數據
  void _retrieveData() {
    Future.delayed(Duration(seconds: 2)).then((e) {
      _words.insertAll(
          _words.length - 1,
          // 每次生成20個單詞
          generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
      );
      setState(() {
        // 重新構建列表
      });
    });
  }
}

總結

不同的構造函數對應了不同的列表項生成模型,如果需要自定義列表項生成模型,可以通過ListView.custom來自定義,它需要實現一個SliverChildDelegate用來給ListView生成列表項組件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM