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