一,概述

  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 = ;
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 + ,//列表长度+底部加载中提示
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: ), () {
setState(() {
isLoading = false;
offState = true;
list = List.generate(, (i) {
return ItemInfo("ListView的一行数据$i");
});
});
});
} /**
* 模拟到底部加载更多数据
*/
void getMoreData() async {
if (isLoading) {
return;
}
setState(() {
isLoading = true;
page++;
});
print('上拉刷新开始,page = $page');
await Future.delayed(Duration(seconds: ), () {
setState(() {
isLoading = false;
showMore = false;
list.addAll(List.generate(, (i) {
return ItemInfo("上拉添加ListView的一行数据$i");
}));
print('上拉刷新结束,page = $page');
});
});
} /**
* 模拟下拉刷新
*/
Future < void > _onRefresh() async {
if (isLoading) {
return;
}
setState(() {
isLoading = true;
page = ;
}); print('下拉刷新开始,page = $page'); await Future.delayed(Duration(seconds: ), () {
setState(() {
isLoading = false; List tempList = List.generate(, (i) {
return ItemInfo("下拉添加ListView的一行数据$i");
});
tempList.addAll(list);
list = tempList;
print('下拉刷新结束,page = $page');
});
});
}
}

【Flutter学习】基本组件之上下刷新列表(一)的更多相关文章

  1. 【Flutter学习】组件通信(父子、兄弟)

    一,概述 flutter一个重要的特性就是组件化.组件分为两种状态,一种是StatefulWidget有状态组件,一种是StatelessWidget无状态组件. 无状态组件不能更新状态,有状态组件具 ...

  2. 【Flutter学习】组件学习之目录

    01. Flutter组件-Layout-Container-容器  02. Flutter组件-Text-Text-文本  03. Flutter组件-Text-RichText-富文本  04. ...

  3. Flutter学习笔记(12)--列表组件

    如需转载,请注明出处:Flutter学习笔记(12)--列表组件 在日常的产品项目需求中,经常会有列表展示类的需求,在Android中常用的做法是收集数据源,然后创建列表适配器Adapter,将数据源 ...

  4. 【Flutter学习】基本组件之基本列表ListView组件

    一,概述 列表是前端最常见的需求. 在flutter中,用ListView来显示列表页,支持垂直和水平方向展示,通过一个属性我们就可以控制其方向,列别有以下分类 水平列表 垂直列表 数据量非常大的列表 ...

  5. Flutter学习笔记(25)--ListView实现上拉刷新下拉加载

    如需转载,请注明出处:Flutter学习笔记(25)--ListView实现上拉刷新下拉加载 前面我们有写过ListView的使用:Flutter学习笔记(12)--列表组件,当列表的数据非常多时,需 ...

  6. Flutter学习六之实现一个带筛选的列表页面

    上期实现了一个网络轮播图的效果,自定义了一个轮播图组件,继承自StatefulWidget,我们知道Flutter中并没有像Android中activity的概念.页面见的跳转是通过路由从一个全屏组件 ...

  7. Flutter学习笔记(11)--文本组件、图标及按钮组件

    如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 文本组件 文本组件(text)负责显示文本和定义显示样式,下表为text常见属性 Text组件属性及描述 属性名 类型 默认 ...

  8. Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解

    如需转载,请注明出处:Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解 最近一段时间生病了,整天往医院跑,也没状态学东西了,现在是好了不少了,也该继续学习啦!!! ...

  9. Flutter学习笔记(16)--Scaffold脚手架、AppBar组件、BottomNavigationBar组件

    如需转载,请注明出处:Flutter学习笔记(15)--MaterialApp应用组件及routes路由详解 今天的内容是Scaffold脚手架.AppBar组件.BottomNavigationBa ...

随机推荐

  1. PHP curl_multi_getcontent函数

    curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流 说明 string curl_multi_getcontent ( r ...

  2. 「长乐集训 2017 Day8」修路 (斯坦纳树)

    题目描述 村子间的小路年久失修,为了保障村子之间的往来,AAA君决定带领大家修路. 村子可以看做是一个边带权的无向图GGG, GGG 由 nnn 个点与 mmm 条边组成,图中的点从 1∼n1 \si ...

  3. win7搭建局域网时间服务器

    近日,本人想在局域网内通过普通的windows 7 PC搭建一台NTP服务器,可看似简单的配置却给我捣腾了了半天.初期,参考了互联网的上相关的配置文档,可网络设备就是死活不同步NTP服务器的时间.实在 ...

  4. ElasticSearch 简介概念及核心

    1.ES是什么 ES是面向文档的Nosql,这意味着它可以存储整个对象或文档.然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索.在es中,你可以对文档(而非成行成列的数据)进行索 ...

  5. Linux基本常用命令|ubuntu获取root权限

    我用的是ubuntu12.4系统,因为默认是没有获取root的权限的 下边讲解怎么获取root权限 在终端中输入: sudo passwd root Enter new UNIX password: ...

  6. mysql的windows客户端链接远程全套案例

    我是linux 的服务器,navicat12的客户端, 开始链接的时候需要开服务器上得对外爆漏端口 3306,方法: 添加指定需要开放的端口: firewall-cmd --add-port=/tcp ...

  7. python的列表 元组 字典

    列表和元组都是序列,是数据元素的集合,数据元素可以是数值.字符串,布尔值.对象等. 一.列表:用方括号定义[] 空列表 names = [] 带值的列表 names = ["bill&quo ...

  8. Linux随笔 - linux 多个会话同时执行命令后history记录不全的解决方案【转载】

    基本认识linux默认配置是当打开一个shell终端后,执行的所有命令均不会写入到~/.bash_history文件中,只有当前用户退出后才会写入,这期间发生的所有命令其它终端是感知不到的. 问题场景 ...

  9. MDX members使用

    Members (Set) 函数返回该指定层次结构内所有成员(不包括计算成员)的集: Members (String) 函数返回已指定名称的单个成员. 通常,将 Members (String) 函数 ...

  10. 关于JAVA的环境变量和那些jar包

    大家配置环境变量一般都是 JAVA_HOME:C:\Program Files (x86)\Java\jdk1.6.0_30; PATH:%JAVA_HOME%\bin; CLASSPATH:.;%J ...