zoukankan      html  css  js  c++  java
  • 【Flutter】可滚动组件之滚动控制和监听

    前言

    可以用ScrollController来控制可滚动组件的滚动位置。

    接口描述

    ScrollController({
        // 初始滚动位置
        double initialScrollOffset = 0.0,
        // 是否保持滚动位置
        this.keepScrollOffset = true,
        this.debugLabel,
      })
    
    

    代码示例

    // ScrollController
    
    // 可以用ScrollController来控制可滚动组件的滚动位置。
    
    import 'package:flutter/material.dart';
    
    class ScrollControllerTest extends StatefulWidget{
      @override
      ScrollControllerTestState createState() => ScrollControllerTestState();
    }
    
    class ScrollControllerTestState extends State<ScrollControllerTest>{
      ScrollController _controller = ScrollController();
      // 是否显示“返回到顶部”按钮
      bool showToTopBtn = false;
    
      @override
      void 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();
      }
    
      Widget build(BuildContext context){
        return Scaffold(
          appBar: AppBar(
            title: Text('滚动控制1'),
          ),
          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,
                // 返回顶部的过程中执行一个滚动动画,动画时间是200毫秒,动画曲线是Curves.ease
                duration: Duration(milliseconds: 200),
                curve: Curves.ease,
              );
            },
          ),
        );
      }
    
    }
    
    
    
    // 滚动监听
    class ScrollNotificationTest extends StatefulWidget{
      @override
      _ScrollNotificationTestState createState() => _ScrollNotificationTestState();
    }
    
    class _ScrollNotificationTestState extends State<ScrollNotificationTest>{
      //保持进度百分比
      String _progress = '0%';
    
      Widget build(BuildContext context){
        return Scaffold(
          appBar: AppBar(
            title: Text('滚动控制2'),
          ),
          // 进度条
          body: Scrollbar(
            // 监听滚动通知
            child: NotificationListener<ScrollNotification>(
              onNotification: (ScrollNotification notification){
                // pixels:当前滚动位置;maxScrollExtent:最大可滚动长度
                double progress = notification.metrics.pixels /
                    notification.metrics.maxScrollExtent;
                // 重新构建
                setState(() {
                  _progress = '${(progress * 100).toInt()}%';
                });
                // extentBefore:滑出ViewPort顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
                // extentInside:ViewPort内部长度;此示例中屏幕显示的列表部分的长度。
                // extentAfter:列表中未滑入ViewPort部分的长度;此示例中列表底部未显示到屏幕范围部分的长度。
                // atEdge:是否滑到了可滚动组件的边界(此示例中相当于列表顶或底部)。
                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,
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
    
    

    总结

    滚动位置恢复

    PageStorage是一个用于保存页面(路由)相关数据的组件,它并不会影响子树的UI外观,其实,PageStorage是一个功能型组件,它拥有一个存储桶(bucket),子树中的Widget可以通过指定不同的PageStorageKey来存储各自的数据或状态。
    每次滚动结束,可滚动组件都会将滚动位置offset存储到PageStorage中,当可滚动组件重新创建时再恢复。如果ScrollController.keepScrollOffset为false,则滚动位置将不会被存储,可滚动组件重新创建时会使用ScrollController.initialScrollOffset;ScrollController.keepScrollOffset为true时,可滚动组件在第一次创建时,会滚动到initialScrollOffset处,因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略。
    当一个路由中包含多个可滚动组件时,如果你发现在进行一些跳转或切换操作后,滚动位置不能正确恢复,这时你可以通过显式指定PageStorageKey来分别跟踪不同的可滚动组件的位置。

    ScrollPosition

    ScrollPosition是用来保存可滚动组件的滚动位置的。一个ScrollController对象可以同时被多个可滚动组件使用,ScrollController会为每一个可滚动组件创建一个ScrollPosition对象,这些ScrollPosition保存在ScrollController的positions属性中(List)。ScrollPosition是真正保存滑动位置信息的对象,offset只是一个便捷属性。
    一个ScrollController虽然可以对应多个可滚动组件,但是有一些操作,如读取滚动位置offset,则需要一对一!但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置。ScrollPosition有两个常用方法:animateTo() 和 jumpTo(),它们是真正来控制跳转滚动位置的方法,ScrollController的这两个同名方法,内部最终都会调用ScrollPosition的。

    ScrollController控制原理

    ScrollController的另外三个方法:

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

    当ScrollController和可滚动组件关联时,可滚动组件首先会调用ScrollController的createScrollPosition()方法来创建一个ScrollPosition来存储滚动位置信息,接着,可滚动组件会调用attach()方法,将创建的ScrollPosition添加到ScrollController的positions属性中,这一步称为“注册位置”,只有注册后animateTo() 和 jumpTo()才可以被调用。
    当可滚动组件销毁时,会调用ScrollController的detach()方法,将其ScrollPosition对象从ScrollController的positions属性中移除,这一步称为“注销位置”,注销后animateTo() 和 jumpTo() 将不能再被调用。
    需要注意的是,ScrollController的animateTo() 和 jumpTo()内部会调用所有ScrollPosition的animateTo() 和 jumpTo(),以实现所有和该ScrollController关联的可滚动组件都滚动到指定的位置。

    滚动监听

    Flutter Widget树中子Widget可以通过发送通知(Notification)与父(包括祖先)Widget通信。父级组件可以通过NotificationListener组件来监听自己关注的通知,这种通信方式类似于Web开发中浏览器的事件冒泡。
    可滚动组件在滚动时会发送ScrollNotification类型的通知,ScrollBar正是通过监听滚动通知来实现的。通过NotificationListener监听滚动事件和通过ScrollController有两个主要的不同:

    1. 通过NotificationListener可以在从可滚动组件到widget树根之间任意位置都能监听。而ScrollController只能和具体的可滚动组件关联后才可以。
    2. 收到滚动事件后获得的信息不同;NotificationListener在收到滚动事件时,通知中会携带当前滚动位置和ViewPort的一些信息,而ScrollController只能获取当前滚动位置。
  • 相关阅读:
    Sendkeys 和 Sendmessage 使用技巧一例
    和菜鸟一起学算法之二分法求极值问题
    和菜鸟一起学算法之三分法求极值问题
    和菜鸟一起学证券投资之国内生产总值GDP
    和菜鸟一起学OK6410之Led字符驱动
    和菜鸟一起学OK6410之最简单驱动模块hello world
    和菜鸟一起学OK6410之交叉编译hello world
    和菜鸟一起学android4.0.3源码之touchscreen配置+调试记录
    和菜鸟一起学android4.0.3源码之红外遥控器适配
    和菜鸟一起学OK6410之最简单字符驱动
  • 原文地址:https://www.cnblogs.com/parzulpan/p/12195948.html
Copyright © 2011-2022 走看看