FlutterFlutter

Flutter 学习 - 可滚动的 Widget

2019-11-15  本文已影响0人  迷途小顽童

前言 -- 这是一篇陆陆续续写了三天的文章

在实际的开发中我们经常会遇到用列表展示数据,当内容超过一屏的时候可以进行滚动,来查看更多的内容,Android中这样的控件有ScrollView,ListView,RecycleView,GridView,ViewPager,那么在Flutter中都有哪些滚动的Widget呢,这篇文章我们将一一介绍。

正文 - Flutter中的可滚动Widget

SingleChildScrollView(
        padding: EdgeInsets.all(15),
        child: Text(
          "hello world 你好吗" * 500,
          style: TextStyle(fontSize: 18, color: Colors.green),
        ),
      ),

下面看下构造方法

const SingleChildScrollView({
    Key key,
    this.scrollDirection = Axis.vertical,//滑动方向,默认垂直方向,类型Axis
    this.reverse = false,//控制是从头开始滑动还是从尾开始滑动,false是从头开始滑动
    this.padding,//子Widget的内边距,
    bool primary,//是否是与父级关联的主滚动视图,当为true时,即使SingleChildScrollView中没有足够的内容也能滑动
    this.physics,//设置SingleChildScrollView滚动效果,类型ScrollPhysics
    this.controller,//可以控制 SingleChildScrollView 滚动的位置当 primary 为 true 时,controller 必须为 null,类型ScrollController
    this.child,//SingleChildScrollView 的列表项
    this.dragStartBehavior = DragStartBehavior.start,//确定处理拖动开始行为的方式
  }) : 
      ...;

ScrollPhysics

DragStartBehavior

ScrollController提供以下的几个功能:

ListView(
          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"),
              ),
              ListTile(
                  title: Text("title8"),
              ),
              ListTile(
                  title: Text("title9"),
              ),
              ListTile(
                  title: Text("title10"),
              ),
              ListTile(
                  title: Text("title11"),
              ),
              ListTile(
                  title: Text("title12"),
              ),
              ListTile(
                  title: Text("title13"),
              ),
              ListTile(
                  title: Text("title14"),
              ),
              ListTile(
                  title: Text("title15"),
              ),
              ListTile(
                  title: Text("title16"),
              )    
          ],
      )

PS:这种方式只适用于那些只有少量子Widget的ListView,ListView在创建的时候,其子Widget也会一起创建
下面看下ListView的构造函数

ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,//滑动的方向,默认为 Axis.vertical,垂直方向可滑动
    bool reverse = false,//控制 ListView 里列表项的排列顺序,是按照插入顺序排,还是按照插入顺序相反的方向排序
    ScrollController controller,//可以控制 ListView 滚动的位置
    bool primary,//是否是与父级关联的主滚动视图
    ScrollPhysics physics,//设置 ListView 的滚动效果
    bool shrinkWrap = false,//是否根据列表项的总长度来设置 ListView的长度
    EdgeInsetsGeometry padding,//ListView 的内边距
    this.itemExtent,//itemExtent 指的是列表项的大小
    bool addAutomaticKeepAlives = true,//是否用 AutomaticKeepAlive 来包列表项,默认为 true
    bool addRepaintBoundaries = true,// 是否用 RepaintBoundary 来包列表项,默认为 true
    bool addSemanticIndexes = true,//是否用 IndexedSemantics 来包列表项,默认为 true,使用 IndexedSemantics 是为了正确的用于辅助模式
    double cacheExtent,//ListView 可见部分的前面和后面的区域可以用来缓存列表项,这部分区域的 item 即使不可见,也会加载出来,所以当滑动到这个区域的时候,缓存的区域就会变的可见,cacheExtent 就表示缓存区域在可见部分的前面和后面有多少像素
    List<Widget> children = const <Widget>[],//ListView 的列表项
    int semanticChildCount,//提供语义信息的列表项的数量,默认为 ListView 的 item 的数量
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,//同SingleChildScrollView中解释
  }) : childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
        ...
       );

shrinkWrap: 当 shrinkWrap 为 false 时,ListView 会在滚动方向扩展到可占用的最大空间
当 shrinkWrap 为 true 时,ListView 在滚动方向占用的空间就是其列表项的总长度,但是这样会很耗性能,因为当其列表项发生变化时,ListView 的大小会重新计算

itemExtent: 如果滚动方向是垂直方向,则 itemExtent 代表的是 子Widget 的高度,
如果滚动方向为水平方向,则 itemExtent 代表的是 子Widget 的长度
如果 itemExtent 不为 null,则会强制所有 子Widget 在滑动方向的大小都为 itemExtent
指定 itemExtent 会比较高效,因为 子Widget 的高度就不需要在去计算,ListView 也可以提前知道列表的长度

addAutomaticKeepAlives: 在一个 lazy list 里,如果 子Widget 为了保证自己在滑出可视界面时不被回收,就需要把 addAutomaticKeepAlives 设为 true
当 子Widget 不需要让自己保持存活时,为了提升性能,请把 addAutomaticKeepAlives 设为 false
如果 子Widget 自己维护其 KeepAlive 状态,那么此参数必须置为false。

addRepaintBoundaries: 当 addRepaintBoundaries 为 true 时,可以避免列表项重绘,提高性能
但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加 RepaintBoundary 反而会更高效。

2.适用ListView.builder可用于和数据绑定实现大量或者无限的列表
先看效果图

演示5.gif

看下实现代码

class ScrollLayoutDemo extends StatefulWidget {
  String title;
  List<String> items = List<String>.generate(500, (i) => "item $i");
  ScrollLayoutDemo({Key key, this.title});
  @override
  _ScrollLayoutDemoState createState() => _ScrollLayoutDemoState();
}

class _ScrollLayoutDemoState extends State<ScrollLayoutDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          widget.title,
        ),
      ),
      body: ListView.builder(
          itemCount: widget.items.length,
          itemBuilder: (context,index){
              return ListTile(
                  title: Text(widget.items[index]),
              );
          },
      )
    );
  }
}

其构造函数大部分属性和ListView都一样,只有两个:

itemCount:代表 子Widget 的数量,虽然是可选的,但是还是建议赋值,可以让 ListView 预估最大滑动距离,从而提升性能。如果为null,则子节点数由[itemBuilder]返回null的最小索引确定。

itemBuilder:必传参数,itemBuilder 用于创建实际可见的 子Widget,只有索引大于或等于零且小于 itemCount 才会调用 itemBuilder。

3.使用 ListView.separated,具有分割项的 ListView.builder
ListView.separated相比ListView.builder多了一个separatorBuilder,separatorBuilder是用于构建分割项的,而且是必选的
先看下效果

演示6.gif

使用方法

ListView.separated(
          itemCount: widget.items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(widget.items[index]),
            );
          },
          separatorBuilder: (context, index) {
            return Container(
              constraints: BoxConstraints.tightFor(height: 1),
              color: Colors.greenAccent,
            );
          },
        ));

4.使用 ListView.custom,需要使用 SliverChildDelegate
先看下效果图

演示7.gif
看下代码实现
//使用方法
ListView.custom(
            childrenDelegate: SliverChildListDelegate(
                _getWidget()
            ),
        )

代码中添加widget的方法如下

_getWidget(){
    List<Widget> widgets = [];
    for (var i = 0;i < 100;i++) {
      widgets.add(ListTile(title: Text("item $i"),));
    }
 return widgets;  
}

先看下ListView.custom的构造函数,

const ListView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
  }) : assert(childrenDelegate != null),
       super(
         ...
       );

由构造函数可以看出ListView.custom跟ListView主要的区别就是childrenDelegate,这个是必传参数,类型是SliverChildDelegate
,这是一个抽象类,主要有两个实现类SliverChildBuilderDelegate和SliverChildListDelegate(我们demo中就是使用这个类来实现列表的)

GridView(
        gridDelegate:
            SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
        children: _getWidget(),
      )

下面看下GridView的源码:
我们主要看下GridView新增的属性,一些滚动公共的属性在前面在SingleChildScrollview中有介绍,这里就不再赘述

GridView({
    Key key,
    Axis scrollDirection = Axis.vertical,//滚动方向,默认垂直滚动,类型是Axis
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,//控制GridView子Widget的布局的委托类型是SliverGridDelegate
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  }) : assert(gridDelegate != null),
       ...,
       super(
        ...
       );
const SliverGridDelegateWithFixedCrossAxisCount({
    @required this.crossAxisCount,//交叉轴子view的数量
    this.mainAxisSpacing = 0.0,//主轴方向的的间距(主轴为垂直方向则为行间距,主轴为水平方向则为列间距)
    this.crossAxisSpacing = 0.0,//交叉轴方向的间距(交叉轴为垂直方向则为行间距,交叉轴为水平方向则为列间距)
    this.childAspectRatio = 1.0,//横轴和主轴的比例
  }) : ...;

SliverGridDelegateWithMaxCrossAxisExtent的构造函数

const SliverGridDelegateWithMaxCrossAxisExtent({
    @required this.maxCrossAxisExtent,
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.childAspectRatio = 1.0,
  }) :...;

和上面构造函数的唯一区别就是maxCrossAxisExtent这个参数,其他的都一样
maxCrossAxisExtent:在交叉轴上每一项的的最大范围,例如当主轴是垂直的,GridView的宽度是500px,这个值为150px则Delegate就是自动计算一共可以显示4列,每列是125px。

2、使用GridView.count 创建GridView
这个实现的效果其实和GridView很像,就不上效果图了,我们直接看其构造函数

GridView.count({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required int crossAxisCount,
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
       childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
         ...
       );

从构造函数我们可以看到,这个方法其实就是将GridView中的SliverGridDelegateWithFixedCrossAxisCount,拆了出来,将SliverGridDelegateWithFixedCrossAxisCount中的属性直接放在构造函数中,使用方式其实没有差别,下面看下使用方式

GridView.count(
        crossAxisCount: 3,
        children: _getWidget(),
      )

就是这么简单。

同理既然SliverGridDelegateWithFixedCrossAxisCount这个可以拆,那么SliverGridDelegateWithMaxCrossAxisExtent是不是一样可以,答案是肯定的,这就有了第三种方式

3、使用 GridView.extent来创建GridView
看下其构造方法

GridView.extent({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required double maxCrossAxisExtent,
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
       childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
        ...
       );

下面看下实现

GridView.extent(
        maxCrossAxisExtent: 120,
        children: _getWidget(),
      )

4、使用GridView.builder,可用于和数据绑定实现大量或者无限的列表
这个和ListView.builder的使用就很相似了,我们先看下效果图

演示9.gif
下面看下实现代码
class GridViewLayoutDemo extends StatefulWidget {
  String title;
  List<Widget> items = _getWidget();
  GridViewLayoutDemo({Key key, this.title});
  @override
  _GridViewLayoutDemoState createState() => _GridViewLayoutDemoState();
}

_getWidget() {
  List<Widget> widgets = [];
  for (var i = 0; i < 100; i++) {
    widgets.add(ListTile(
      title: Text("item $i"),
    ));
  }
  return widgets;
}

class _GridViewLayoutDemoState extends State<GridViewLayoutDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GridView.builder(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 5,
          ),
          itemCount: widget.items.length,
          itemBuilder: (context,index){
              return widget.items[index];
          },
      )
    );
  }
}

下面看下构造函数

GridView.builder({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    int semanticChildCount,
  }) : assert(gridDelegate != null),
       childrenDelegate = SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
        ...
       );

这里主要是gridDelegate和itemBuilder这两个参数,和ListView中itemCount和itemBuilder属性很像。
5、使用 GridView.custom来创建GridView
先看下其实现方式

GridView.custom(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3
          ),
          childrenDelegate: SliverChildListDelegate(
              _getWidget()
          ),
      ),

下面看下构造函数

const GridView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required this.childrenDelegate,//类型是SliverChildDelegate
    double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
        ...
       );

这里其实是增加了childrenDelegate类型是SliverChildDelegate,可以定制子widget,和ListView.custom中的一样,用法也是一样的

CustomScrollView(
            slivers: <Widget>[
                SliverAppBar(
                    pinned: true,
                    expandedHeight: 100,
                    flexibleSpace: FlexibleSpaceBar(
                        title: Text("demo"),
                    ),
                ),
            SliverGrid(
                gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                    maxCrossAxisExtent: 150,
                    mainAxisSpacing: 10.0,
                    crossAxisSpacing: 10.0,
                    childAspectRatio: 4.0
                ),
                delegate: SliverChildBuilderDelegate((context,index){
                    return Container(
                        alignment: Alignment.center,
                        color: Colors.lightGreen[100*(index % 9)],
                        child: Text("grid item $index"),
                    );
                },
                childCount: 20),
            ),
            SliverFixedExtentList(
                itemExtent: 50.0,
                delegate: SliverChildBuilderDelegate((contxt,index){
                    return Container(
                        alignment: Alignment.center,
                        color: Colors.pink[100*(index % 9)],
                        child: Text("grid item $index"),
                    );
                },
                childCount: 100)
                ),
            ],
        
        )

下面看下CustomScrollView的构造函数
它里面的属性我们在介绍其他控件是都有介绍 ,这里就不做赘述

const CustomScrollView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    Key center,//默认为空,当shrinkWrap=false时,必须为空
    double anchor = 0.0,
    double cacheExtent,
    this.slivers = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : super(
    ...
  );

这里主要看下slivers这个属性,这个属性的值只能是以Sliver开头的一系列Widget:

 const SliverList({
    Key key,
    @required SliverChildDelegate delegate,
  }) : super(key: key, delegate: delegate);

用法和上面的ListView.custom类似,属性含义可以参考上文

  const SliverFixedExtentList({
    Key key,
    @required SliverChildDelegate delegate,
    @required this.itemExtent,
  }) : super(key: key, delegate: delegate);

用法和上面的ListView.custom类似,属性含义可以参考上文

 const SliverGrid({
    Key key,
    @required SliverChildDelegate delegate,
    @required this.gridDelegate,
  }) : super(key: key, delegate: delegate);

这个Widget跟GridView很像,除了这个还有SliverGrid.extent(),和SliverGrid.count()两种构造方法,用法分别和GridView.extent()和GridView.count()也是相似

  const SliverPadding({
    Key key,
    @required this.padding,//内边距
    Widget sliver,//这里widget也只能是Sliver开头的Widget
  }) : assert(padding != null),
       super(key: key, child: sliver);

const SliverAppBar({
    Key key,
    this.leading,//左侧的图标或文字,多为返回箭头
    this.automaticallyImplyLeading = true,//没有leading为true的时候,默认返回箭头,没有leading且为false,则显示title
    this.title,//标题
    this.actions,//标题右侧的操作
    this.flexibleSpace,//可以理解为SliverAppBar的背景内容区
    this.bottom,//SliverAppBar的底部区
    this.elevation,//阴影
    this.forceElevated = false,//是否显示阴影
    this.backgroundColor,//背景颜色
    this.brightness,//状态栏主题,默认Brightness.dark,可选参数light
    this.iconTheme,//SliverAppBar图标主题
    this.actionsIconTheme,//action图标主题
    this.textTheme,//文字主题
    this.primary = true,//是否显示在状态栏的下面,false就会占领状态栏的高度
    this.centerTitle,//标题是否居中显示
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,//标题横向间距
    this.expandedHeight,//合并的高度,默认是状态栏的高度加AppBar的高度
    this.floating = false,//滑动时是否悬浮
    this.pinned = false,//标题栏是否固定
    this.snap = false,//配合floating使用
  }): assert(automaticallyImplyLeading != null),
       assert(forceElevated != null),
       assert(primary != null),
       assert(titleSpacing != null),
       assert(floating != null),
       assert(pinned != null),
       assert(snap != null),
       assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
       super(key: key)
const SliverSafeArea({
    Key key,
    this.left = true,//是否左侧在可是区域内
    this.top = true,//是否顶部在可是区域内
    this.right = true,//是否右侧在可是区域内
    this.bottom = true,//是否底部在可是区域内
    this.minimum = EdgeInsets.zero,//最小边距
    @required this.sliver,//这里widget也只能是Sliver开头的Widget
  }) : assert(left != null),
       assert(top != null),
       assert(right != null),
       assert(bottom != null),
       super(key: key);

这个Widget可以保证内容展示在可视区域内

PageView有三种实现方式,下面我们一一介绍
1、使用默认的构造函数,给 children 属性赋值
使用默认构造函数写 PageView,只适用于那些只有少量 子Widget 的 PageView。
先看效果图

演示11.gif
看下实现代码
PageView(
            onPageChanged: (index){
                
            },
            children: <Widget>[
                Center(
                    child: Text(
                        "title0",
                        style: TextStyle(
                            fontSize: 24,
                            color: Colors.purple
                        ),
                    ),
                ),
                Center(
                    child: Text(
                        "title1",
                         style: TextStyle(
                            fontSize: 24,
                            color: Colors.red
                        ),
                    ),
                ),
                Center(
                    child: Text(
                        "title2",
                         style: TextStyle(
                            fontSize: 24,
                            color: Colors.green
                        ),
                    ),
                ),
                Center(
                    child: Text(
                        "title3",
                         style: TextStyle(
                            fontSize: 24,
                            color: Colors.blue
                        ),
                    ),
                )
            ],
        )

可以看到使用很简单,接下来看下其构造函数

PageView({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,//默认值为 true,设置为false以禁用页面捕捉,对自定义滚动行为很有用。
    this.onPageChanged,//当 PageView 当前页面切换的时候调用,是个方法
    List<Widget> children = const <Widget>[],//PageView 的列表项
    this.dragStartBehavior = DragStartBehavior.start,
  }) : controller = controller ?? _defaultPageController,
       childrenDelegate = SliverChildListDelegate(children),
       super(key: key);

2、使用 PageView.builder
PageView.builder 可以和数据绑定,用于构建大量或无限的列表。而且只会构建那些实际可见的 子Widget
看下演示效果

演示12.gif
看下使用方法
PageView.builder(
            onPageChanged: (index){
                
            },
            itemCount: 10,
            itemBuilder: (context,index){
                return Center(
                    child: Text(
                        "title $index",
                        style: TextStyle(
                            fontSize: 24,
                            color: Colors.purple
                        ),
                    ),
                );
                
            },
        )

看下其构造方法

PageView.builder({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    this.dragStartBehavior = DragStartBehavior.start,
  }) : controller = controller ?? _defaultPageController,
       childrenDelegate = SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
       super(key: key);

可以看到多了和 ListView.builder 类似的 itemCount 和 itemBuilder 属性,用法也是一样的。
3、使用 PageView.custom
先看下演示效果

演示13.gif
看下代码实现
PageView.custom(
        onPageChanged: (index) {},
        childrenDelegate: SliverChildListDelegate(<Widget>[
          Center(
            child: Text(
              "title1",
              style: TextStyle(fontSize: 24, color: Colors.green),
            ),
          ),
          Center(
            child: Text(
              "title2",
              style: TextStyle(fontSize: 24, color: Colors.green),
            ),
          ),
          Center(
            child: Text(
              "title3",
              style: TextStyle(fontSize: 24, color: Colors.green),
            ),
          ),
        ]),
      ),

下面我们看下构造函数

PageView.custom({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    @required this.childrenDelegate,
    this.dragStartBehavior = DragStartBehavior.start,
  }) : assert(childrenDelegate != null),
       controller = controller ?? _defaultPageController,
       super(key: key);

可以看到这里主要是childrenDelegate 这个属性,类型是SliverChildDelegate,它具有定制子Widget的能力,这个跟ListView.custom()中的childrenDelegate属性用法相同

结语

截止到此,关于可滚动的Widget就介绍完了。
以下是我的Flutter系列的链接,后续会持续更新,欢迎大家指正。

Flutter 系列文章

上一篇 下一篇

猜你喜欢

热点阅读