Flutter 滾動控制元件篇-->ListView

夜夕i 發表於 2019-10-04

Flutter 中的ListView是最常用的可滾動的元件之一,
可以沿一個方向線性排布所有子元件,並且它也支援基於Sliver的延遲構建模型。

基於Sliver的延遲構建

什麼是基於Sliver的延遲構建模型呢?

通常可滾動元件的子元件可能會非常多、佔用的總高度也會非常大;如果要一次性將子元件全部構建出將會非常昂貴!為此,Flutter中提出一個Sliver(中文為“薄片”的意思)概念,如果一個可滾動元件支援Sliver模型,那麼該滾動可以將子元件分成好多個“薄片”(Sliver),只有當Sliver出現在視口中時才會去構建它,這種模型也稱為“基於Sliver的延遲構建模型”。

原始碼示例

建構函式如下:

ListView({
	...  
	//可滾動元件的公共引數
	Axis scrollDirection = Axis.vertical,
	bool reverse = false,
	ScrollController controller,
	bool primary,
	ScrollPhysics physics,	
	EdgeInsetsGeometry padding,	
	
	//ListView各個建構函式的共同引數
	this.itemExtent,
	bool shrinkWrap = false,
	bool addAutomaticKeepAlives = true,
	bool addRepaintBoundaries = true,
	bool addSemanticIndexes = true,
	double cacheExtent,
	
	//子元件列表
	List<Widget> children = const <Widget>[],
	int semanticChildCount,
	DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
複製程式碼

屬性解釋

scrollDirection

決定子元件的滾動方向(排列方向),預設是垂直方向

scrollDirection:Axis.horizontal,水平方向
scrollDirection:Axis.vertical,垂直方向

reverse

決定滾動方向是否與閱讀方向一致

圖片載入失敗!

primary

當內容不足以滾動時,是否支援滾動;

值為true或者false,我試了一下,好像沒什麼卵用,不知道是理解錯了,還是怎麼的,先寫上吧

controller

此屬性接收一個ScrollController物件。ScrollController的主要作用是控制滾動位置和監聽滾動事件
有關ScrollController的使用及詳情,請參考Flutter 滾動控制元件篇-->滾動監聽及控制(ScrollController)

預設情況下,Widget樹中會有一個預設的PrimaryScrollController,如果子樹中的可滾動元件沒有顯式的指定controller,並且primary屬性值為true時(預設就為true),可滾動元件會使用這個預設的PrimaryScrollController。這種機制帶來的好處是父元件可以控制子樹中可滾動元件的滾動行為

physics

此屬性接受一個ScrollPhysics型別的物件,它決定可滾動元件如何響應使用者操作,比如使用者滑動完抬起手指後,繼續執行動畫;或者滑動到邊界時,如何顯示。

在iOS上會出現彈性效果,而在Android上會出現微光效果。

shrinkWrap

該屬性表示是否根據子元件的總長度來設定ListView的長度,預設值為false
預設情況下,ListView會在滾動方向儘可能多的佔用空間。當ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true,否則會報錯。

itemExtent

該引數如果不為null,則會強制children的“長度”為itemExtent的值;

這裡的“長度”是指滾動方向上子元件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子元件的高度;如果滾動方向為水平方向,則itemExtent就代表子元件的寬度。

addAutomaticKeepAlives

該屬性表示是否將列表項(子元件)包裹在AutomaticKeepAlive元件中;

在一個懶載入列表中,如果將列表項包裹在AutomaticKeepAlive中,在該列表項滑出視口時也不會被回收,它會使用KeepAliveNotification來儲存其狀態。如果列表項自己維護其KeepAlive狀態,那麼此引數必須置為false

addRepaintBoundaries

該屬性表示是否將列表項(子元件)包裹在RepaintBoundary元件中。
當可滾動元件滾動時,將列表項包裹在RepaintBoundary中可以避免列表項重繪,但是當列表項重繪的開銷非常小(如一個顏色塊,或者一個較短的文字)時,不新增RepaintBoundary反而會更高效。和addAutomaticKeepAlive一樣,如果列表項自己維護其KeepAlive狀態,那麼此引數必須置為false

addSemanticIndexes

該屬性表示是否把子控制元件包裝在IndexedSemantics裡,用來提供無障礙語義

cacheExtent

可見區域的前後會有一定高度的空間去快取子控制元件,當滑動時就可以迅速呈現

簡單的就是說,當你快要滑到載入資料的時候,他已經提前一步載入好了,等到你滑到的時候就會顯示出來,而不至於使用者滑到的時候還需要等待一會兒。

semanticChildCount

有含義的子控制元件的數量

如:ListView會用children的長度,而ListView.separated會用children長度的一半

children

這裡的children需要說一下,和別的元件裡的不一樣。

這裡的children引數,他就收一個列表,但是這種方式適合只有少量的子元件的情況。因為這種方式需要將所有children都提前建立好(這需要做大量工作),而不是等到子元件真正顯示的時候再建立,也就是說通過預設建構函式構建的ListView沒有應用基於Sliver的懶載入模型

再次強調,可滾動元件通過一個List來作為其children屬性時,只適用於子元件較少的情況,這是一個通用規律。

ListView.builder

上面的children只適合資料較少的情況下使用

ListView.builder則適合列表項比較多(或者無限)的情況下使用,因為只有當子元件真正顯示的時候才會被建立,也就說通過該建構函式建立的ListView是支援基於Sliver的懶載入模型的。

原始碼示例

建構函式如下:

ListView.builder({
  // ListView公共引數已省略  
  ...
  @required IndexedWidgetBuilder itemBuilder,
  int itemCount,
  ...
})
複製程式碼

屬性解釋

itemBuilder

它是列表項的構建器,型別為IndexedWidgetBuilder,返回值為一個widget(就是一個元件)。當列表滾動到具體的index位置時,會呼叫該構建器構建列表項,也就是所謂的基於Sliver的懶載入模型。

itemCount

該屬性表示列表項的數量,如果為null,則表示無限列表

注:可滾動元件的建構函式如果需要一個列表項Builder,那麼通過該建構函式構建的可滾動元件通常就是支援基於Sliver的懶載入模型的,反之則不支援,這是個一般規律。

程式碼示例:

ListView.builder(
	itemCount: 100,
	itemExtent: 50.0, //強制高度為50.0,如果這個值越來越小的話,那麼顯示的值是會重疊的
	itemBuilder: (BuildContext context, int index) {
	  return ListTile(title: Text("$index"));
})
複製程式碼

執行效果:

圖片載入失敗!

ListView.separated

ListView.separated可以在生成的列表項之間新增一個分割元件。

它比ListView.builder多了一個separatorBuilder引數,該引數是一個分割元件生成器。

程式碼示例:

在奇數行新增一條藍色下劃線,偶數行新增一條紅色下劃線。

import 'package:flutter/material.dart';

class CategoryPage extends StatefulWidget {
  @override
  _CategoryPageState createState() => _CategoryPageState();
}

class _CategoryPageState extends State<CategoryPage> {
  @override
  Widget build(BuildContext context) {
    //下劃線widget預定義以供複用。
    Widget Lineblue = Divider(color: Colors.blue);
    Widget Linered = Divider(color: Colors.red);
    return Scaffold(
        appBar: AppBar(
          title: Text(
            "ListView.separated",
            // style: TextStyle(color: Color(0xFF1E88E5)),
          ),
        ),
        body: ListView.separated(
          itemCount: 100,
          //列表項構造器
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("$index"));
          },
          //分割器構造器
          separatorBuilder: (BuildContext context, int index) {
            return index % 2 == 0 ? Lineblue : Linered;
          },
        ));
  }
}
複製程式碼

執行效果:

圖片載入失敗!

無限載入列表

假設我們要從資料來源非同步分批拉取一些資料,然後用ListView展示。
當我們滑動到列表末尾時,判斷是否需要再去拉取資料,如果是,則去拉取,拉取過程中在表尾顯示一個轉著的小圓圈,拉取成功後將資料插入列表;如果不需要再去拉取,則在表尾提示"沒有更多了"。

這裡我們需要安裝一個包english_words: ^3.1.5(在pubspec.yaml檔案中的dependencies下安裝),可以給我們自動的生成英語單詞

程式碼示例:

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

class CategoryPage extends StatefulWidget {
  @override
  _CategoryPageState createState() => _CategoryPageState();
}

class _CategoryPageState extends State<CategoryPage> {
  static const loadingTag = "##loading##"; //表尾標記
  var _words = <String>[loadingTag];

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

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemCount: _words.length,
      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(
                  "沒有更多了",
                  style: TextStyle(color: Colors.grey),
                ));
          }
        }
        //顯示單詞列表項
        return ListTile(title: Text(_words[index]));
      },
      separatorBuilder: (context, index) => Divider(height: .0),
    );
  }

  void _retrieveData() {
    Future.delayed(Duration(seconds: 2)).then((e) {
      _words.insertAll(
          _words.length - 1,
          //每次生成20個單詞
          generateWordPairs().take(20).map((e) => e.asPascalCase).toList());
      setState(() {
        //重新構建列表
      });
    });
  }
}
複製程式碼

_retrieveData()的功能是模擬從資料來源非同步獲取資料,
english_words包的generateWordPairs()方法可以每次生成20個單詞。

執行效果:

圖片載入失敗!

ListTile

這裡我們說一下ListTile
ListTile通常用於在 Flutter 中填充 ListView

原始碼示例:

建構函式如下:

const ListTile({
	Key key,
	this.leading,
	this.title,
	this.subtitle,
	this.trailing,
	this.isThreeLine = false,
	this.dense,
	this.contentPadding,
	this.enabled = true,
	this.onTap,
	this.onLongPress,
	this.selected = false,
})
複製程式碼

屬性解釋

title

title引數可以接受任何小元件,但通常是文字小元件

程式碼示例:

ListTile(
  title: Text('我喜歡你!'),
)
複製程式碼

subtitle

他是一個副標題,顯示在標題(title)下面較小的文字

程式碼示例:

ListTile(
  title: Text('我喜歡你!'),
  subtitle: Text('你喜歡我嗎?'),
)
複製程式碼

dense

使文字更小,並將所有內容打包在一起

程式碼示例:

ListTile(
  title: Text('我喜歡你!'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
)
複製程式碼

leading

將影象或圖示新增到列表的開頭。

程式碼示例:

ListTile(
  leading: CircleAvatar(
    backgroundImage: NetworkImage(imageUrl),
  ),
  title: Text('我喜歡你'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
)
複製程式碼

trailing

在列表的末尾放置一個影象。

程式碼示例:

ListTile(
  leading: CircleAvatar(
    backgroundImage: NetworkImage(imageUrl),
  ),
  title: Text('我喜歡你'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
  trailing: Icon(Icons.keyboard_arrow_right),
)
複製程式碼

contentPadding

設定內容邊距,預設是 16
我這裡設定30

程式碼示例:

ListTile(
  leading: CircleAvatar(
    backgroundImage: NetworkImage(imageUrl),
  ),
  title: Text('我喜歡你'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
  trailing: Icon(Icons.keyboard_arrow_right),
  contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
)
複製程式碼

selected

如果選中列表的 item 項,那麼文字和圖示的顏色將成為主題的主顏色。

程式碼示例:

ListTile(
  leading: CircleAvatar(
    backgroundImage: NetworkImage(imageUrl),
  ),
  title: Text('我喜歡你'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
  trailing: Icon(Icons.keyboard_arrow_right),
  contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
  selected: true,
)
複製程式碼

onTap、onLongPress

onTap 為單擊,onLongPress 為長按。

程式碼示例:

ListTile(
  leading: CircleAvatar(
    backgroundImage: NetworkImage(imageUrl),
  ),
  title: Text('我喜歡你'),
  subtitle: Text('你喜歡我嗎?'),
  dense:true,
  trailing: Icon(Icons.keyboard_arrow_right),
  contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
  selected: true,
  onTap: () {
    // do something
  },
  onLongPress: (){
    // do something else
  },
)
複製程式碼

enabled

通過將 enable 設定為 false,來禁止點選事件

這裡就不寫程式碼了,比較簡單。

屬性demo示例

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

class CategoryPage extends StatefulWidget {
  @override
  _CategoryPageState createState() => _CategoryPageState();
}

class _CategoryPageState extends State<CategoryPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ListTile'),),
        body: Column(
      children: <Widget>[
        ListTile(
          leading: CircleAvatar(
            backgroundImage: AssetImage('images/Test.jpg'),
          ),
          title: Text('我喜歡你'),
          subtitle: Text('你喜歡我嗎?'),
          dense: true,
          trailing: Icon(Icons.keyboard_arrow_right),
          contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
          // selected: true,
          onTap: () {
            // do something
          },
          onLongPress: () {
            // do something else
          },
        ),
        ListTile(
          leading: CircleAvatar(
            backgroundImage: AssetImage('images/Test.jpg'),
          ),
          title: Text('我喜歡你'),
          subtitle: Text('你喜歡我嗎?'),
          dense: true,
          trailing: Icon(Icons.keyboard_arrow_right),
          contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
          selected: true,
          onTap: () {
            // do something
          },
          onLongPress: () {
            // do something else
          },
        )
      ],
    ));
  }
}
複製程式碼

執行效果:

圖片載入失敗!

新增固定列表頭

很多時候我們需要給列表新增一個固定表頭。

我們需要讓ListView自動拉伸以適應螢幕,這個時候就需要我們使用到彈性佈局Flex,如果不知道的話,請移步Flutter 佈局控制元件篇-->Flex、Expanded

我們可以使用Expanded自動拉伸元件大小,並且我們也說過Column是繼承自Flex的,所以我們可以直接使用Column+Expanded來實現,

程式碼示例:

Column(children: <Widget>[
  ListTile(title: Text("數字列表")),
  Expanded(
	child: ListView.builder(itemBuilder: (BuildContext context, int index) {
	  return ListTile(title: Text("$index"));
	}),
  ),
]);
複製程式碼

執行效果:

圖片載入失敗!


T_T


相關文章

flutter: SharedPreferences桌面外掛 Flutter

flutter: SharedPreferences桌面外掛

flutter可以構建跨平臺的多端應用, 正好開發的應用需要桌面版本, 那就嘗試傳說中的無縫移植.然而剛開始就遇到了大麻煩: 移動端普遍使用的SharedPreferences在桌面端只有macOS有
Flutter 滾動控制元件篇-->滾動監聽及控制(ScrollController) Flutter

Flutter 滾動控制元件篇-->滾動監聽及控制(ScrollController)

在前面的滾動控制元件篇的文章中,我們提到了controller屬性,他接收一個ScrollController物件。ScrollController的主要作用是控制滾動位置和監聽滾動事件。本章以Lis
Flutter學習系列之Flutter上手環境準備 Flutter

Flutter學習系列之Flutter上手環境準備

記錄一下入門flutter的過程,遇到的問題以及解決的辦法。一、本地環境Windows10 + 牆內+Android Studio。二、安裝Flutter映象環境國內環境需要配置一下以下映象環境。PU
Flutter 基礎控制元件篇-->單選框(Switch)、核取方塊(Checkbox) Flutter

Flutter 基礎控制元件篇-->單選框(Switch)、核取方塊(Checkbox)

本章說一下 單選框(Switch) 和 核取方塊(Checkbox)單選框(Switch)因為按鈕都是有狀態的,所以在建立模板的時候,一定要建立有狀態的模板(使用快捷鍵stf建立),核取按鈕也一樣但是
Flutter 基礎控制元件篇-->進度指示器 Flutter

Flutter 基礎控制元件篇-->進度指示器

Flutter 中的Material 元件庫中提供了兩種進度指示器:LinearProgressIndicator和CircularProgressIndicator。它們都可以同時用於精確的進度指示
flutter新手集訓營(上) Flutter

flutter新手集訓營(上)

前端開發是否迎來大統一?flutter是否可以勝任原生開發的效能?帶著一個有一個的疑問,去了解flutter。flutter開發語言上的選擇。為什麼最終使用了dart一個曾經js熱度大增的時刻卻不溫不
Flutter(十一)之封裝幾個小Widget Flutter

Flutter(十一)之封裝幾個小Widget

更新地點: 首發於公眾號,第二天更新於掘金、思否、開發者頭條等地方;更多交流: 可以新增我的微信 372623326,關注我的微博:coderwhy學習完列表渲染後,我打算做一個綜合一點的練習小專案:
Flutter(十)之Flutter的滾動Widget Flutter

Flutter(十)之Flutter的滾動Widget

前言一:接下來一段時間我會陸續更新一些列Flutter文字教程更新進度: 每週至少兩篇;更新地點: 首發於公眾號,第二天更新於掘金、思否、開發者頭條等地方;更多交流: 可以新增我的微信 3726233
Flutter動畫實現粒子漂浮效果 Flutter

Flutter動畫實現粒子漂浮效果

本文所有原始碼見github.com/MoonRiser/F…要問2019年最火的移動端框架,肯定非Google的Flutter莫屬。本著學習的態度,基本的Dart語法(個人感覺語法風格接近Java+
flutter 支付寶APP支付 (包含後臺) Flutter

flutter 支付寶APP支付 (包含後臺)

支付寶app支付,最全攻略,看完了你就會了,這裡就是實際執行把每一步都寫下來,和看視訊是沒有區別的,而且我這個是一個系列,歡迎關注,點贊最好啦。博主這裡全部都是一字不漏的,和操作一個不漏的記錄下來,並
Flutter | 定義一個通用的多功能網路請求 Widget Flutter

Flutter | 定義一個通用的多功能網路請求 Widget

首先道個歉,最近公司很忙,又趕上十一假期,所以鴿了將近半個月。不過,後續還是會每週最少更新兩篇的!那說起網路請求的控制元件,我們首先是不是會想起 FutureBuilder?FutureBuilder
Flutter 跨端網路抓包 (以Android 為例) Flutter|Android

Flutter 跨端網路抓包 (以Android 為例)

背景在很多公司測試環境使用的是內網測試,我們公司也是。但是我們有點扯的是內網的域名沒有配置內網域名解析,必須手動配置hosts才可以正常訪問測試環境的域名。如下:# localhost is used