ScrollController

ScrollController结构函数如下:

ScrollController({
  double initialScrollOffset = 0.0, //初始翻滚方位
  this.keepScrollOffset = true,//是否保存翻滚方位
  ...
})

咱们介绍一下ScrollController常用的特点和办法:

  • offset:可翻滚组件当时的翻滚方位。
  • jumpTo(double offset)、animateTo(double offset,…):这两个办法用于跳转到指定的方位,它们不同之处在于,后者在跳转时会履行一个动画,而前者不会。

ScrollController还有一些特点和办法,咱们将在后面原理部分解释。

翻滚监听

ScrollController直接继承自Listenable,咱们能够依据ScrollController来监听翻滚事情,如:

controller.addListener(()=>print(controller.offset))

翻滚监听示例

咱们创立一个ListView,当翻滚方位产生变化时,咱们先打印出当时翻滚方位,然后判断当时方位是否超越1000像素,假如超越则在屏幕右下角显现一个“回来顶部”的按钮,该按钮点击后能够使ListView康复到初始方位;假如没有超越1000像素,则隐藏“回来顶部”按钮。代码如下:

import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/1/19 10:46 下午
/// @Description:
class MyScrollController extends StatefulWidget {
  const MyScrollController({Key? key}) : super(key: key);
  @override
  _MyScrollControllerState createState() => _MyScrollControllerState();
}
class _MyScrollControllerState extends State<MyScrollController> {
  final ScrollController _controller = ScrollController();
  bool showToTopBtn = false; //是否显现“回来到顶部”按钮
  @override
  void initState() {
    // TODO: implement initState
    super.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: getAppBar('翻滚监听以及操控'),
      body: _buildScollbar(),
      floatingActionButton: showToTopBtn==false?null:FloatingActionButton(
        onPressed: (){
          //回来到顶部时候履行动画
          _controller.animateTo(0, duration: const Duration(milliseconds: 200), curve: Curves.easeIn);
        },
        child: const Icon(Icons.arrow_upward),),
    );
  }
  _buildScollbar(){
    return Scrollbar(
        child: ListView.builder(
          controller: _controller,
            itemCount: 100,
            itemExtent: 44,
            itemBuilder: (context,index){
          return ListTile(title: Text('$index'),);
        })
    );
  }
}

运转效果:

33、Flutter之ScrollController滚动监听及控制
由于列表项高度为 44 像素,当滑动到第 20+ 个列表项后,右下角 “回来顶部” 按钮会显现,点击该按钮,ListView 会在回来顶部的过程中履行一个翻翻滚画,动画时间是 200 毫秒,动画曲线是 Curves.ease

翻滚方位康复

PageStorage是一个用于保存页面(路由)相关数据的组件,它并不会影响子树的UI外观,其实,PageStorage是一个功能型组件,它拥有一个存储桶(bucket),子树中的Widget能够经过指定不同的PageStorageKey来存储各自的数据或状况。

每次翻滚完毕,可翻滚组件都会将翻滚方位offset存储到PageStorage中,当可翻滚组件从头创立时再康复。假如ScrollController.keepScrollOffset为false,则翻滚方位将不会被存储,可翻滚组件从头创立时会运用ScrollController.initialScrollOffsetScrollController.keepScrollOffset为true时,可翻滚组件在第一次创立时,会翻滚到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就会毁掉,这时假如想康复翻滚方位就需求指定

ScrollPosition

ScrollPosition是用来保存可翻滚组件的翻滚方位的。一个ScrollController目标能够同时被多个可翻滚组件运用,ScrollController会为每一个可翻滚组件创立一个ScrollPosition目标,这些ScrollPosition保存在ScrollControllerpositions特点中(List<ScrollPosition>)。ScrollPosition是真正保存滑动方位信息的目标,offset只是一个快捷特点:

double get offset => position.pixels;

一个ScrollController虽然能够对应多个可翻滚组件,可是有一些操作,如读取翻滚方位offset,则需求1对1!可是咱们仍然能够在一对多的情况下,经过其它办法读取翻滚方位,举个例子,假定一个ScrollController同时被两个可翻滚组件运用,那么咱们能够经过如下办法别离读取他们的翻滚方位:

...
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
...    

咱们能够经过controller.positions.length来确定controller被几个可翻滚组件运用。

ScrollPosition的办法

ScrollPosition有两个常用办法:animateTo()jumpTo(),它们是真正来操控跳转翻滚方位的办法,ScrollController的这两个同名办法,内部最终都会调用ScrollPosition的。

ScrollController操控原理

咱们来介绍一下ScrollController的别的三个办法:

ScrollPosition createScrollPosition(
    ScrollPhysics physics,
    ScrollContext context,
    ScrollPosition oldPosition);
void attach(ScrollPosition position) ;
void detach(ScrollPosition position) ;

ScrollController和可翻滚组件相关时,可翻滚组件首先会调用ScrollControllercreateScrollPosition()办法来创立一个ScrollPosition来存储翻滚方位信息,接着,可翻滚组件会调用attach()办法,将创立的ScrollPosition添加到ScrollControllerpositions特点中,这一步称为“注册方位”,只有注册后animateTo()jumpTo()才能够被调用。

当可翻滚组件毁掉时,会调用ScrollControllerdetach()办法,将其ScrollPosition目标从ScrollControllerpositions特点中移除,这一步称为“刊出方位”,刊出后animateTo()jumpTo() 将不能再被调用。

需求注意的是,ScrollControlleranimateTo()jumpTo()内部会调用一切ScrollPositionanimateTo()jumpTo(),以完成一切和该ScrollController相关的可翻滚组件都翻滚到指定的方位。

翻滚监听

下面,咱们监听ListView的翻滚告诉,然后显现当时翻滚进展百分比:

import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/1/19 11:21 下午
/// @Description: 
class MyScrollcontroller2 extends StatefulWidget {
  const MyScrollcontroller2({Key? key}) : super(key: key);
  @override
  _MyScrollcontroller2State createState() => _MyScrollcontroller2State();
}
class _MyScrollcontroller2State extends State<MyScrollcontroller2> {
  String _progress ='0%';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar("翻滚监听"),
      body: 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 false;          //return true; //放开此行注释后,进展条将失效
        },
        child: Stack(
          alignment: Alignment.center,
          children: [
            ListView.builder(
              itemCount: 100,
                itemExtent: 50,
                itemBuilder: (context,index){
                return ListTile(title: Text('$index'),);
                }),
            CircleAvatar(
              radius: 30,
              child: Text(_progress),
              backgroundColor: Colors.black54,
            )
          ],
        ),
      ),
    );
  }
}

运转成果:

33、Flutter之ScrollController滚动监听及控制

在接收到翻滚事情时,参数类型为ScrollNotification,它包括一个metrics特点,它的类型是ScrollMetrics,该特点包括当时ViewPort及翻滚方位等信息:

  • pixels:当时翻滚方位。
  • maxScrollExtent:最大可翻滚长度。
  • extentBefore:滑出ViewPort顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
  • extentInsideViewPort内部长度;此示例中屏幕显现的列表部分的长度。
  • extentAfter:列表中未滑入ViewPort部分的长度;此示例中列表底部未显现到屏幕规模部分的长度。
  • atEdge:是否滑到了可翻滚组件的边界(此示例中相当于列表顶或底部)。

ScrollMetrics还有一些其它特点,能够自行查阅API文档。

具体的官方文档地址:api.flutter.dev/flutter/wid…

33、Flutter之ScrollController滚动监听及控制

官方文档解释 操控可翻滚小部件。

翻滚操控器一般作为成员变量存储在State目标中,并在每个State.build中重用。单个翻滚操控器可用于操控多个可翻滚小部件,但有些操作(如读取翻滚偏移量)要求操控器与单个可翻滚小部件一起运用。

翻滚操控器创立一个ScrollPosition来办理特定于单个可翻滚小部件的状况。要运用自定义的ScrollPosition,子类化ScrollController并重写createScrollPosition。

ScrollController是一个Listenable。当附加的任何scrollposition告诉它们的侦听器时(即当它们中的任何一个翻滚时),它会告诉它的侦听器。当附加的scrollposition列表产生变化时,它不会告诉侦听器。

一般与ListView, GridView, CustomScrollView一起运用。

参见: ListView, GridView, CustomScrollView,它们能够由ScrollController操控。 Scrollable,它是较低层的小部件,用于创立ScrollPosition目标和ScrollController目标并将它们相关起来。 PageController,它是操控PageView的一个类似目标。 ScrollPosition,用于办理单个翻滚小部件的翻滚偏移量。 ScrollNotification和NotificationListener,它们可用于监视翻滚方位,而无需运用ScrollController。