Flutter 实战:正真意义上的无限循环轮播器

3,674 阅读2分钟

效果图

效果图

前言

最近感觉flutter的圈子异常的火爆,怀揣着一颗赤诚的学习之心,我也加入这个大家庭(大坑哈哈)

使用

废话不多说,先看下如何使用:

new MyPageComponent(datas, callback: RouterUtil.NavigatorPush(context, new CardView())),

一行代码调用,datas是一个list数据源,callback是图片的点击事件。

大致的实现逻辑

List<PageComponentModel> imgs;
imgs = new List();
    imgs.add(widget.datas[widget.datas.length - 1]);
    imgs.addAll(widget.datas);
    imgs.add(widget.datas[0]);

数据源是list里边放了个自定义的model暂时只放了图片的url和title。如上代码这边的实现是在数据源的头部把数据源的最后一个数据添加进去,在数据源的尾部把数据源的第一个数据添加进去,然后通过一些逻辑的处理实现了无限循环逻辑。

首先创建pageView

Widget _pageView(constraints, callback) =>
      new NotificationListener(
          onNotification: (ScrollNotification note) {
            if (note is ScrollEndNotification ||
                note is UserScrollNotification) { //判断动画是否结束,或者用户手动滚动
              _currentState = true;
            } else {
              _currentState = false;
            }
            setState(() {
              _currentPage = _pageController.page;
              _index = _currentPage.round();
              _isEndScroll = _currentState;
            });
          },
          child: new PageView.custom(
            physics: const PageScrollPhysics(
                parent: const BouncingScrollPhysics()),
            controller: _pageController,
            childrenDelegate: new SliverChildBuilderDelegate(
                  (context, index) =>
              new GestureDetector(child: new _ChildPage(
                imgs[index].imgPath,
                title: imgs[index].title,
                parallaxOffset: constraints.maxWidth / 2.0 *
                    (index - _currentPage),
              ),
                onTap: callback,),
              childCount: imgs.length,
            ),
            onPageChanged: (index) {
              if (index == imgs.length - 1) {
                new Future.delayed(
                    new Duration(milliseconds: 200)).then((lwh) {
                  _pageController.jumpToPage(1);
                });
              } else if (index == 0) {
                new Future.delayed(
                    new Duration(milliseconds: 200)).then((lwh) {
                  _pageController.jumpToPage(imgs.length - 2);
                });
              }
            },
          )
      );

上边代码没什么好说的看一眼就明白,有一个地方需要注意的是 new Duration(milliseconds: 200)).then((lwh) { _pageController.jumpToPage(1);这句话这边添加了一个消息延迟处理跳转,由于_pageController.page获取到的是一个时时的位置double类型,然后在onPageChanged(index)是个int类型内部是通过四舍五入的,所以如果不加一个延迟的话动画会不那么平滑。

创建指示器

Widget _indicator() {
    List<Widget> radios = new List();
    for (int i = 1; i < imgs.length - 1; i++) {
      if (_index == 0) {
        _index = imgs.length - 2;
      } else if (_index == imgs.length - 1) {
        _index = 0;
      }
      IconData iconData = _index == i ? Icons.radio_button_checked : Icons
          .radio_button_unchecked;
      Icon icon = new Icon(iconData, color: Colors.grey[100], size: 8.0,);
      radios.add(icon);
    }
    return new Container(
      alignment: Alignment.bottomCenter,
      margin: const EdgeInsets.only(bottom: 10.0),
      child: new Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: radios,
      ),
    );
  }
}

由于数据源的头尾都添加了一个数据,所以循环从下表1位置开始,创建个数为imgs的长度减去2,也就是datas的长度。

pageView子部件的简单实现

class _ChildPage extends StatelessWidget {
  _ChildPage(this.data, {
    Key key,
    this.parallaxOffset = 0.0,
    this.title,
  }) : super(key: key);

  final String title;
  final String data;
  final double parallaxOffset;

  @override
  Widget build(BuildContext context) =>
      new Center(
        child: new Center(
          child: new Stack(
            children: <Widget>[
              new FadeInImage.assetNetwork(
                imageScale: 0.0,
                placeholder: "assets/images/ic_error.png",
                image: data,
                fit: BoxFit.fill,),
              new Container(
                alignment: Alignment.bottomCenter,
                margin: const EdgeInsets.only(bottom: 20.0),
                child: new Transform(
                  transform: new Matrix4.translationValues(
                      parallaxOffset, 0.0, 0.0),
                  child: Text(
                    title, style: new TextStyle(color: Colors.grey[100]),),
                ),
              ),
            ],
          ),
        ),
      );
}

就是一张图片加一个标题

组合部件

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new LayoutBuilder(
          builder: (context, constraints) =>
          new Stack(children: <Widget>[
            _pageView(constraints, widget.callback),
            _indicator(),
          ],
          )
      ),
    );
  }

创建轮询

timer = new Timer.periodic(new Duration(seconds: 3), (timer) {
      if (_isEndScroll) {
        print('lwh$_index');
        if (_index == imgs.length - 1) {
          _index = 1;
          _pageController.jumpToPage(_index);
        } else if (_index == 0) {
          _index = imgs.length - 1;
          _pageController.jumpToPage(_index);
        }
        _pageController.animateToPage(_index + 1,
            duration: new Duration(milliseconds: 500), curve: Curves.linear);
      }
    });

结语

经过上边的几步,轮播器大致就实现了,如果有更高级的需求可以接着扩展。欢迎大家留言交流,一起学习。

最后附上github地址(哈哈 给个star支持一下):源码地址