【Flutter】开发之进阶Widget(五)

前言

在上一篇中,我们学习了controller、点击事件onPressedGestureDetectorTabBarPageView联动的使用,这一篇,我们来说说ListView的上拉刷新、下拉加载和轮播图。

刷新控件

下拉刷新

官方为我们提供了RefreshIndicator,主要有colorbackgroundColordisplacementonRefresh等属性

        RefreshIndicator(
        //刷新进度条颜色
        color: Colors.black45,
        //背景色
        backgroundColor: Colors.blue,
        //触发下拉刷新的距离 默认40
        displacement: 40,
        //下拉回调方法,方法需要有async和await关键字,没有await,刷新图标立马消失,没有async,刷新图标不会消失
        onRefresh: refresh,
        child: ListView.separated(
          itemBuilder: ((context, index) {
            return MoveItem();
          }),
          separatorBuilder: (context, index) {
            return Divider(
              color: Colors.black45,
              height: 10,
            );
          },
          itemCount: count,
        ),
      ),
  int count = 2;

  Future refresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        count = 10;
      });
    });
  }

效果图如下


20190625_174848.gif

上拉加载

上拉加载的话,需要使用ListViewcontroller属性

final ScrollController _scrollController = new ScrollController();

 @override
  void initState() {
    ///增加滑动监听
    _scrollController.addListener(() {
      ///判断当前滑动位置是不是到达底部,触发加载更多回调
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          count += 5;
        });
      }
    });

    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
  }

然后将_scrollController设置给ListView
当我们运行时却发现,不仅不能上拉加载,连下拉刷新也失效了!
这是因为,RefreshIndicatorScrollController有兼容性问题,当然官方也给出了解决办法,给ListView添加如下代码:
physics: const AlwaysScrollableScrollPhysics()
效果图如下

20190625_175404.gif

虽然说功能实现了,但是感觉效果有点怪怪的,新的item出现前应该有个过渡。
思路如下,当触发上拉加载时,给ListView添加一个加载中的item,当加载完成后,再移除。
先定义个变量,bool loadMore = false;
当触发上拉加载时,设置为true

  @override
  void initState() {
    ///增加滑动监听
    _scrollController.addListener(() {
      ///判断当前滑动位置是不是到达底部,触发加载更多回调
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          loadMore = true;
        });
        getMore();
      }
    });
    super.initState();
  }

加载完成时,设置为false

  Future getMore() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        loadMore = false;
        count += 5;
      });
    });
  }

这时,item就不能是固定的了

  Widget getItem(int index) {
    if (loadMore && index == count) {
      return LoadMoreItem();
    } else {
      return MoveItem();
    }
  }

包括count

  int getItemCount() {
    if (loadMore) {
      return count + 1;
    } else {
      return count;
    }
  }

效果图如下


20190625_174934.gif

完整代码如下

class ListViewDemo extends StatefulWidget {
  @override
  _ReListViewDemoState createState() => _ReListViewDemoState();
}

class _ReListViewDemoState extends State<ListViewDemo> {
  int count = 2;
  final ScrollController _scrollController = new ScrollController();

  bool loadMore = false;

  Future refresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        count = 10;
      });
    });
  }

  Future getMore() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        loadMore = false;
        count += 5;
      });
    });
  }

  @override
  void initState() {
    ///增加滑动监听
    _scrollController.addListener(() {
      ///判断当前滑动位置是不是到达底部,触发加载更多回调
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          loadMore = true;
        });
        getMore();
      }
    });
    super.initState();
  }

  Widget getItem(int index) {
    if (loadMore && index == count) {
      return LoadMoreItem();
    } else {
      return MoveItem();
    }
  }

  int getItemCount() {
    if (loadMore) {
      return count + 1;
    } else {
      return count;
    }
  }

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListViewDemo'),
        centerTitle: true,
        brightness: Brightness.dark,
      ),
      body: RefreshIndicator(
        //刷新进度条颜色
        color: Colors.black45,
        //背景色
        backgroundColor: Colors.blue,
        ////触发下拉刷新的距离 默认40
        displacement: 40,
        //下拉回调方法,方法需要有async和await关键字,没有await,刷新图标立马消失,没有async,刷新图标不会消失
        onRefresh: refresh,
        child: ListView.separated(
          itemBuilder: ((context, index) {
            return getItem(index);
          }),
          separatorBuilder: (context, index) {
            return Divider(
              color: Colors.black45,
              height: 10,
            );
          },
          itemCount: getItemCount(),
          controller: _scrollController,
          //保持ListView任何情况都能滚动,解决在RefreshIndicator的兼容问题。
          physics: const AlwaysScrollableScrollPhysics(),
        ),
      ),
    );
  }
}

轮播图

首先,用到的是PageView,以及PageController,这些之前已经说过,就不在细说

  Widget _pageView() {
    return PageView(
      children: <Widget>[
        Image.network(
          'http://img4.imgtn.bdimg.com/it/u=1621655683,865218969&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
        Image.network(
          'http://img1.imgtn.bdimg.com/it/u=1901690610,3955011377&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
        Image.network(
          'http://img3.imgtn.bdimg.com/it/u=1546158593,2358526642&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
      ],
      controller: pageController,
    );
  }

其次,需要一个定时器,别忘记取消

  int count = 3;
  int currentPosition = 0;

  @override
  void initState() {
    super.initState();
    _timer = new Timer.periodic(Duration(seconds: 2), (time) {
       //每2秒执行一次
      changePage();
    });
  }

  @override
  void dispose() {
    super.dispose();
    _timer.cancel();
    pageController.dispose();
  }
  void changePage() {
      pageController.animateToPage(currentPosition % count,
          duration: Duration(milliseconds: 200), curve: Curves.fastOutSlowIn);
    currentPosition++;
  }

效果图如下


20190625_175123.gif

当我们手动滑动时,currentPosition会错乱,我们需要对其进行调整,需要用到的是onPageChanged属性

      onPageChanged: (index) {
        _timer.cancel();
        currentPosition = index;
        _timer = new Timer.periodic(Duration(seconds: 2), (time) {
          changePage();
        });
      },

当然,这么常用的控件,已经有造好的轮子了 flutter_swiper
首先添加依赖flutter_swiper: ^1.1.6
下面是一些常用属性

  Widget _swiper() {
    return new Swiper(
      itemBuilder: (BuildContext context, int index) {
        return new Image.network(
          "http://img4.imgtn.bdimg.com/it/u=1621655683,865218969&fm=200&gp=0.jpg",
          fit: BoxFit.fitWidth,
          height: 150,
        );
      },
      itemCount: 3,
      //动画时间,默认300.0毫秒
      duration: 300,
      //初始位置
      index: 0,
      //无限轮播模式开关
      loop: true,
      //是否自动播放,默认false
      autoplay: true,
      layout: SwiperLayout.DEFAULT,
      //滚动方式
      scrollDirection: Axis.horizontal,
      //点击轮播的事件
      onTap: (index) {},
      //用户拖拽的时候,是否停止自动播放
      autoplayDisableOnInteraction: true,
      //指示器
      pagination: new SwiperPagination(),
      //左右箭头
      control: null,
    );
  }

推荐阅读更多精彩内容