Flutter学习-滚动的Widget

Flutter学习-滚动的Widget

  • 1. ListView
    • 1.1 ListView()初始化
      • 1.1.1 基础使用
      • 1.1.2 ListTitle
      • 1.1.3 List.generate 快速创建Item
      • 1.1.4 滚动方向
    • 1.2 ListView.build() 初始化
    • 1.3 ListView.separated
  • 2. GridView
    • 2.1 GridView()
      • 2.1.1 SliverGridDelegateWithFixedCrossAxisCount
      • 2.1.2 SliverGridDelegateWithMaxCrossAxisExtent
    • 2.2 GridView.count、GridView.extent
    • 2.3 GridView.build
    • 2.4 屏幕四周间距问题
  • 3. Slivers
    • 3.1 ListView、GridView的本质
    • 3.2 Slivers的基本使用
      • 3.2.1 SliverList
      • 3.2.2 SliverFixedExtentList
      • 3.2.3 SliverGrid
      • 3.2.4 SliverPadding
      • 3.2.5 SliverAppBar
      • 3.2.6 SliverSafeArea
      • 3.2.7 组合使用
  • 4. 监听滚动事件
    • 4.1 ScrollController
    • 4.2 NotificationListener

前言: 列表是移动端经常使用的一种视图展示方式,在Flutter中提供了ListView和GridView来实现列表和网格的布局展示

1. ListView

移动端数据量比较大时,我们都是通过列表来展示,比如 商品数据、聊天列表、通讯录、朋友圈等
在IOS中我们可以通过UITableView和UICollectionView来实现
在Flutter中我们也有对应的Widget,那就是ListView

1.1 ListView()初始化

ListView可以沿一个方向(垂直或水平方向,默认是垂直方向)来排列其所有子Widget。

一种最简单的使用方式是直接将所有需要排列的子Widget放在ListView的children属性中即可。

官方文档对默认构造器的描述:
在这里插入图片描述

大概的意思: 默认构造函数显式接受List的子元素。这个构造函数适用于只有少量子元素的列表视图,因为构造list需要为每个可能显示在列表视图中的子元素工作,而不仅仅是那些实际可见的子元素。(不管在屏幕上是不是可见的,都会把Item创建好,而不是现实Item的时候在创建)

flutter想要实现滚动效果,必须使用滚动的Widget

ListView({
    Key? key,
    Axis scrollDirection = Axis.vertical, //设置滑动方向
    bool reverse = false, //控制数据显示的顺序
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry? padding,
    this.itemExtent,// 设置Item的高度,不设置表示默认包裹内容
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double? cacheExtent,
    List<Widget> children = const <Widget>[],//Item
    int? semanticChildCount,
    ...
  }) 

1.1.1 基础使用

代码演练:

class GYHomeContent extends StatelessWidget {
  final TextStyle textStyle = TextStyle(fontSize: 20, color: Colors.redAccent);

  @override
  Widget build(BuildContext context) {
    //实现圆角图片
    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Container(
            child: Text("人的一切痛苦,本质上都是对自己无能的愤怒。", style: textStyle),
            color: Colors.orange,
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Container(
              child: Text("人活在世界上,不可以有偏差;而且多少要费点劲儿,才能把自己保持到理性的轨道上。",
                  style: textStyle),color: Colors.green,),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Container(
              child: Text("我活在世上,无非想要明白些道理,遇见些有趣的事。", style: textStyle),color: Colors.red,),
        ),
      ],
    );
  }
}

效果图:
Flutter学习-滚动的Widget_第1张图片

1.1.2 ListTitle

在开发中我们经常看见一种列表:有一个图标或图片(Icon),有一个标题(Title),有一个子标题(Subtitle),还有尾部一个图标(Icon)。

示例代码:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //实现圆角图片
    return ListView(
        children: [
          ListTile(
            leading: Icon(Icons.people, size: 36,),
            title: Text("联系人"),
            subtitle: Text("联系人信息"),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          ListTile(
            leading: Icon(Icons.email, size: 36,),
            title: Text("邮箱"),
            subtitle: Text("邮箱地址信息"),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          ListTile(
            leading: Icon(Icons.message, size: 36,),
            title: Text("消息"),
            subtitle: Text("消息详情信息"),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          ListTile(
            leading: Icon(Icons.map, size: 36,),
            title: Text("地址"),
            subtitle: Text("地址详情信息"),
            trailing: Icon(Icons.arrow_forward_ios),
          )
        ]
    );
  }
}

Flutter学习-滚动的Widget_第2张图片

1.1.3 List.generate 快速创建Item

代码演练:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //实现圆角图片
    return ListView(
        // 100 : 表示需要创建Item的个数, index: Item的下标
        children: List.generate(100, (index) {
      return ListTile(
        leading: Icon(
          Icons.people,
          size: 36,
        ),
        title: Text("联系人"),
        subtitle: Text("联系人信息 ${index + 1}"),
        trailing: Icon(Icons.arrow_forward_ios),
      );
    }));
  }
}

Flutter学习-滚动的Widget_第3张图片

1.1.4 滚动方向

ListView默认是垂直方向滚动,我们也可以修改滚动方式,实现水平滚动

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //实现圆角图片
    return ListView(
        scrollDirection: Axis.horizontal,
        // 100 : 表示需要创建Item的个数, index: Item的下标
        children: List.generate(100, (index) {
          return ListTile(
            leading: Icon(
              Icons.people,
              size: 36,
            ),
            title: Text("联系人"),
            // subtitle: Text("联系人信息 ${index + 1}"),
            // trailing: Icon(Icons.arrow_forward_ios),
          );
        }));
  }
}

Flutter学习-滚动的Widget_第4张图片

1.2 ListView.build() 初始化

官网描述:
在这里插入图片描述

该构造方法创建ListView,适合Item比较多的情况, 并且是在ListView会在真正需要的时候去创建子Widget,而不是一开始就全部初始化好。

ListView.builder该方法有两个重要的参数:

  • itemBuilder:列表项创建的方法。当列表滚动到对应位置的时候,ListView会自动调用该方法来创建对应的子Widget。类型是IndexedWidgetBuilder,是一个函数类型
  • itemCount:表示列表项的数量,如果为空,则表示ListView为无限列表。
class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
        itemCount: 100,
        itemExtent: 50,
        itemBuilder: (BuildContext ctx, int index) {
          return Text(
            "Hello World: $index",
            style: TextStyle(fontSize: 20),
          );
        });
  }
}

Flutter学习-滚动的Widget_第5张图片

1.3 ListView.separated

ListView.separated可以生成列表项之间的分割器,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割器生成器。

  • ListView.separated没有参数itemExtent,所以不能够设置Item的高度, 只能自适应高度

示例代码:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.separated(
        itemBuilder: (BuildContext ctx, int index) {
          return Text(
            "Hello World: $index",
            style: TextStyle(fontSize: 20),
          );
        },
        separatorBuilder: (BuildContext ctx, int index){
          return Divider(
            color: Colors.red,
            height: 50, //设置分割线区域高度
            thickness: 10,//设置分割线的高度
            indent: 15, //设置分割线开始的距离
            endIndent: 15//设置分割线结束的距离,
          );
        },
        itemCount: 100
    );
  }
}

Flutter学习-滚动的Widget_第6张图片

  • cacheExtent: 预加载高度,就设置高度越高,可能预加载的Item就越多

2. GridView

GridView用于展示多列的展示,在开发中也非常常见,比如直播App中的主播列表、电商中的商品列表等等。
在Flutter中我们可以使用GridView来实现,使用方式和ListView也比较相似。

2.1 GridView()

源码:

GridView({
    Key? key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry? padding,
    required this.gridDelegate, //必须传递一个参数,不能为null
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double? cacheExtent,
    List<Widget> children = const <Widget>[],
    int? semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    Clip clipBehavior = Clip.hardEdge,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    String? restorationId,
  })

右上述源码可知,默认默认构造器方法中的参数列表中,有一个必传参数this.gridDelegate,该参数用于控制交叉轴的item数量或者宽度,参数的类型是SliverGridDelegate,但是该类是一个抽象类
我们查看该抽象类的子类如下:

Flutter学习-滚动的Widget_第7张图片

除了SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent这两个子类示例外,其它的子类都是_开头属于私有子类

2.1.1 SliverGridDelegateWithFixedCrossAxisCount

const SliverGridDelegateWithFixedCrossAxisCount({
    required this.crossAxisCount, // 交叉轴的item个数
    this.mainAxisSpacing = 0.0, // 主轴的间距
    this.crossAxisSpacing = 0.0, // 交叉轴的间距
    this.childAspectRatio = 1.0, // 子Widget的宽高比
  })
  • crossAxisCount:我们只需要提供Item的个数,,然后Fluterr会根据屏幕的宽度自动计算出每个Item的宽度

  • mainAxisSpacing: 主轴方向Item和Item之间的距离

  • crossAxisSpacing:交叉轴轴方向Item和Item之间的距离

  • childAspectRatio: 这里不能够设置Item的明确的宽和高(没有提供相关参数),只能设置该参数来确定Item的宽度

  • 示例代码:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
        ),
      children:List.generate(100, (index) {
        return Container(
          color: Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1),
          child: Center(child: Text("Item:$index",style: TextStyle(fontSize: 24),)),
        );
      }) ,
    );
  }
}

Flutter学习-滚动的Widget_第8张图片

2.1.2 SliverGridDelegateWithMaxCrossAxisExtent

源码

const SliverGridDelegateWithMaxCrossAxisExtent({
    required this.maxCrossAxisExtent,//交叉轴Item最大的宽度
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.childAspectRatio = 1.0,
    this.mainAxisExtent, // 主轴Item最大的高度
  })

代码示例:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
          mainAxisExtent: 200
        ),
      children:List.generate(100, (index) {
        return Container(
          color: Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1),
          child: Center(child: Text("Item:$index",style: TextStyle(fontSize: 24),)),
        );
      }) ,
    );
  }
}

Flutter学习-滚动的Widget_第9张图片

  • 注意: 由运行结果可知,我们虽然设置maxCrossAxisExten的值是200,但是实际的运行结果Item的宽度是肯定没有200的,其实这里maxCrossAxisExten设置是只是Item的最大宽度,是一个范围值,并不是一个明确的值,所以Flutter会根据你设置的值,可能会调整布局,最终显示的Item的宽可能不是你想要的值。

2.2 GridView.count、GridView.extent

如果你觉得上面配置delegate比较麻烦,不想使用delegate,这里有其它的构造方法

  • GridView.count(): 构造方法创建相当于设置delegate为SliverGridDelegateWithFixedCrossAxisCount
  • GridView.extent():构造方法相当于设置delegate为SliverGridDelegateWithMaxCrossAxisExtent

2.3 GridView.build

ListView一样,使用构造函数会一次性创建所有的子Widget,会带来性能问题,所以我们可以使用GridView.build来交给GridView自己管理需要创建的子Widget

示例代码:

class GYHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
        ),
        itemCount: 100,
        itemBuilder: (BuildContext ctx, int index) {
          return Container(
            color: Color.fromRGBO(Random().nextInt(256), Random().nextInt(256),
                Random().nextInt(256), 1),
            child: Center(
                child: Text(
              "Item:$index",
              style: TextStyle(fontSize: 24),
            )),
          );
        });
  }
}

Flutter学习-滚动的Widget_第10张图片

  • 注意:不管是ListView还是GridView,Item的点击需要我们自己添加收拾监听,构造器方法中并没有提供监听的属性(有点坑爹)
  • itemCount: 如果该属性没有设置,那么就是无限滚动,表示有无限个Item

2.4 屏幕四周间距问题

  • 如果希望两边也有间距的话,我们可以在外层包裹一个Padding,但是这里有一个问题,如果设置了上下的距离的话, 表格像上滑动的时候,会出现白色间距的问题
    Flutter学习-滚动的Widget_第11张图片

3. Slivers

我们考虑一个这样的布局:一个滑动的视图中包括一个标题视图(HeaderView),一个列表视图(ListView),一个网格视图(GridView)。
我们怎么可以让它们做到统一的滑动效果呢?使用前面的滚动是很难做到的。
Flutter中有一个可以完成这样滚动效果的Widget:CustomScrollView,可以统一管理多个滚动视图。
在CustomScrollView中,每一个独立的,可滚动的Widget被称之为Sliver。
补充:Sliver可以翻译成裂片、薄片,你可以将每一个独立的滚动视图当做一个小裂片。

3.1 ListView、GridView的本质

通过查看源码我们知道不管是ListView还是GridView都是继承自BoxScrollView,而BoxScrollView 继承自ScrollView,而ScrollView继承自StateLessWidget
Flutter学习-滚动的Widget_第12张图片

之前我们说过,想知道flutter最终渲染的是那个组件,我们可以查看build方法:

Flutter学习-滚动的Widget_第13张图片

我们可以看到本质最终创建一个Scrollable的组件,然后调用了buildSlivers()方法获取sliver,而slivers里面才是我们真正可以滚动的东西

接下来我们查看buildSlivers()方法,可以发现该方法是一个抽象方法

 @protected
  List<Widget> buildSlivers(BuildContext context);

抽象方法子类肯定是需要实现, 那么我们可以查看BoxScrollView是否实现了该方法:
Flutter学习-滚动的Widget_第14张图片

  • 通过上述代码我们可以得出一个结论:BoxScrollView只有一个sliver
  • BoxScrollView中的buildChildLayout是一个抽象法方法, ListView和GridView实现了这个抽象方法
  • ListView该方法代码:
@override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
      return SliverFixedExtentList(
        delegate: childrenDelegate,
        itemExtent: itemExtent!,
      );
    }
    return SliverList(delegate: childrenDelegate);
  }
  • GridView的该方法代码:
@override
  Widget buildChildLayout(BuildContext context) {
    return SliverGrid(
      delegate: childrenDelegate,
      gridDelegate: gridDelegate,
    );
  }

我们发现该方法返回的都是 SliverXXX对象,我们在结合ListView和GridView的初始化方法

Flutter学习-滚动的Widget_第15张图片

Flutter学习-滚动的Widget_第16张图片

通过初始化方法可以看到 ,ListViewGridView 本质上其实是创建了对应的sliver对象

当你希望一个列表中既有ListView式的滚动,又有GridView的滚动, 那么使用BoxScrollView是实现不了的, 要想实现这种效果, 那么就需要CustomScrollView

3.2 Slivers的基本使用

因为我们需要把很多的Sliver放在一个CustomScrollView中,所以CustomScrollView有一个slivers属性,里面让我们放对应的一些Sliver

const CustomScrollView({
    Key? key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
    ScrollBehavior? scrollBehavior,
    bool shrinkWrap = false,
    Key? center,
    double anchor = 0.0,
    double? cacheExtent,
    this.slivers = const <Widget>[], //存放sliver的数组
    int? semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    String? restorationId,
    Clip clipBehavior = Clip.hardEdge,
  }) 
  • 注意:slivers数组中的元素必须是特定的widget,不能随便的赋值,比如Text() 就不可以
    Flutter学习-滚动的Widget_第17张图片

  • SliverList:类似于我们之前使用过的ListView

  • SliverFixedExtentList:类似于SliverList,只是可以设置滚动的高度;

  • SliverGrid:类似于我们之前使用过的GridView

  • SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;

  • SliverAppBar:添加一个AppBar,通常用来作为CustomScrollViewHeaderView

  • SliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容)

3.2.1 SliverList

使用CustomScrollView和SliverList的方式来实现ListView的效果

SliverList的初始化源码:

const SliverList({
    Key? key,
    required SliverChildDelegate delegate,
  }) 
  • SliverChildDelegate是一个抽象类,有两个子类分别是SliverChildBuilderDelegateSliverChildListDelegate,前者创建就类似ListView.build()的构造方法,后者类似ListView()的默认构造方法
  • SliverChildListDelegate代码演练:
class SliverListDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return CustomScrollView(
      slivers: [
        SliverList(
            delegate: SliverChildListDelegate(
              List.generate(100, (index) {
                return ListTile(
                  leading: Icon(
                    Icons.people,
                    size: 36,
                  ),
                  title: Text("联系人"),
                  subtitle: Text("联系人信息 ${index + 1}"),
                  trailing: Icon(Icons.arrow_forward_ios),
                );
              }),
            ))
      ],
    );
  }
}

Flutter学习-滚动的Widget_第18张图片

  • SliverChildBuilderDelegate的代码演练
class SliverListDemo2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return CustomScrollView(
      slivers: [
        SliverList(
            delegate: SliverChildBuilderDelegate((BuildContext ctx, int index) {
              return ListTile(
                leading: Icon(
                  Icons.people,
                  size: 36,
                ),
                title: Text("联系人"),
                subtitle: Text("联系人信息 ${index + 1}"),
                trailing: Icon(Icons.arrow_forward_ios),
              );
            }, childCount: 100))
      ],
    );
  }
}

Flutter学习-滚动的Widget_第19张图片

3.2.2 SliverFixedExtentList

代码演练:

class SliverDemo4 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return CustomScrollView(
      slivers: [
        SliverFixedExtentList(
            delegate: SliverChildBuilderDelegate((BuildContext ctx, int index) {
              return ListTile(
                leading: Icon(
                  Icons.people,
                  size: 36,
                ),
                title: Text("联系人"),
                subtitle: Text("联系人信息 ${index + 1}"),
                trailing: Icon(Icons.arrow_forward_ios),
              );
            }),
            itemExtent: 100//设置Item的高度
        )
      ],
    );
  }
}

Flutter学习-滚动的Widget_第20张图片

3.2.3 SliverGrid

class SliverDemo3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return CustomScrollView(
      slivers: [
        SliverGrid(
          //这里和listView的使用是一样
            delegate: SliverChildBuilderDelegate((BuildContext ctx, int int) {
              return Container(
                  color: Color.fromARGB(255, Random().nextInt(256),
                      Random().nextInt(256), Random().nextInt(256)));
            }, childCount: 100),
            // 这里除了SliverGridDelegateWithFixedCrossAxisCount 还有 SliverGridDelegateWithMaxCrossAxisExtent
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              mainAxisSpacing: 8,
              crossAxisSpacing: 8,
            ))
      ],
    );
  }
}

Flutter学习-滚动的Widget_第21张图片

3.2.4 SliverPadding

上面提过一个问题,我们希望增加屏幕四周的边距,如果使用Padding组件的话,顶部的滑动会有一个边距问题,这里使用SliverPadding可以解决这个问题

滑动前:
Flutter学习-滚动的Widget_第22张图片

滑动后(我们可以发现向上滑动不会有白色间距了):
Flutter学习-滚动的Widget_第23张图片

3.2.5 SliverAppBar

这个组件的默认高度就是 AppBar的高读,如果我们不设置主题内的appBar属性,使用SliverAppBar组件我们也会出现一个导航栏

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            // appBar: AppBar(
            //   title: Text("图片widget"),
            // ),
            body: CustomScrollView(
      slivers: [
        SliverAppBar(
          expandedHeight: 200, //如果不设置就默认是appBar的高度
          title: Text("SliverAppBar"),
          pinned: true,
          flexibleSpace: FlexibleSpaceBar(
            title: Text("Hello World"),
            background: Image.asset("images/test.jpeg", fit: BoxFit.cover,),
          ),
        ),
        SliverPadding(
          padding: EdgeInsets.all(20),
          sliver: SliverGrid(
              //这里和listView的使用是一样
              delegate: SliverChildBuilderDelegate((BuildContext ctx, int int) {
                return Container(
                    color: Color.fromARGB(255, Random().nextInt(256),
                        Random().nextInt(256), Random().nextInt(256)));
              }, childCount: 100),
              // 这里除了SliverGridDelegateWithFixedCrossAxisCount 还有 SliverGridDelegateWithMaxCrossAxisExtent
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                mainAxisSpacing: 8,
                crossAxisSpacing: 8,
              )),
        )
      ],
    )));
  }
}

Flutter学习-滚动的Widget_第24张图片

3.2.6 SliverSafeArea

如果我们没有appBar属性的话,我们的内容可能会被顶部留海挡住,那么怎么解决这个问题,flutter中有一个安全区域组件safeArea表示在安全区域显示内容
Flutter学习-滚动的Widget_第25张图片

使用safeArea确实可以使我们的内容不被留海阻挡,但是我们发现,当列表像上滑动的时候,发现内容被安全区域阻挡了。这里Flutter提供了SliverSafeArea组件来解决这个问题

没有滑动之前:
Flutter学习-滚动的Widget_第26张图片

滑动之后:
Flutter学习-滚动的Widget_第27张图片

  • SliverSafeArea: 可以让内容滚到安全区域里面, safeArea则表示任何东西都不能进入安全区域

3.2.7 组合使用

现在我们有如下需求,我们界面中有一个头部视图、网格列表、水平列表

class SliverDemo6 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        //头部视图
        SliverAppBar(
          expandedHeight: 300,
          pinned: true,
          flexibleSpace: FlexibleSpaceBar(
            title: Text("Hello World"),
            background: Image.asset(
              "images/test.jpeg",
              fit: BoxFit.cover,
            ),
          ),
        ),
        //网格视图
        SliverGrid(
            //这里和listView的使用是一样
            delegate: SliverChildBuilderDelegate((BuildContext ctx, int int) {
              return Container(
                  color: Color.fromARGB(255, Random().nextInt(256),
                      Random().nextInt(256), Random().nextInt(256)));
            }, childCount: 6),
            // 这里除了SliverGridDelegateWithFixedCrossAxisCount 还有 SliverGridDelegateWithMaxCrossAxisExtent
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
            )),
        //水平列表
        SliverList(
            delegate: SliverChildBuilderDelegate((BuildContext ctx, int index) {
          return ListTile(
            title: Text("联系人"),
            subtitle: Text("联系人信息 ${index + 1}"),
            trailing: Icon(Icons.arrow_forward_ios),
          );
        }, childCount: 10))
      ],
    );
  }
}

Flutter学习-滚动的Widget_第28张图片

4. 监听滚动事件

对于滚动的视图,我们经常需要监听它的一些滚动事件,在监听到的时候去做对应的一些事情。
比如视图滚动到底部时,我们可能希望做上拉加载更多;
比如滚动到一定位置时显示一个回到顶部的按钮,点击回到顶部的按钮,回到顶部;
比如监听滚动什么时候开始,什么时候结束;
在Flutter中监听滚动相关的内容由两部分组成:ScrollController和ScrollNotification。

4.1 ScrollController

在Flutter中,Widget并不是最终渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常这种监听事件以及相关的信息并不能直接从Widget中获取,而是必须通过对应的Widget的Controller来实现。

ListView、GridView的组件控制器是ScrollController,我们可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。

另外,通常情况下,我们会根据滚动的位置来改变一些Widget的状态信息,所以ScrollController通常会和StatefulWidget一起来使用,并且会在其中控制它的初始化、监听、销毁等事件

我们来做一个案例,当滚动到1000位置的时候,显示一个回到顶部的按钮

  • jumpTo(double offset)animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
  • ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件

控制器监听滑动代码示例:

class ScrollViewListenDemo extends StatefulWidget {
  const ScrollViewListenDemo({Key? key}) : super(key: key);

  @override
  _ScrollViewListenDemoState createState() => _ScrollViewListenDemoState();
}

class _ScrollViewListenDemoState extends State<ScrollViewListenDemo> {
  //滑动的控制器
  ScrollController _scrollController = ScrollController();
  //是否显示按钮 ,便宜量大于1000时才显示按钮
  bool isShowFolatButton = false;

  @override
  void initState() {
    // TODO: implement initState
    //监听滑动
    _scrollController.addListener(() {
      print("List 在滑动-----${_scrollController.offset}");
      setState(() {
        isShowFolatButton = _scrollController.offset >= 1000 ;
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("滑动监听"),
        ),
        body: ListView.builder(
          itemBuilder: (BuildContext ctx, int index) {
            return Text(
              "Item: $index",
              style: TextStyle(fontSize: 24),
            );
          },
          itemCount: 100,
          itemExtent: 50,
          controller: _scrollController,
        ),
      floatingActionButton: isShowFolatButton ? FloatingActionButton(
        child: Icon(Icons.arrow_upward),
        onPressed: (){
          setState(() {
            //回到顶端
            _scrollController.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn);
          });
        },
      ) : null,
    );
  }
}

4.2 NotificationListener

如果我们希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener

  • NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
  • NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。
  • 该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为true时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续。

案例: 列表滚动, 并且在中间显示滚动进度

class ScrollviewNotificationDemo extends StatefulWidget {
  const ScrollviewNotificationDemo({Key? key}) : super(key: key);

  @override
  _ScrollviewNotificationDemoState createState() => _ScrollviewNotificationDemoState();
}

class _ScrollviewNotificationDemoState extends State<ScrollviewNotificationDemo> {
  int _progress = 0;
  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification){
        if (notification is ScrollStartNotification) {
          print("列表开始滑动*****");
        } else if (notification is ScrollUpdateNotification) {
          print("列表正在滑动中------");
          // 当前滚动的位置和总长度
          final currentPixel = notification.metrics.pixels;
          final totalPixel = notification.metrics.maxScrollExtent;
          double progress = currentPixel / totalPixel;
          setState(() {
            _progress = (progress * 100).toInt();
          });
        } else if (notification is ScrollEndNotification) {
          print("列表滑动结束********");
        }
        return true;
      },
      child: Stack(
        alignment: Alignment.bottomRight,
        children: [
          ListView.builder(
            itemBuilder: (BuildContext ctx, int index) {
              return Text(
                "Item: $index",
                style: TextStyle(fontSize: 24),
              );
            },
            itemCount: 100,
            itemExtent: 50,
          ),
          Positioned(
            right: 30,
            bottom: 30,
            child: CircleAvatar(
              radius: 30,
              child: Text("$_progress%"),
              backgroundColor: Colors.black54,
            ),
          )
        ],
      ),
    );
  }
}

Flutter学习-滚动的Widget_第29张图片

你可能感兴趣的:(Flutter,flutter)