Flutter ListView 列表進階

列表是最常用的一個組件,通常相對于比較大的數據量都會使用到列表來顯示。

滾動監聽

當使用 ScrollView、ListView、PageView 等帶有滾動條的組件的時候,如何監聽它的滾動信息呢?Flutter 內建了 Notification 機制,一個 Widget 可以通過 Notification 將一個事件冒泡到 Widget Tree 的上層節點。監聽一個 Notification 的方法是用 NotificationListener 包裹需要監聽事件的子視圖。

new LayoutBuilder(builder: (context, constraints) {
    return new NotificationListener(
        onNotification: (ScrollNotification note) {
            print(note.metrics.pixels.toInt());  // 滾動位置。
        },
        child: new ListView.builder(
            itemCount: 40,
            itemBuilder: (BuildContext context, int index) {
                return new Container(
                    padding: const EdgeInsets.all(8.0),
                    child: new Text('今天吃什么?'),
                );
            },
        ),
    );
});

當給 ListView 外包裹一個 NotificationListener 的時候,就可以在 NotificationListener 的 onNotification 事件里監聽滾動條的信息。

在 onNotification 的回調函數里會有一個 ScrollNotification 的對象,此對象只有兩個屬性:metrics 和 context。

metrics 記錄著滾動條的信息,它有以下只讀屬性:

  • atEdge → bool - 是否能夠正好匹配 min/maxScrollExtent。
  • axis → Axis - 視圖滾動的軸。
  • axisDirection → AxisDirection - 滾動的方向。
  • extentAfter → double - 位于可滾動視口的“下方”的數量。
  • extentBefore → double - 位于可滾動視口的“上方”的數量。
  • extentInside → double - 可見內容的數量。
  • maxScrollExtent → double - 滾動最大的范圍。
  • minScrollExtent → double - 滾動最小的范圍。
  • outOfRange → bool - 是否在范圍內。
  • pixels → double - 當前滾動位置。
  • viewportDimension → double - 沿著 axisDirection 的視口范圍。

控制器

在 ScrollView、ListView、PageView 里都提供了 controller 屬性,此屬性用于控制滾動條的行為。比如,指定滾動到某個位置,實現回到頂部等功能。

實現滾動到某個位置。

// 先創建一個 controller
var controller = new ScrollController();

// 在 ListView 上添加 controller
new ListView.builder(
    itemCount: 40,
    controller: this.controller,
    itemBuilder: (BuildContext context, int index) {
        return new Container(
            padding: const EdgeInsets.all(8.0),
            child: new Text('今天吃什么? $index'),
        );
    },
),

// 點擊時滾動到某個位置。
onPressed: () {
    this.controller.animateTo(
        100.0,
        duration: new Duration(milliseconds: 300),  // 300ms
        curve: Curves.bounceIn,                     // 動畫方式
    );
},

對應的有一個沒有動畫效果的 API:jumpTo(double index)

下拉刷新

Flutter 是提供了一個下拉刷新的組件的,你可以使用原生的下拉刷新組件,或者使用第三方的組件。

使用第三方組件

使用的是一個叫 pull_to_refresh的組件,它同時支持下拉刷新和上拉加載功能。

安裝依賴:

dependencies:
    pull_to_refresh: ^1.1.5

使用它:

import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'dart:async';

new SmartRefresher(
    enablePullDown: true,
    // enablePullUp: true,
    onRefresh: (bool up) async {
        await new Future.delayed(const Duration(milliseconds: 2000)); // 等待異步操作
        this.controller2.sendBack(true, RefreshStatus.completed);     // 設置狀態為完成
        setState(() {});                                              // 更新界面
    },
    onOffsetChange: (bool up, double offset) {},
    headerBuilder: (context, mode) {
        return new ClassicIndicator(
            mode: mode,
            height: 45.0,
            releaseText: '松開手刷新',
            refreshingText: '刷新中',
            completeText: '刷新完成',
            failedText: '刷新失敗',
            idleText: '下拉刷新',
        );
    },
    controller: this.controller2,   // 控制器
    child: new ListView.builder(
        itemCount: 1000,
        controller: this.controller,
        itemBuilder: (BuildContext context, int index) {
            return new Container(
                padding: const EdgeInsets.all(8.0),
                child: new Text('今天吃什么? $index'),
            );
        },
    ),
),

上拉加載

Flutter 也提供上來加載功能。

越界回彈效果

在 ScrollView 的里有一個屬性:physics 是用于設置回彈效果的。

physics 的默認值是 ScrollPhysics,是沒有回彈效果的。那么看看其他值:

  • AlwaysScrollableScrollPhysics - 在 Android 上,默認情況下會壓縮過度滾動并導致過度滾動。在 iOS 上,過度滾動將加載彈簧,該彈簧將在發布時將滾動視圖返回到其正常范圍。
  • BouncingScrollPhysics - 用于設置列表在滑出界時,產生一個回彈效果。
  • ClampingScrollPhysics - 滾動物理環境允許滾動偏移超出內容范圍,但隨后將內容反彈回這些邊界的邊緣。

使用 BouncingScrollPhysics 即可實現回彈效果。

new ListView(
    physics: new BouncingScrollPhysics(),
)
new CustomScrollView(
    physics: new BouncingScrollPhysics(),
)

滾動視覺差

new CustomScrollView(
    physics: new BouncingScrollPhysics(),
    // 需要使用 slivers 才可以
    slivers: <Widget>[
        // 頭部內容
        new SliverAppBar(
            // 高度
            expandedHeight: 256.0,
            pinned: true,
            floating: !true,
            snap: !true,
            flexibleSpace: new FlexibleSpaceBar(
                // 標題
                title: Text('標題'),
                centerTitle: true,
                // 背景圖
                background: new Image.network(
                    'http://img.anfone.net/Outside/anfone/201666/2016661523021277.jpg',
                    fit: BoxFit.cover,
                ),
            ),
        ),
        // 列表內容
        new SliverList(
            delegate: new SliverChildBuilderDelegate((ctx, index) {
                return new Text('item: $index');
            }, childCount: this.count),
        ),
    ],
)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容