Flutter listview下拉刷新 上拉加载更多

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战markdown

下拉刷新

在Flutter中系统已经为咱们提供了google material design的刷新功能 , 样式与原生Android同样. 咱们可使用RefreshIndicator组件来实现Flutter中的下拉刷新,下面们仍是先来看下如何使用吧网络

RefreshIndicator

构造方法:app

const RefreshIndicator({
    Key key,
    @required this.child,
    this.displacement: 40.0,      //触发下拉刷新的距离
    @required this.onRefresh,     //下拉回调方法
    this.color,                   //进度指示器前景色 默认为系统主题色
    this.backgroundColor,         //背景色
    this.notificationPredicate: defaultScrollNotificationPredicate,
  })
复制代码

而后咱们看一下效果以及实现方式: 这里写图片描述async

而后咱们看一下代码:ide

class _MyHomePageState extends State<MyHomePage> {
  List list = new List(); //列表要展现的数据
 

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getData();
  }

  /** * 初始化list数据 加延时模仿网络请求 */
  Future getData() async {
    await Future.delayed(Duration(seconds: 2), () {
      setState(() {
        list = List.generate(15, (i) => '哈喽,我是原始数据 $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');
      });
    });
  }
}
复制代码

代码不复杂,咱们一步步分析: MyHomePage 只是返回一个State,这里省略了. 首先body里咱们返回了一个RefreshIndicator,这个组件自带下拉回调,而后里面咱们包裹了一个listview, 而后使用List.generate()方法来建立了一个长度为15的List,并把List里的值赋值给ListView Item中的ListTile。 下拉回调onRefresh 咱们返回了一个改变list的方法 . 在上面的代码中咱们使用_onRefresh()方法来处理下拉刷新的回调post

/**
   * 下拉刷新方法,为list从新赋值
   */
  Future<Null> _onRefresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      print('refresh');
      setState(() {
        list = List.generate(20, (i) => '哈喽,我是新刷新的 $i');
      });
    });
  }
复制代码

其中 Future.delayed()方法能够选择延迟处理任务,这里咱们假设网络的延迟是3秒. 这样一个简单的下拉刷新就实现了.ui

上拉加载更多

对于加载更多的组件在Flutter中是没有提供的,因此在这里咱们就须要考虑如何实现的。this

在ListView中有一个ScrollController属性,它就是专门来控制ListView滑动事件,在这里咱们能够根据ListView的位置来判断是否滑动到了底部来作加载更多的处理。google

在这里咱们可使用以下代码来判断ListView 是否滑动到了底部spa

@override
  void initState() {
    // TODO: implement initState
    super.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; //是否正在加载数据

  @override
  void initState() {
    // TODO: implement initState
    super.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');
      });
    });
  }

  @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次上拉来的数据'));
          _page++;
          isLoading = false;
        });
      });
    }
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.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参数来查看是第几回触发上拉加载.

由于咱们加了个监听,在组件卸载掉的时候记得移除这个监听,因此:

@override
  void dispose() {
    // TODO: implement dispose
    super.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 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(); //列表要展现的数据
  ScrollController _scrollController = ScrollController(); //listview的控制器
  int _page = 1; //加载的页数
  bool isLoading = false; //是否正在加载数据

  @override
  void initState() {
    // TODO: implement initState
    super.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');
      });
    });
  }

  @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次上拉来的数据'));
          _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,
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose();
  }
}

复制代码

总结:

  • RefreshIndicator能够显示下拉刷新

  • 使用ScrollController能够监听滑动事件,判断当前view所处的位置

  • 能够根据item所处的位置来处理加载更多显示效果

相关文章
相关标签/搜索