Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能
下拉刷新
在Flutter中系統(tǒng)已經(jīng)為我們提供了google material design的刷新功能 , 樣式與原生Android一樣.
我們可以使用RefreshIndicator組件來(lái)實(shí)現(xiàn)Flutter中的下拉刷新,下面?zhèn)冞€是先來(lái)看下如何使用吧
RefreshIndicator
構(gòu)造方法:
const RefreshIndicator({
Key key,
@required this.child,
this.displacement: 40.0, //觸發(fā)下拉刷新的距離
@required this.onRefresh, //下拉回調(diào)方法
this.color, //進(jìn)度指示器前景色 默認(rèn)為系統(tǒng)主題色
this.backgroundColor, //背景色
this.notificationPredicate: defaultScrollNotificationPredicate,
})
然后我們看一下效果以及實(shí)現(xiàn)方式:

然后我們看一下代碼:
class _MyHomePageState extends State<MyHomePage> {
List list = new List(); //列表要展示的數(shù)據(jù)
@override
void initState() {
// TODO: implement initState
super.initState();
getData();
}
/**
* 初始化list數(shù)據(jù) 加延時(shí)模仿網(wǎng)絡(luò)請(qǐng)求
*/
Future getData() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
list = List.generate(15, (i) => '哈嘍,我是原始數(shù)據(jù) $i');
});
});
}
@override
Widget 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');
});
});
}
}
代碼不復(fù)雜,我們一步步分析:
MyHomePage 只是返回一個(gè)State,這里省略了.
首先body里我們返回了一個(gè)RefreshIndicator,這個(gè)組件自帶下拉回調(diào),然后里面我們包裹了一個(gè)listview,
然后使用List.generate()方法來(lái)創(chuàng)建了一個(gè)長(zhǎng)度為15的List,并把List里的值賦值給ListView Item中的ListTile。
下拉回調(diào)onRefresh 我們返回了一個(gè)改變list的方法 .
在上面的代碼中我們使用_onRefresh()方法來(lái)處理下拉刷新的回調(diào)
/**
* 下拉刷新方法,為list重新賦值
*/
Future<Null> _onRefresh() async {
await Future.delayed(Duration(seconds: 3), () {
print('refresh');
setState(() {
list = List.generate(20, (i) => '哈嘍,我是新刷新的 $i');
});
});
}
其中 Future.delayed()方法可以選擇延遲處理任務(wù),這里我們假設(shè)網(wǎng)絡(luò)的延遲是3秒.
這樣一個(gè)簡(jiǎn)單的下拉刷新就實(shí)現(xiàn)了.
上拉加載更多
對(duì)于加載更多的組件在Flutter中是沒(méi)有提供的,所以在這里我們就需要考慮如何實(shí)現(xiàn)的。
在ListView中有一個(gè)ScrollController屬性,它就是專(zhuān)門(mén)來(lái)控制ListView滑動(dòng)事件,在這里我們可以根據(jù)ListView的位置來(lái)判斷是否滑動(dòng)到了底部來(lái)做加載更多的處理。
在這里我們可以使用如下代碼來(lái)判斷ListView 是否滑動(dòng)到了底部
@override
void initState() {
// TODO: implement initState
super.initState();
getData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('滑動(dòng)到了最底部');
_getMore();
}
});
}
_scrollController是我們初始化的ScrollController對(duì)象,通過(guò)監(jiān)聽(tīng)我們可以判斷現(xiàn)在的位置是否是最大的下滑位置來(lái)判斷是否下滑到了底部。
看一下代碼和效果:

class _MyHomePageState extends State<MyHomePage> {
List list = new List(); //列表要展示的數(shù)據(jù)
ScrollController _scrollController = ScrollController(); //listview的控制器
int _page = 1; //加載的頁(yè)數(shù)
bool isLoading = false; //是否正在加載數(shù)據(jù)
@override
void initState() {
// TODO: implement initState
super.initState();
getData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('滑動(dòng)到了最底部');
_getMore();
}
});
}
/**
* 初始化list數(shù)據(jù) 加延時(shí)模仿網(wǎng)絡(luò)請(qǐng)求
*/
Future getData() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
list = List.generate(15, (i) => '哈嘍,我是原始數(shù)據(jù) $i');
});
});
}
@override
Widget 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次上拉來(lái)的數(shù)據(jù)'));
_page++;
isLoading = false;
});
});
}
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose();
}
}
滑動(dòng)到底部的時(shí)候,我們執(zhí)行加載更多的方法,給list數(shù)據(jù)多加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次上拉來(lái)的數(shù)據(jù)'));
_page++;
isLoading = false;
});
});
}
}
是的,看著上面的效果我們已經(jīng)實(shí)現(xiàn)了下拉加載更多,但是因?yàn)槲覀兪腔瑒?dòng)到底部觸發(fā)的,如果在正在請(qǐng)求的過(guò)程中多次下拉就會(huì)造成多次加載更多的情況,所以我們還得對(duì)這個(gè)做下處理為了避免多次觸發(fā),我們加了一個(gè)isLoading,在上拉方法執(zhí)行的過(guò)程中不會(huì)再次執(zhí)行.
可以看到,我們僅僅在上面代碼的基礎(chǔ)上加上了一個(gè)isLoading的變量,當(dāng)這個(gè)變量的值為true時(shí),就不會(huì)觸發(fā)加載更多的操作。
而因?yàn)槭蔷W(wǎng)絡(luò)請(qǐng)求,可能需要分頁(yè),所以我們加了個(gè)page參數(shù)來(lái)查看是第幾次觸發(fā)上拉加載.
因?yàn)槲覀兗恿藗€(gè)監(jiān)聽(tīng),在組件卸載掉的時(shí)候記得移除這個(gè)監(jiān)聽(tīng),所以:
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose();
}
這個(gè)一定不要忘記,養(yǎng)成好習(xí)慣,每次加了監(jiān)聽(tīng)都跑到這個(gè)方法里移除掉.
這樣,我們一個(gè)簡(jiǎn)單的上拉加載更多的功能就實(shí)現(xiàn)了.
但是還有個(gè)問(wèn)題,沒(méi)有用戶(hù)交互啊,加載的時(shí)候要有個(gè)提示,于是我們嘗試上拉的時(shí)候展示一個(gè)加載中的組件給用戶(hù):
首先我們創(chuàng)建加載更多時(shí)顯示的Vidget
/**
* 加載更多時(shí)顯示的組件,給用戶(hù)提示
*/
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,)
],
),
),
);
}
然后我們?cè)趌istview的itemcount那里把count+1,相當(dāng)于我們給listview加了個(gè)尾部的組件.
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemBuilder: _renderRow,
itemCount: list.length + 1, //這里!這里!這里!
controller: _scrollController,
),
看一下效果是否滿(mǎn)意:

嗯,基本符合要求,感覺(jué)那個(gè)刷新圖標(biāo)加的有點(diǎn)丑,畫(huà)蛇添足了,不過(guò)功能都是ok了的.
當(dāng)然, 大家可以根據(jù)自己的需要去自己實(shí)現(xiàn)想要的樣式
看一下全部的代碼:
/*
* Created by 李卓原 on 2018/9/13.
* email: zhuoyuan93@gmail.com
*
*/
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(); //列表要展示的數(shù)據(jù)
ScrollController _scrollController = ScrollController(); //listview的控制器
int _page = 1; //加載的頁(yè)數(shù)
bool isLoading = false; //是否正在加載數(shù)據(jù)
@override
void initState() {
// TODO: implement initState
super.initState();
getData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('滑動(dòng)到了最底部');
_getMore();
}
});
}
/**
* 初始化list數(shù)據(jù) 加延時(shí)模仿網(wǎng)絡(luò)請(qǐng)求
*/
Future getData() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
list = List.generate(15, (i) => '哈嘍,我是原始數(shù)據(jù) $i');
});
});
}
@override
Widget 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次上拉來(lái)的數(shù)據(jù)'));
_page++;
isLoading = false;
});
});
}
}
/**
* 加載更多時(shí)顯示的組件,給用戶(hù)提示
*/
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,
)
],
),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose();
}
}
總結(jié):
- RefreshIndicator可以顯示下拉刷新
- 使用ScrollController可以監(jiān)聽(tīng)滑動(dòng)事件,判斷當(dāng)前view所處的位置
- 可以根據(jù)item所處的位置來(lái)處理加載更多顯示效果
到此這篇關(guān)于Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能的文章就介紹到這了,更多相關(guān)Flutter listview下拉刷新上拉加載更多內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android解決dialog彈出時(shí)無(wú)法捕捉Activity的back事件的方法
這篇文章主要介紹了Android解決dialog彈出時(shí)無(wú)法捕捉Activity的back事件的方法,涉及Android操作Activity事件的相關(guān)技巧,需要的朋友可以參考下2015-05-05
Android編程之Application設(shè)置全局變量及傳值用法實(shí)例分析
這篇文章主要介紹了Android編程之Application設(shè)置全局變量及傳值用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了全局變量及傳值的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12
六款值得推薦的android(安卓)開(kāi)源框架簡(jiǎn)介
同事整理的android(安卓)開(kāi)源框架,個(gè)個(gè)都堪稱(chēng)經(jīng)典。32 個(gè)贊!2014-06-06
Android?Gradle?插件自定義Plugin實(shí)現(xiàn)注意事項(xiàng)
這篇文章主要介紹了Android?Gradle?插件自定義Plugin實(shí)現(xiàn)注意事項(xiàng),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-06-06
Android 中使用EditText 點(diǎn)擊全選再次點(diǎn)擊取消全選功能
這篇文章主要介紹了Android 中使用EditText 點(diǎn)擊全選再次點(diǎn)擊取消全選功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2016-12-12
Win10下Android App安裝配置開(kāi)發(fā)環(huán)境
這篇文章主要為大家詳細(xì)介紹了Win10下Android App安裝配置開(kāi)發(fā)環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07

