zoukankan      html  css  js  c++  java
  • 【Flutter学习】基本组件之上下刷新列表(一)

    一,概述

      RefreshIndicator是Flutter基于Material设计语言内置的控件,集合了下拉手势、加载指示器和刷新操作一体,可玩性比FutureBuilder差了一大截,不过大家也用过Material设计语言的其他控件,视觉效果也不赖的。
      要实现拉刷新列表的功能仅仅依靠RefreshIndicator还不行,我们还需要ScrollController对ListView的移动偏移量进行监控。

    二,两个重要的组件

    • RefreshIndicator
      • 构造函数
        /**
         * 下拉刷新组件
         *const RefreshIndicator
            ({
            Key key,
            @required this.child,
            this.displacement: 40.0, //触发下拉刷新的距离
            @required this.onRefresh, //下拉回调方法,方法需要有async和await关键字,没有await,刷新图标立马消失,没有async,刷新图标不会消失
            this.color, //进度指示器前景色 默认为系统主题色
            this.backgroundColor, //背景色
            this.notificationPredicate: defaultScrollNotificationPredicate,
            })
         */

        注意

        • RefreshIndicator的子元素必须是一个可以滚动的控件
        • 如果你遇到不符合条件的控件,请将其用可以滚动的控件(如ListView、PageView等)包装一下
        • onRefresh的回调函数必须是Future<Null>类型
    • ScrollController
      • 构造函数
        ScrollController({
          double initialScrollOffset = 0.0, //初始滚动位置
          this.keepScrollOffset = true,//是否保存滚动位置
          ...
        })
      • 属性和方法

        • offset:可滚动Widget当前滚动的位置。
        • jumpTo(double offset)、animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
        • 滚动监听(addListener(listener))

          ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件。如:

          controller.addListener(()=>print(controller.offset))
        • 滚动位置恢复(keepScrollOffset,initialScrollOffset)

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

          每次滚动结束,Scrollable Widget都会将滚动位置offset存储到PageStorage中,当Scrollable Widget 重新创建时再恢复。如果ScrollController.keepScrollOffsetfalse,则滚动位置将不会被存储,Scrollable Widget重新创建时会使用ScrollController.initialScrollOffset;ScrollController.keepScrollOffsettrue时,Scrollable Widget在第一次创建时,会滚动到initialScrollOffset处,因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略。

        • 滚动监听

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

          1. 通过NotificationListener可以在从Scrollable Widget到Widget树根之间任意位置都能监听。而ScrollController只能和具体的Scrollable Widget关联后才可以。
          2. 收到滚动事件后获得的信息不同;NotificationListener在收到滚动事件时,通知中会携带当前滚动位置和ViewPort的一些信息,而ScrollController只能获取当前滚动位置。

          NotificationListener

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

      • ScrollController控制原理

        我们来介绍一下ScrollController的另外三个方法:

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

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

        需要注意的是,ScrollController的animateTo() 和 jumpTo()内部会调用所有ScrollPositionanimateTo() 和 jumpTo(),以实现所有和该ScrollController关联的Scrollable Widget都滚动到指定的位置。

    三,下拉加载,上拉刷新实现

    class Widget_RefreshIndicator_State extends State<Widget_RefreshIndicator_Page> {
      var list = [];
      int page = 0;
      bool isLoading = false;//是否正在请求新数据
      bool showMore = false;//是否显示底部加载中提示
      bool offState = false;//是否显示进入页面时的圆形进度条
     
      ScrollController scrollController = ScrollController();
     
      @override
      void initState() {
        super.initState();
        scrollController.addListener(() {
          if (scrollController.position.pixels ==
              scrollController.position.maxScrollExtent) {
            print('滑动到了最底部${scrollController.position.pixels}');
            setState(() {
              showMore = true;
            });
            getMoreData();
          }
        });
        getListData();
      }
     
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
              appBar: AppBar(
                title: Text("RefreshIndicator"),
              ),
              body: Stack(
                children: <Widget>[
                  RefreshIndicator(
                    child: ListView.builder(
                      controller: scrollController,
                      itemCount: list.length + 1,//列表长度+底部加载中提示
                      itemBuilder: choiceItemWidget,
                    ),
                    onRefresh: _onRefresh,
                  ),
                  Offstage(
                    offstage: offState,
                    child: Center(
                      child: CircularProgressIndicator(),
                    ),
                  ),
                ],
              )
          ),
        );
      }
     
      @override
      void dispose() {
        super.dispose();
        //手动停止滑动监听
        scrollController.dispose();
      }
     
      /**
       * 加载哪个子组件
       */
      Widget choiceItemWidget(BuildContext context, int position) {
        if (position < list.length) {
          return HomeListItem(position, list[position], (position) {
            debugPrint("点击了第$position条");
          });
        } else if (showMore) {
          return showMoreLoadingWidget();
        }else{
          return null;
        }
      }
     
      /**
       * 加载更多提示组件
       */
      Widget showMoreLoadingWidget() {
        return Container(
          height: 50.0,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Text('加载中...', style: TextStyle(fontSize: 16.0),),
            ],
          ),
        );
      }
     
      /**
       * 模拟进入页面获取数据
       */
      void getListData() async {
        if (isLoading) {
          return;
        }
        setState(() {
          isLoading = true;
        });
        await Future.delayed(Duration(seconds: 3), () {
          setState(() {
            isLoading = false;
            offState = true;
            list = List.generate(20, (i) {
              return ItemInfo("ListView的一行数据$i");
            });
          });
        });
      }
     
      /**
       * 模拟到底部加载更多数据
       */
      void getMoreData() async {
        if (isLoading) {
          return;
        }
        setState(() {
          isLoading = true;
          page++;
        });
        print('上拉刷新开始,page = $page');
        await Future.delayed(Duration(seconds: 3), () {
          setState(() {
            isLoading = false;
            showMore = false;
            list.addAll(List.generate(3, (i) {
              return ItemInfo("上拉添加ListView的一行数据$i");
            }));
            print('上拉刷新结束,page = $page');
          });
        });
      }
     
      /**
       * 模拟下拉刷新
       */
      Future < void > _onRefresh() async {
        if (isLoading) {
          return;
        }
        setState(() {
          isLoading = true;
          page = 0;
        });
     
        print('下拉刷新开始,page = $page');
     
        await Future.delayed(Duration(seconds: 3), () {
          setState(() {
            isLoading = false;
     
            List tempList = List.generate(3, (i) {
              return ItemInfo("下拉添加ListView的一行数据$i");
            });
            tempList.addAll(list);
            list = tempList;
            print('下拉刷新结束,page = $page');
          });
        });
      }
    }
  • 相关阅读:
    Atitit 安全措施流程法 目录 1. 常见等安全措施方法 2 1.1. 安全的语言 代码法,编译型 java 2 1.2. 安全编码法 2 1.3. 安全等框架类库 api 2 1.4. 加密法
    Atitit api与安全措施法 目录 1.1. 模板替换 sprintf %f %d 数字小数字段格式化转换校验法 1 2.  $pdo->exec 与query 2 2.1. 数字校
    Atitit 安全审计法 目录 1. 安全审计数据结构 1 2. Expame 提现流程 1 2.1. 获取提现钱的数据余额 1 2.2. 扣去余额 1 2.3. 开始safe log 2 2.4.
    Atitit 防注入 sql参数编码法 目录 1.2. 提升可读性pg_escape_literal — 转义文字以插入文本字段 1 1.2.1. 说明 1 1.3. 推荐pg_escape_str
    Atitit aes 加密法php实现
    Atitit 登录票据安全法 目录 1.1. cookie对象规范 1 1.2. Cookie加解密 1 1.3. Cookie密文动态更换,根据一个时间3天比如 1 1.4. 服务端撤销key 1
    atitit 2010 2010 diary log events memorabilia v4 tbb No finish , wait to finish ***Mon8-11 cant
    Atitit 安全流程法 目录 1. 常见等安全措施方法 2 1.1. 安全的语言 代码法,编译型 java 2 1.2. 安全编码法 2 1.3. 安全等框架类库 api 2 1.4. 加密法 2
    Atitit 数据查询法 目录 1. 数据查询语言QL (推荐) 1 1.1. Sql 1 1.2. 对象查询语言(OQL) 1 1.3. Atitit QL查询语言总结Jpql Ongl
    Atitit json数据查询法 jsonpath 目录 1.1. 1.概述 1 1.2. 3.2。经营者特殊符号 1 1.3. # JSONPath expressions 2 1.4. Xpa
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11155005.html
Copyright © 2011-2022 走看看