Flutter 可滚动Widget

前言

本文介绍 Flutter 中可滚动的 Widget,主要有 ListView、GridView和PageView。

一、ListView

ListView 是可以线性排列子Widget 的可滚动Widget。ListView 可以和数据绑定用来实现瀑布流。

1. 使用方法

① 使用默认的构造函数,给 children 属性赋值
② 使用 ListView.builder,可用于和数据绑定实现大量或无限的列表
③ 使用 ListView.separated,具有分割项的 ListView.builder
④ 使用 ListView.custom,需要使用 SliverChildDelegate

(1)使用默认的构造函数,给 children 属性赋值

  • 使用场景:
    适用于数据量少的情况
  • 代码示例:
import 'package:flutter/material.dart';

class ListViewDefaultWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListViewDefaultWidget'),
        ),
        body: ListView(
          children: <Widget>[
            ListTile(
              title: new Text('Title1'),
            ),
            ListTile(
              title: new Text('Title2'),
            ),
            ListTile(
              title: new Text('Title3'),
            ),
            ListTile(
              title: new Text('Title4'),
            ),
            ListTile(
              title: new Text('Title5'),
            ),
            ListTile(
              title: new Text('Title6'),
            ),
          ],
        ),
      ),
    );
  }
}
  • 运行结果:


    Screenshot_1562897333.png

(2)使用 ListView.builder,可用于和数据绑定实现大量或无限的列表

  • 使用场景:
    适用于构建大量或无限的列表,并且 ListView.builder 只会构建那些实际可见的子Widget。

大部分属性和 ListView 一样,多了 itemCount 和 @required IndexedWidgetBuilder itemBuilder 属性。
itemCount:代表子Widget 的数量,可以让 ListView 预估最大滑动距离,从而提升性能。如果不赋值,为null,则子节点数由[itemBuilder]返回null的最小索引确定。
@required IndexedWidgetBuilder itemBuilder:itemBuilder 用于创建实际可见的子Widget,只有索引大于或等于零且小于 itemCount 才会调用 itemBuilder。

  • 代码示例:
import 'package:flutter/material.dart';

void main() => runApp(ListViewBuilderWidget(
    items: List<String>.generate(10000, (i) => "Item $I"),
));

class ListViewBuilderWidget extends StatelessWidget {

  List<String> items;

  ListViewBuilderWidget({Key key, @required this.items}) :super(key: key);

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListViewBuilderWidget'),
        ),
        body: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('${items[index]}'),
              );
            }),
      ),
    );
  }
}

(3)使用 ListView.separated,具有分割项的 ListView.builder

  • 使用场景:
    适用于需要分割线的列表。

相比 ListView.builder 多了一个 separatorBuilder。

  • 代码示例:
import 'package:flutter/material.dart';

class ListViewBuilderWidget extends StatelessWidget {

  List<String> items;

  ListViewBuilderWidget({Key key, @required this.items}) :super(key: key);

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListViewBuilderWidget'),
        ),
        body: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('${items[index]}'),
              );
            }),
      ),
    );
  }
}
  • 运行结果:


(4)使用 ListView.custom,需要使用 SliverChildDelegate

  • 使用场景:
    SliverChildDelegate 有个默认实现 SliverChildListDelegate,我们可以用 SliverChildListDelegate 来实现 ListView.custom。
  • 代码示例:
import 'package:flutter/material.dart';

class ListViewCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListViewCustomWidget'),
        ),
        body: ListView.custom(
            childrenDelegate: new SliverChildListDelegate(<Widget>[
          ListTile(
            title: new Text('Title1'),
          ),
          ListTile(
            title: new Text('Title2'),
          ),
          ListTile(
            title: new Text('Title3'),
          ),
          ListTile(
            title: new Text('Title4'),
          ),
          ListTile(
            title: new Text('Title5'),
          ),
        ])),
      ),
    );
  }
}

2. ListView 的构造函数及参数说明

class ListView extends BoxScrollView {
  ListView({
    Key key,//可选;类型Key;Widget的标识
    Axis scrollDirection = Axis.vertical,//可选;类型Axis;滑动的方向
    bool reverse = false,//可选;类型bool;列表项的排列顺序
    ScrollController controller,//可选;类型ScrollController;控制ListView的滚动位置
    bool primary,//可选;类型bool;是否是父级关联的主滚动视图
    ScrollPhysics physics,//可选;类型ScrollPhysics;设置滚动效果
    bool shrinkWrap = false,//可选;类型bool;是否根据列表项的总长度来设置ListView的长度
    EdgeInsetsGeometry padding,//可选;类型EdgeInsetsGeometry;ListView 的内边距
    this.itemExtent,//可选;类型double;列表项的大小
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) 
    ...
}

二、GridView

GridView 是一个可以构建二维网格列表的可滚动Widget。

1. 使用方法:

① 使用默认的构造函数,给 children 属性赋值
② 使用 GridView.count
③ 使用 GridView.extent
④ 使用 GridView.builder,可用于和数据绑定实现大量或无限的列表
⑤ 使用 GridView.custom

(1)使用默认的构造函数,给 children 属性赋值

  • 使用场景:
    适用于使用少量 子Widget 的GridView
  • 代码示例:
import 'package:flutter/material.dart';

class GridViewDefaultWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
          appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
          body: GridView(
            gridDelegate:
                SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),//3列
            children: <Widget>[
              ListTile(
                title: Text('Title1'),
              ),
              ListTile(
                title: Text('Title2'),
              ),
              ListTile(
                title: Text('Title3'),
              ),
              ListTile(
                title: Text('Title4'),
              ),
              ListTile(
                title: Text('Title5'),
              ),
              ListTile(
                title: Text('Title6'),
              ),
            ],
          )),
    );
  }
}
  • 运行结果:


(2)使用 GridView.count
将构造函数里的 gridDelegate 属性,拆分成了 crossAxisCount、mainAxisSpacing、crossAxisSpacing 和 childAspectRatio。

  • 代码示例:
import 'package:flutter/material.dart';

class GridViewCountWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
          appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
          body: GridView.count(
            crossAxisCount: 3,
            children: <Widget>[
              ListTile(
                title: Text('Title1'),
              ),
              ListTile(
                title: Text('Title2'),
              ),
              ListTile(
                title: Text('Title3'),
              ),
              ListTile(
                title: Text('Title4'),
              ),
              ListTile(
                title: Text('Title5'),
              ),
              ListTile(
                title: Text('Title6'),
              ),
            ],
          )),
    );
  }
}

(3)使用 GridView.extent
布局算法不一样

  • 代码示例:
import 'package:flutter/material.dart';

class GridViewExtentWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
          appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
          body: GridView.extent(
            maxCrossAxisExtent: 300,
            children: <Widget>[
              ListTile(
                title: Text('Title1'),
              ),
              ListTile(
                title: Text('Title2'),
              ),
              ListTile(
                title: Text('Title3'),
              ),
              ListTile(
                title: Text('Title4'),
              ),
              ListTile(
                title: Text('Title5'),
              ),
              ListTile(
                title: Text('Title6'),
              ),
              ListTile(
                title: Text('Title7'),
              ),
            ],
          )),
    );
  }
}

(4)使用 GridView.builder,可用于和数据绑定实现大量或无限的列表

  • 使用场景:
    用于构建大量或无限的列表,而且只会构建实际可见的子Widget。
  • 代码示例:
import 'package:flutter/material.dart';

void main() => runApp(GridViewBuilderWidget(
      items: List<String>.generate(10000, (i) => "Item $I"),
    ));

class GridViewBuilderWidget extends StatelessWidget {
  final List<String> items;

  GridViewBuilderWidget({Key key, @required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
        appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
        body: GridView.builder(
          gridDelegate:
              SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
          itemCount: items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('${items[index]}'),
            );
          },
        ),
      ),
    );
  }
}

(5)使用 GridView.custom

  • 使用场景:
    增加了 childrenDelegate 的属性,类型为 SliverChildDelegate,可以自定义子Widget。
  • 代码示例:
import 'package:flutter/material.dart';

void main() => runApp(GridViewCustomWidget(
      items: List<String>.generate(10000, (i) => "Item $I"),
    ));

class GridViewCustomWidget extends StatelessWidget {
  final List<String> items;

  GridViewCustomWidget({Key key, @required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
        appBar: AppBar(title: new Text('Flutter 可滚动Widget -- GridView')),
        body: GridView.custom(
          gridDelegate:
              SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
          childrenDelegate: SliverChildListDelegate(<Widget>[
            ListTile(
              title: Text('Title1'),
            ),
            ListTile(
              title: Text('Title2'),
            ),
            ListTile(
              title: Text('Title3'),
            ),
            ListTile(
              title: Text('Title4'),
            ),
            ListTile(
              title: Text('Title5'),
            ),
            ListTile(
              title: Text('Title6'),
            ),
            ListTile(
              title: Text('Title7'),
            ),
            ListTile(
              title: Text('Title8'),
            ),
          ]),
        ),
      ),
    );
  }
}

2. GridView 构造函数和参数说明

class GridView extends BoxScrollView {
  GridView({
    Key key,//可选;类型 Key;Widget的标识
    Axis scrollDirection = Axis.vertical,//可选;类型 Axis;滑动的方向
    bool reverse = false,//可选;类型 bool;列表项的排列顺序
    ScrollController controller,//可选;类型 ScrollController;控制滚动位置
    bool primary,//可选;类型 bool;是否是与父级关联的主滚动视图
    ScrollPhysics physics,//可选;类型 ScrollPhysics;设置滚动效果,值必须为ScrollPhysics的子类,比如:AlwaysScrollableScrollPhysics():可以让 GridView 里没有足够的内容也能滑动,ScrollPhysics():GridView 在没有足够的内容的时候不能滑动
    bool shrinkWrap = false,//可选;类型 bool;是否根据列表项的总长度来设置 GridView 的长度
    EdgeInsetsGeometry padding,//可选;类型 EdgeInsetsGeometry;内边距
    @required this.gridDelegate,//必选;类型 SliverGridDelegate;控制子Widget布局的委托
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })
  ...
}

三、PageView

PageView 是可以一页一页滑动的可滚动Widget,类似 Android 中ViewPager。

1.使用方式:

① 使用默认的构造函数
② 使用 PageView.builder
③ 使用 PageView.custom

(1)使用默认的构造函数,给 children 属性赋值

  • 使用场景:
    只适用于那些只有少量子Widget 的 PageView。
  • 代码示例:
import 'package:flutter/material.dart';

class PageViewDefaultWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: new Text('PageViewDefaultWidget'),
        ),
        body: PageView(
          onPageChanged: (index) {
            print('current page $index');
          },
          children: <Widget>[
            ListTile(
              title: new Text('Title0'),
            ),
            ListTile(
              title: new Text('Title1'),
            ),
            ListTile(
              title: new Text('Title2'),
            ),
            ListTile(
              title: new Text('Title3'),
            ),
            ListTile(
              title: new Text('Title4'),
            ),
          ],
        ),
      ),
    );
  }
}
  • 运行结果:


(2) 使用 PageView.builder

  • 使用场景:
    用于构建大量和无限的列表。而且只会构建那些实际可见的子Widget。
  • 代码示例:
    多了 itemCount 和 itemBuilder 属性
void main() => runApp(PageViewBuilderWidget(
      items: List<String>.generate(10000, (i) => "Item $I"),
    ));

class PageViewBuilderWidget extends StatelessWidget {
  final List<String> items;

  PageViewBuilderWidget({Key key, @required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
        appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
        body: PageView.builder(
          onPageChanged: (index) {
            print('current page $index ');
          },
          itemCount: items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('${items[index]}'),
            );
          },
        ),
      ),
    );
  }
}

(3)使用 PageView.custom

  • 使用场景:
    增加了childrenDelegate 的属性,类型为 SliverChildDelegate,具有自定义子Widget 的能力,不合适大量数据。
  • 代码示例:
import 'package:flutter/material.dart';

void main() => runApp(PageViewCustomWidget());

class PageViewCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
          appBar: AppBar(title: new Text('Flutter 可滚动Widget -- PageView')),
          body: PageView.custom(
            onPageChanged: (index) {
              print('current page $index ');
            },
            childrenDelegate: SliverChildListDelegate(<Widget>[
              ListTile(title: Text('Title0')),
              ListTile(title: Text('Title1')),
              ListTile(title: Text('Title2')),
              ListTile(title: Text('Title3')),
              ListTile(title: Text('Title4')),
            ]),
          )),
    );
  }
}

2. PageView 的构造函数和参数说明

class PageView extends StatefulWidget {
  PageView({
    Key key,//可选;类型 Key;Widget的标识
    this.scrollDirection = Axis.horizontal,//可选;类型 Axis;滑动方向
    this.reverse = false,//可选;类型 bool;子Widget的排列顺序
    PageController controller,//可选;类型 PageController;控制滑动
    this.physics,//可选;类型 ScrollPhysics;设置滚动效果
    this.pageSnapping = true,//可选;类型 bool;默认值false,用来禁止页面捕捉,对自定义滚动行为有用
    this.onPageChanged,//可选;类型 ValueChanged<int>;监听页面的切换
    List<Widget> children = const <Widget>[],//可选;类型 List<Widget;PageView的列表项
    this.dragStartBehavior = DragStartBehavior.down,//可选;类型 DragStartBehavior;确定处理拖动开始行为的方式。如果设置为DragStartBehavior.start,则在检测到拖动手势时将开始滚动拖动行为。如果设置为DragStartBehavior.down,它将在首次检测到向下事件时开始
  }) : 
    ...
}

总结

本文主要介绍的可滚动的 Widget,ListView、GridView 和 PageView。除了这三个之外还有很多可以滚动的 Widget,如 SingleChildScrollView、CustomScrollView 等等,若需要了解更多的 Widget 可以去查看官网的介绍https://flutter.dev/docs/development/ui/widgets/scrolling

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343