在《APP 开发从 0 到 1(三)布局与 ListView》我们完成了 ListView,这篇文章将做 ListView 下拉加载和加载更多。

ListView 下拉加载

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,
})

效果预览

完整代码

废话不多说,直接上完整代码,你可细品下哦。

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');
});
});
}
}

ListView 加载更多

Flutter 没有直接提供加载更多组件,但我们可以在 ListView 监听 ScrollController,判断是否滑到底,然后加载下一页。

效果预览

完整代码

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);
}));
}
}

Flutter 开发从 0 到 1(四)ListView 下拉加载和加载更多的更多相关文章

  1. WP & Win10开发:实现ListView下拉加载的两种方法

    1.通过ListView控件的ContainerContentChanging方法.该方法在列表项被实例化时触发,在列表项最后一个项目实例化的时候触发刷新数据逻辑就可以实现下拉加载了. 代码如下:// ...

  2. Windows Phone 8.1开发:如何让ListView下拉加载更多?

    Windows Phone 8.1开发中使用ListView作为数据呈现载体时,经常需要一个下拉(拇指向上滑动)加载更多的交互操作.如何完成这一操作呢?下面为您阐述. 思路是这样的: 1.在ListV ...

  3. (UWP开发)更为合理的一种ListView下拉刷新(PullToRefresh)实现方法

    最近在做的一个项目需要用到下拉刷新,但是参考了现在网络上比较普遍的方法,觉得都不太好,因为要在外部套上一个SrollViewer,容易出现滚动错误.于是刚开始的时候就把思路定到了ListView内部的 ...

  4. ListView下拉加载一(分页)

    首先创建在主xml里放置一个listview列表,代码如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/r ...

  5. ListView下拉加载二(分页)

    这次在一的基础上做了数据通过HttpClient远程获取显示 并且分页,首先看下效果吧: 以上就是效果图了 下面看下具体代码实现吧 主要代码和上节差不多 主入口代码: package com.tp.s ...

  6. Flutter 开发从 0 到 1(五)源码

    Flutter 开发从 0 到 1 明天开始又要上班了,你的假期任务完成如何啊?由于平时加班太多了,实在挤不出更多时间,从开始想用 Flutter <Flutter 开发从 0 到 1(一)需求 ...

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

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

  8. 安卓开发笔记——关于开源组件PullToRefresh实现下拉刷新和上拉加载(一分钟搞定,超级简单)

    前言 以前在实现ListView下拉刷新和上拉加载数据的时候都是去继承原生的ListView重写它的一些方法,实现起来非常繁杂,需要我们自己去给ListView定制下拉刷新和上拉加载的布局文件,然后添 ...

  9. MaterialRefreshLayout+ListView 下拉刷新 上拉加载

    效果图是这样的,有入侵式的,非入侵式的,带波浪效果的......就那几个属性,都给出来了,自己去试就行. 下拉刷新 上拉加载 关于下拉刷新-上拉加载的效果,有许许多多的实现方式,百度了一下竟然有几十种 ...

随机推荐

  1. python笔记-dumps()与loads()的使用

    json.dumps是将一个Python数据类型列表进行json格式的编码解析, 示例如下: >>> import json #导入python 中的json模块 >>& ...

  2. Python 中的数字到底是什么?

    花下猫语:在 Python 中,不同类型的数字可以直接做算术运算,并不需要作显式的类型转换.但是,它的"隐式类型转换"可能跟其它语言不同,因为 Python 中的数字是一种特殊的对 ...

  3. 基于PHP实现CMS识别

    最近正在开发一款基于PHP实现的Web安全检测平台,写到了CMS识别这一功能,便写一篇文章总结一下. 首先需要梳理的是CMS识别的几种方法: 1.网页源代码特征:例如源代码中包含CMS的名称或是某种特 ...

  4. 小程序开发-block组件的使用

    微信小程序中,block不是一个组件,而是一个包装元素,不会在页面中做任何渲染. 使用情况:条件渲染 wx:if 因为 wx:if 是一个控制属性,需要将它添加到一个标签/组件上,用于控制隐藏与显示. ...

  5. 小程序开发-小程序tabBar不显示的原因分析

    在尝试小程序开发时,tabBar这个是个非常常见的组件,但是今天在进行开发时,设置了TabBarb并没有显示,被这个问题困扰了近半小时,现在将排查问题后所得到的经验记录下来. 首先 如果tabBar不 ...

  6. 为系统增加删除swap空间

    增加 1.创建/home/swap这么一个分区文件.文件大小是512000个block,一般情况下1个block为1k,所以这里空间是512M,这个空间大小自己随意定义. dd if=/dev/zer ...

  7. HTML绘制表格

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. 备忘录:SQL SERVER2014 出现:“Cannot find one or more components”

    目录 1. 起因 2. 解决方案 3. 备注 4. 参考 2020年9月13日 00:40:09-shanzm 1. 起因 因为卸载vs2015的时候,使用了一个VS2013/2015卸载工具Tota ...

  9. [LeetCode]534. 游戏玩法分析 III(Mysql)

    题目 Table: Activity +--------------+---------+ | Column Name | Type | +--------------+---------+ | pl ...

  10. 在windows下使用pip安装python包遇到缺失stdint.h文件的错误

    今天在windows上使用pip安装一个python包python-lzf时遇到如下的错误: fatal error C1083: Cannot open include file: 'stdint. ...