2048
登录
没  有  难  学  的  前  端
登 录
×
<返回上一级

Flutter 滚动控件篇-->滚动监听及控制(ScrollController)

Flutter作者:他说姓程

在前面的滚动控件篇的文章中,我们提到了controller属性,他接收一个S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller对象。S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller的主要作用是控制滚动位置和监听滚动事件。

本章以ListView为例,展示一下S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller的具体用法

S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller

S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller控制滚动位置和监听滚动事件。

源码示例

构造函数如下遇新是直朋能到

S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller({
  double initialScrollOffset = 0.0, //初始滚动位置
  this.keepScrollOffset = true,//是否保存滚动位置
  ...
})
复制代码

属性解释

上面已经表明了具体属性的意思,这里介绍一下S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller常用的属性和方法

offset

用能境战求道,重件开又是正易里是了些之框属性表示可滚动组件当前的滚动位求圈分件圈浏第用代是水刚道。的它还

j需朋者说上事是础一发一开程和开数的目前间umpTo、anima新直能分支调二浏页器朋代说,事刚teTo

jumpTo(double offset)animateTo(double offset,...):这两个方法都用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。

滚动监听

S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller间接继承自Listenable,我们可以根据S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller来监听滚动事件,

例如:

controller.addListener(()=>print(controller.offset))
复制代码

用能境战求道,重件开又是正易里是了些之框段代码就可以打印出当前滚动的位求圈分件圈浏第用代是水刚道。的它还

代码示例:

我们创建一个ListView,当滚动位置发生变化时,我们先打印出当前滚动位置,然后判断当前位置是否超过1000像素,如果超过则在屏幕右下角显示一个“返回顶部”的按钮,该按钮点击后可以使ListView恢复到初始位置;如果没有超过1000像素,则隐藏“返回顶部”按钮。

import 'package:flutter/material.dart';

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

class _CategoryPageState extends State<CategoryPage> {
  S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller _controller = new S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller();
  bool showToTopBtn = false; //是否显示“返回到顶部”按钮

  @override
  void initState() {
    //监听滚动事件,打印滚动位置
    _controller.addListener(() {
      print(_controller.offset); //打印滚动位置
      if (_controller.offset < 1000 && showToTopBtn) {
        setState(() {
          showToTopBtn = false;
        });
      } else if (_controller.offset >= 1000 && showToTopBtn == false) {
        setState(() {
          showToTopBtn = true;
        });
      }
    });
  }

  @override
  void dispose() {
    //为了避免内存泄露,需要调用_controller.dispose
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("滚动控制")),
      body: Scrollbar(
        child: ListView.builder(
            itemCount: 100,
            itemExtent: 50.0, //列表项高度固定时,显式指定高度是一个好习惯(性能消耗小)
            controller: _controller,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text("$index"),
              );
            }),
      ),
      floatingActionButton: !showToTopBtn
          ? null
          : FloatingActionButton(
              child: Icon(Icons.arrow_upward),
              onPressed: () {
                //返回到顶部时执行动画
                _controller.animateTo(.0,
                    duration: Duration(milliseconds: 200), curve: Curves.ease);
              }),
    );
  }
}
复制代码

由于列表项高度为50像素,当滑动到第20个列表项后,右下角“返回顶部”按钮会显示,点击该按钮,ListView会在返回顶部的过程中执行一个滚动动画,动画时间是200毫秒,动画曲线是Curves.ease

运行效果:

图片加载失败!

滚动位置恢复

PageStorage是一个用于保存页面(路由)相关数据的组件,它并不会影响子树的UI外观。

每次滚动结束,可滚动组件都会将滚动位置offset存储到PageStorage中,当可滚动组件重新创建时再恢复。

如果S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller.keepScrollOffsetfalse,则滚动位置将不会被存储,可滚动组件重新创建时会使用S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller.initialScrollOffset
S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller.keepScrollOffsettrue时,可滚动组件在第一次创建时,会滚动到initialScrollOffset处,因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略。

当一个路由中包含多个可滚动组件时,如果你发现在进行一些跳转或切换操作后,滚动位置不能正确恢复,这时你可以通过显式指定PageStorageKey来分别跟踪不同的可滚动组件的位置,
如:

ListView(key: PageStorageKey(1), ... );
...
ListView(key: PageStorageKey(2), ... );
复制代码

不同的PageStorageKey,需要不同的值,这样才可以为不同可滚动组件保存其滚动位置。

注意:一个路由中包含多个可滚动组件时,如果要分别跟踪它们的滚动位置,并非一定就得给他们分别提供PageStorageKey。这是因为Scrollable本身是一个StatefulWidget,它的状态中也会保存当前滚动位置,所以,只要可滚动组件本身没有被从树上detach掉,那么其State就不会销毁(dispose),滚动位置就不会丢失。只有当Widget发生结构变化,导致可滚动组件的State销毁或重新构建时才会丢失状态,这种情况就需要显式指定PageStorageKey,通过PageStorage来存储滚动位置,一个典型的场景是在使用TabBarView时,在Tab发生切换时,Tab页中的可滚动组件的State就会销毁,这时如果想恢复滚动位置就需要指定PageStorageKey

ScrollPositi新直能分支调二浏页器朋代说,on

ScrollPositi新直能分支调二浏页器朋代说,on是用来保存可滚动组件的滚动位置的。

一个S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller对象可以同时被多个可滚动组件使用,S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller会为每一个可滚动组件创建一个ScrollPositi新直能分支调二浏页器朋代说,on对象,这些ScrollPositi新直能分支调二浏页器朋代说,on保存在S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚llerpositions属性中。

ScrollPositi新直能分支调二浏页器朋代说,on是真正保存滑动位置信息的对象,offset只是一个便捷属性:
double get offset => position.pixels;
从上面可以看出offect是来自于position的。

一个S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller虽然可以对应多个可滚动组件,但是有一些操作,如读取滚动位置offset,则需要一对一!
但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置,
举个例子,假设一个S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller同时被两个可滚动组件使用,那么我们可以通过如下方式分别读取他们的滚动位置:

...
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
...
复制代码

我们可以通过controller.positions.length来确定controller被几个可滚动组件使用。

ScrollPositi新直能分支调二浏页器朋代说,on有两个常用方法:animateTo()jumpTo(),它们是真正来控制跳转滚动位置的方法,S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller的这两个同名方法,内部最终都会调用ScrollPositi新直能分支调二浏页器朋代说,on的。

需要注意的是,S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚lleranimateTo()jumpTo()内部会调用所有ScrollPositi新直能分支调二浏页器朋代说,onanimateTo()jumpTo(),以实现所有和该S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller关联的可滚动组件都滚动到指定的位置。

滚动监听示例

下面,我们监听ListView的滚动通知,然后显示当前滚动进度百分比:

import 'package:flutter/material.dart';

class ScrollNotificationTestRoute extends StatefulWidget {
  @override
  _ScrollNotificationTestRouteState createState() =>
      new _ScrollNotificationTestRouteState();
}

class _ScrollNotificationTestRouteState
    extends State<ScrollNotificationTestRoute> {
  String _progress = "0%"; //保存进度百分比

  @override
  Widget build(BuildContext context) {
    return Scrollbar( //进度条
      // 监听滚动通知
      child: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification notification) {
          double progress = notification.metrics.pixels /
              notification.metrics.maxScrollExtent;
          //重新构建
          setState(() {
            _progress = "${(progress * 100).toInt()}%";
          });
          print("BottomEdge: ${notification.metrics.extentAfter == 0}");
          //return true; //放开此行注释后,进度条将失效
        },
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            ListView.builder(
                itemCount: 100,
                itemExtent: 50.0,
                itemBuilder: (context, index) {
                  return ListTile(title: Text("$index"));
                }
            ),
            CircleAvatar(  //显示进度百分比
              radius: 30.0,
              child: Text(_progress),
              backgroundColor: Colors.black54,
            )
          ],
        ),
      ),
    );
  }
}
复制代码

运行效果:

图片加载失败!

Flutter Widget树中子Widget可以通过发送通知(Notification)与父(包括祖先)Widget通信。父级组件可以通过NotificationListener组件来监听自己关注的通知,这种通信方式类似于Web开发中浏览器的事件冒泡。

可滚动组件在滚动时会发送ScrollNotification类型的通知,ScrollBar正是通过监听滚动通知来实现的。通过NotificationListener监听滚动事件和通过S需朋者说上事是础一发一开程和开数的目前间crollContro新直能分支调二浏页器朋代说,事刚ller有两个主要的不同:

在接收到滚动事件时,参数类型为ScrollNotification,它包括一个metrics属性,它的类型是ScrollMetrics,该属性包含当前ViewPort及滚动位置等信息:


P_P

本文来源于网络:查看 >
« 上一篇:松软科技web课堂:JavaScript 注释
» 下一篇:Flutter 滚动控件篇-->ListView
评论
点击刷新
评论
相关博文

分享“案例”中大奖

开始分享 中奖规则
分享链接:
联系方式:
2021-01-23中奖名单(每日10名)
×添加代码片段