在《APP 開發從 0 到 1(三)佈局與 ListView》咱們完成了 ListView,這篇文章將作 ListView 下拉加載和加載更多。markdown
Flutter 提供了 RefreshIndicator 下拉刷新組件,能夠輕鬆讓咱們實現 Material Design 風格的下拉刷新效果。網絡
//下拉刷新組件
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,
})
複製代碼
廢話很少說,直接上完整代碼,你可細品下哦。app
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ListViewPage extends StatefulWidget {
@override
ListViewPageState createState() => new ListViewPageState();
}
class ListViewPageState extends State<ListViewPage> {
List list = new List(); //列表要展現的數據
@override
void initState() {
super.initState();
getData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AndBlog'),
),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(list[index]),
);
},
)),
floatingActionButton: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.account_box),
onPressed: () {
print("FloatingActionButton");
},
elevation: 30,
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Future<Null> _onRefresh() async {
await Future.delayed(Duration(seconds: 3), () {
print('refresh');
setState(() {
list = List.generate(20, (i) => '哈嘍,我是下拉刷新的數據 $i');
});
});
}
Future getData() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
list = List.generate(30, (i) => '哈嘍,我是原始數據 $i');
});
});
}
}
複製代碼
Flutter 沒有直接提供加載更多組件,但咱們能夠在 ListView 監聽 ScrollController,判斷是否滑到底,而後加載下一頁。async
import 'package:flutter/material.dart';
import 'package:flutter_andblog/andblog/common/color_common.dart';
import 'package:flutter_andblog/andblog/common/http_common.dart';
import 'package:flutter_andblog/andblog/detail/blog_detail_page.dart';
import 'package:http/http.dart' as http;
import 'blog.dart';
class BlogListPage extends StatefulWidget {
@override
BlogListPageState createState() => new BlogListPageState();
}
class BlogListPageState extends State<BlogListPage> {
List<Blog> _blogList = [];
String loadMoreText = "正在加載中...";
TextStyle loadMoreTextStyle =
new TextStyle(color: const Color(0xFF4483f6), fontSize: 14.0);
ScrollController scrollController = new ScrollController();
var hasData = true;
var page = 0;
@override
void initState() {
super.initState();
//一進頁面就請求接口
_getBlogListData();
scrollController.addListener(() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent) {
//已經滑到底了
if (hasData) {
//還有數據,加載下一頁
setState(() {
loadMoreText = "正在加載中...";
loadMoreTextStyle =
new TextStyle(color: const Color(0xFF4483f6), fontSize: 14.0);
});
page++;
print("page=" + page.toString());
_getBlogListData();
} else {
setState(() {
loadMoreText = "沒有更多數據";
loadMoreTextStyle =
new TextStyle(color: const Color(0xFF999999), fontSize: 14.0);
});
}
}
});
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
//網絡請求
Future _getBlogListData() async {
//一頁加載8條數據,skip爲跳過的數據,好比加載第二頁(page=1),skip跳過前8條數據,即顯示第9-16條數據
var skip = page * 8;
print("blog_list_url=" + HttpCommon.blog_list_url + skip.toString());
var response = await http.get(HttpCommon.blog_list_url + skip.toString(),
headers: HttpCommon.headers());
if (response.statusCode == 200) {
// setState 至關於 runOnUiThread
setState(() {
var data = Blog.decodeData(response.body);
if (data.length < 8) {
//某頁數據小於8,代表沒有下一頁了
hasData = false;
} else {
hasData = true;
}
_blogList.addAll(data);
print("_blogList.length0=" + _blogList.length.toString());
});
}
}
@override
Widget build(BuildContext context) {
var content;
if (_blogList.length == 0) {
content = new Center(
// 可選參數 child:
child: new CircularProgressIndicator(),
);
} else {
content = _contentList();
}
return Scaffold(
backgroundColor: ColorCommon.backgroundColor,
appBar: AppBar(
title: Text('AndBlog'),
),
body: content,
floatingActionButton: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.account_box),
onPressed: () {
print("FloatingActionButton");
},
elevation: 30,
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _contentList() {
print("_blogList.length=" + _blogList.length.toString());
return new RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemCount: _blogList.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == _blogList.length) {
return _buildProgressMoreIndicator();
} else {
return _blogItem(index);
}
},
controller: scrollController,
));
}
Future<Null> _onRefresh() async {
await Future.delayed(Duration(seconds: 1), () {
print('refresh');
setState(() {
page = 0;
_blogList.clear();
_getBlogListData();
});
});
}
Widget _buildProgressMoreIndicator() {
return new Padding(
padding: const EdgeInsets.all(15.0),
child: new Center(
child: new Text(loadMoreText, style: loadMoreTextStyle),
),
);
}
Widget _blogItem(int index) {
Blog blog = _blogList[index];
var date = new Padding(
padding: const EdgeInsets.only(
top: 20.0,
left: 10.0,
right: 10.0,
),
child: new Text(
blog.date,
textAlign: TextAlign.center,
style: TextStyle(color: ColorCommon.dateColor, fontSize: 18),
));
var cover = new Padding(
padding: const EdgeInsets.only(
top: 10.0,
left: 10.0,
right: 10.0,
),
child: new ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)),
child: new Image.network(
'http://pic1.win4000.com/wallpaper/2020-04-21/5e9e676001e20.jpg',
)));
var title = new Text(
blog.title,
style: TextStyle(color: ColorCommon.titleColor, fontSize: 22),
);
var summary = new Padding(
padding: const EdgeInsets.only(
top: 5.0,
),
child: new Text(blog.summary,
textAlign: TextAlign.left,
style: TextStyle(color: ColorCommon.summaryColor, fontSize: 18)));
var titleSummary = new Container(
padding: const EdgeInsets.all(10.0),
alignment: Alignment.topLeft,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10.0),
bottomRight: Radius.circular(10.0)),
shape: BoxShape.rectangle,
),
margin: const EdgeInsets.only(left: 10, right: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[title, summary],
),
);
var blogItem = new GestureDetector(
//點擊事件
onTap: () => navigateToMovieDetailPage(blog.objectId, index),
child: new Column(
children: <Widget>[
date,
cover,
titleSummary,
],
),
);
return blogItem;
}
// 跳轉頁面
navigateToMovieDetailPage(String blogId, Object imageTag) {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (BuildContext context) {
return new BlogDetailPage(blogId, imageTag: imageTag);
}));
}
}
複製代碼
更多文章能夠關注公號 吳小龍同窗,與我交流。ide