下拉刷新
在Flutter中系统已经为我们提供了google material design的刷新功能 , 样式与原生Android一样.
我们可以使用RefreshIndicator组件来实现Flutter中的下拉刷新,下面们还是先来看下如何使用吧
RefreshIndicator
构造方法:
const RefreshIndicator({Key key,@required this.child,this.displacement: 40.0,//触发下拉刷新的距离@required this.onRefresh,//下拉回调方法this.color, //进度指示器前景色 默认为系统主题色this.backgroundColor, //背景色this.notificationPredicate: defaultScrollNotificationPredicate,})
然后我们看一下效果以及实现方式:
然后我们看一下代码:
class _MyHomePageState extends State<MyHomePage> {List list = new List(); //列表要展示的数据@overridevoid initState() {// TODO: implement initStatesuper.initState();getData();}/*** 初始化list数据 加延时模仿网络请求*/Future getData() async {await Future.delayed(Duration(seconds: 2), () {setState(() {list = List.generate(15, (i) => '哈喽,我是原始数据 $i');});});}@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(// Here we take the value from the MyHomePage object that was created by// the App.build method, and use it to set our appbar title.title: new Text(widget.title),),body: RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(itemBuilder: _renderRow,itemCount: list.length,),),);}Widget _renderRow(BuildContext context, int index) {return ListTile(title: Text(list[index]),);}/*** 下拉刷新方法,为list重新赋值*/Future<Null> _onRefresh() async {await Future.delayed(Duration(seconds: 3), () {print('refresh');setState(() {list = List.generate(20, (i) => '哈喽,我是新刷新的 $i');});});}}
代码不复杂,我们一步步分析:
MyHomePage 只是返回一个State,这里省略了.
首先body里我们返回了一个RefreshIndicator,这个组件自带下拉回调,然后里面我们包裹了一个listview,
然后使用List.generate()方法来创建了一个长度为15的List,并把List里的值赋值给ListView Item中的ListTile。
下拉回调onRefresh 我们返回了一个改变list的方法 .
在上面的代码中我们使用_onRefresh()方法来处理下拉刷新的回调
/*** 下拉刷新方法,为list重新赋值*/Future<Null> _onRefresh() async {await Future.delayed(Duration(seconds: 3), () {print('refresh');setState(() {list = List.generate(20, (i) => '哈喽,我是新刷新的 $i');});});}
其中 Future.delayed()方法可以选择延迟处理任务,这里我们假设网络的延迟是3秒.
这样一个简单的下拉刷新就实现了.
上拉加载更多
对于加载更多的组件在Flutter中是没有提供的,所以在这里我们就需要考虑如何实现的。
在ListView中有一个ScrollController属性,它就是专门来控制ListView滑动事件,在这里我们可以根据ListView的位置来判断是否滑动到了底部来做加载更多的处理。
在这里我们可以使用如下代码来判断ListView 是否滑动到了底部
@overridevoid initState() {// TODO: implement initStatesuper.initState();getData();_scrollController.addListener(() {if (_scrollController.position.pixels ==_scrollController.position.maxScrollExtent) {print('滑动到了最底部');_getMore();}});}
_scrollController是我们初始化的ScrollController对象,通过监听我们可以判断现在的位置是否是最大的下滑位置来判断是否下滑到了底部。
看一下代码和效果:
class _MyHomePageState extends State<MyHomePage> {List list = new List(); //列表要展示的数据ScrollController _scrollController = ScrollController(); //listview的控制器int _page = 1; //加载的页数bool isLoading = false; //是否正在加载数据@overridevoid initState() {// TODO: implement initStatesuper.initState();getData();_scrollController.addListener(() {if (_scrollController.position.pixels ==_scrollController.position.maxScrollExtent) {print('滑动到了最底部');_getMore();}});}/*** 初始化list数据 加延时模仿网络请求*/Future getData() async {await Future.delayed(Duration(seconds: 2), () {setState(() {list = List.generate(15, (i) => '哈喽,我是原始数据 $i');});});}@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(// Here we take the value from the MyHomePage object that was created by// the App.build method, and use it to set our appbar title.title: new Text(widget.title),),body: RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(itemBuilder: _renderRow,itemCount: list.length,controller: _scrollController,),),// This trailing comma makes auto-formatting nicer for build methods.);}Widget _renderRow(BuildContext context, int index) {return ListTile(title: Text(list[index]),);}/*** 下拉刷新方法,为list重新赋值*/Future<Null> _onRefresh() async {await Future.delayed(Duration(seconds: 3), () {print('refresh');setState(() {list = List.generate(20, (i) => '哈喽,我是新刷新的 $i');});});}/*** 上拉加载更多*/Future _getMore() async {if (!isLoading) {setState(() {isLoading = true;});await Future.delayed(Duration(seconds: 1), () {print('加载更多');setState(() {list.addAll(List.generate(5, (i) => '第$_page次上拉来的数据'));_page++;isLoading = false;});});}}@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose();}}
滑动到底部的时候,我们执行加载更多的方法,给list数据多加5条,这次我们把延迟改到了1秒:
/*** 上拉加载更多*/Future _getMore() async {if (!isLoading) {setState(() {isLoading = true;});await Future.delayed(Duration(seconds: 1), () {print('加载更多');setState(() {list.addAll(List.generate(5, (i) => '第$_page次上拉来的数据'));_page++;isLoading = false;});});}}
是的,看着上面的效果我们已经实现了下拉加载更多,但是因为我们是滑动到底部触发的,如果在正在请求的过程中多次下拉就会造成多次加载更多的情况,所以我们还得对这个做下处理为了避免多次触发,我们加了一个isLoading,在上拉方法执行的过程中不会再次执行.
可以看到,我们仅仅在上面代码的基础上加上了一个isLoading的变量,当这个变量的值为true时,就不会触发加载更多的操作。
而因为是网络请求,可能需要分页,所以我们加了个page参数来查看是第几次触发上拉加载.
因为我们加了个监听,在组件卸载掉的时候记得移除这个监听,所以:
@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose();}
这个一定不要忘记,养成好习惯,每次加了监听都跑到这个方法里移除掉.
这样,我们一个简单的上拉加载更多的功能就实现了.
但是还有个问题,没有用户交互啊,加载的时候要有个提示,于是我们尝试上拉的时候展示一个加载中的组件给用户:
首先我们创建加载更多时显示的Vidget
/*** 加载更多时显示的组件,给用户提示*/Widget _getMoreWidget() {return Center(child: Padding(padding: EdgeInsets.all(10.0),child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text('加载中...',style: TextStyle(fontSize: 16.0),),CircularProgressIndicator(strokeWidth: 1.0,)],),),);}
然后我们在listview的itemcount那里把count+1,相当于我们给listview加了个尾部的组件.
body: RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(itemBuilder: _renderRow,itemCount: list.length + 1, //这里!这里!这里!controller: _scrollController,),
看一下效果是否满意:
嗯,基本符合要求,感觉那个刷新图标加的有点丑,画蛇添足了,不过功能都是ok了的.
当然, 大家可以根据自己的需要去自己实现想要的样式
看一下全部的代码:
/** Created by 李卓原 on /9/13.* email: zhuoyuan93@**/class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);final String title;@override_MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {List list = new List(); //列表要展示的数据ScrollController _scrollController = ScrollController(); //listview的控制器int _page = 1; //加载的页数bool isLoading = false; //是否正在加载数据@overridevoid initState() {// TODO: implement initStatesuper.initState();getData();_scrollController.addListener(() {if (_scrollController.position.pixels ==_scrollController.position.maxScrollExtent) {print('滑动到了最底部');_getMore();}});}/*** 初始化list数据 加延时模仿网络请求*/Future getData() async {await Future.delayed(Duration(seconds: 2), () {setState(() {list = List.generate(15, (i) => '哈喽,我是原始数据 $i');});});}@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(// Here we take the value from the MyHomePage object that was created by// the App.build method, and use it to set our appbar title.title: new Text(widget.title),),body: RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(itemBuilder: _renderRow,itemCount: list.length + 1,controller: _scrollController,),),// This trailing comma makes auto-formatting nicer for build methods.);}Widget _renderRow(BuildContext context, int index) {if (index < list.length) {return ListTile(title: Text(list[index]),);}return _getMoreWidget();}/*** 下拉刷新方法,为list重新赋值*/Future<Null> _onRefresh() async {await Future.delayed(Duration(seconds: 3), () {print('refresh');setState(() {list = List.generate(20, (i) => '哈喽,我是新刷新的 $i');});});}/*** 上拉加载更多*/Future _getMore() async {if (!isLoading) {setState(() {isLoading = true;});await Future.delayed(Duration(seconds: 1), () {print('加载更多');setState(() {list.addAll(List.generate(5, (i) => '第$_page次上拉来的数据'));_page++;isLoading = false;});});}}/*** 加载更多时显示的组件,给用户提示*/Widget _getMoreWidget() {return Center(child: Padding(padding: EdgeInsets.all(10.0),child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text('加载中...',style: TextStyle(fontSize: 16.0),),CircularProgressIndicator(strokeWidth: 1.0,)],),),);}@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose();}}
总结:
RefreshIndicator可以显示下拉刷新
使用ScrollController可以监听滑动事件,判断当前view所处的位置
可以根据item所处的位置来处理加载更多显示效果