40

Flutter 封装一个 Banner 轮播图

 4 years ago
source link: https://www.tuicool.com/articles/nAZVfqF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

实际业务开发中,首页一般都会存在一个轮播图。

在 Flutter 中,如何开发一个轮播?

iY7BVfV.gif

了解需求

首先,我们在开发一个功能的时候要了解这个功能的需求,那一个轮播需要有什么功能?

1. 可以自定义高度和一些属性 2. 展示图片 3. 自动翻页播放 4. 点击事件 5. 指示器 6. 人为拖动的时候关闭自动播放

其中「人为拖动的时候关闭自动播放」是比较难的,我们后续会说,那先一个一个功能来实现。

自定义高度和一些属性

这里主要是做一些前期的工作,如果我们的 Banner 要开源让别人来使用,那我们肯定是要给用户一些可以自定义的属性的,比如:

1. Banner 的高度 2. 图片切换的效果 3. 点击事件的回调

既然我们是封装一个 Widget,那我们新建一个文件 widget_banner.dart ,类名叫  CustomBanner , 构造函数如下:

CustomBanner(

this._images, {

this.height = 200,

this.onTap,

this.curve = Curves.linear,

}) : assert(_images != null);

_images:首先,图片的链接必须有,并且在后面也做了一个断言验证 height:其次,高度可以让用户自己定义,默认为200 onTap:用户点击的回调,是一个  ValueChanged<int> ,回调一个 index curve:图片在切换时候的效果,默认为  Curves.linear

这样初期的准备工作已经做完,下面就开始做展示图片的功能。

展示图片

一般的 Banner 都是由一些图片组成,然后在固定的时间内翻页,

那能够翻页的 Widget,我们首先想到的是 PageView ,而  PageView 也正好能满足我们的需求,

它有如下几个属性:

1. 多页面翻页 2. 有控制器控制翻页 3. 翻页的回调 4. 无限页面

那我们首先就来定义一个 PageView

Widget _buildPageView() {

var length = widget._images.length;

return Container(

height: widget.height,

child: PageView.builder(

controller: _pageController,

onPageChanged: (index) {

if (index == 0) {

_curIndex = length;

}

},

itemBuilder: (context, index) {

return Image.network(

widget._images[index % length],

fit: BoxFit.cover,

);

},

),

);

}

这里定义了一个方法通过 PageView.builder 来生成  PageView ,用该方法的好处是可以生成无限个 Page,这样就不用担心滑到右侧边界的问题。

那有人会问如果是左侧的边界该怎么办?

onPageChange 方法,我们判断了如果  index == 0 那就把  _curIndex 改为 length,为什么改为 length?

因为在 itemBuilder 中,返回的是  widget._images[index % length] ,用 index 对 length 取余,这样就保证了我们的图片不会数组越界,并且第 length 个图片就是第一个图片,这样就保证左侧的边界也不会被触碰到了。

在 PageView 的上方也是定义了一个 Container 来限定高度,来看一下效果:

Fj2UBvA.gif

自动翻页播放

现在能展示图片了,那就该来做自动翻页了。

一般在 Dart 中,使用 Timer.periodic() 来做循环定时任务,该方法有两个参数:

1. duration:指隔多长时间执行一次 2. callback:时间到的时候执行的任务

那有了该方法,我们就可以很轻松的写出自动播放:

_timer = Timer.periodic(Duration(seconds: 3), (t) {

_curIndex++;

_pageController.animateToPage(

_curIndex,

duration: Duration(milliseconds: 300),

curve: Curves.linear,

);

});

在上面我们给 PageView 定义了一个  controller ,这里就可以用上了,

首先定义 Timer.periodic 方法,指出每三秒执行一次,然后在回调任务中执行:

1. _curIndex++:index +1 2. 使用 controller 的  animateToPage  方法,该方法是有动画效果的跳转

animateToPage 有三个参数:

1. 跳转的页面 2. 跳转到该页面动画持续时间(也就是多长时间能翻到该页) 3. 动画的效果

定义好后,我们来看一下效果:

eYNjMjy.gif

点击事件

现在自动播放也 ok 了,那基本的就剩一个点击事件了。

点击事件非常简单,我们可以在 PageView 上面加一个  GestureDetector 来识别手势,

但是我又不想在 PageView 上面加,为什么?

因为后续要添加指示器,指示器应该也要有自己的点击事件,比如点击第二个小圆点就跳转到第二页之类的,

所以,我们要在 Image 上面添加手势识别:

return GestureDetector(

onTap: () {

Scaffold.of(context).showSnackBar(

SnackBar(

content: Text('当前 page 为 ${index % length}'),

duration: Duration(milliseconds: 500),

),

);

},

child: Image.network(

widget._images[index % length],

fit: BoxFit.cover,

),

);

非常简单,就是增加了一个 GestureDetector ,来看一下效果:

vmA363z.gif

讲道理,现在一个最最基本的 Banner 就已经完成了,能看图片,有轮播,有点击事件。

但是还并不完善,下面来做指示器。

指示器

一般的轮播,都会有一个指示器,例如下面的小圆点,或者「1 / 3」类似于这种,那我们这里就只搞第一种小圆点。

作为指示器,应该有如下几点:

1. 在图片前面(废话,在图片后面也看不到) 2. 有几张图片就有几个指示器 3. 显示出当前在第几页

在图片前面显示

这个需求比较简单,我们用一个 Stack 来包裹住  PageView 和  Indicator 就ok了:

return Stack(

alignment: Alignment.bottomCenter,

children: <Widget>[

_buildViewPager(),

_buildIndicator(),

],

);

定义了一个 _buildIndicator() 方法,该方法用来构建一个指示器。

有几张图片就有几个指示器

我们这里说的指示器就是小圆点,也很简单,用 ClipOval 来创建一个圆形就ok了,

具体代码如下:

Widget _buildIndicator() {

var length = widget._images.length;

return Positioned(

bottom: 10,

child: Row(

children: widget._images.map((s) {

return Padding(

padding: const EdgeInsets.symmetric(horizontal: 3.0),

child: ClipOval(

child: Container(

width: 8,

height: 8,

color: Colors.grey,

),

),

);

}).toList(),

),

);

}

逻辑为:

1. 首先获取到图片数据的长度 2. Stack 定义了 Aligment 为  bottomCenter 3. 然后定义了一个  Positioned  来控制距离底部的距离 4. child 为  Row ,横向排列小圆点 5. 给每个小圆点设置边距为3 6. 小圆点的大小为8

看一下效果:

fiqURfn.gif

可以发现小圆点确实是出来了,但是并没有指示到当前是哪一个。

显示出当前在第几页

那接下来就要显示出当前是在第几页,其实这个也很简单(如果不做特殊效果的话),

我们刚才指示器的小圆点是灰色的,那当前页的小圆点我们给弄成白色的:

Widget _buildIndicator() {

var length = widget._images.length;

return Positioned(

bottom: 10,

child: Row(

children: widget._images.map((s) {

return Padding(

padding: const EdgeInsets.symmetric(horizontal: 3.0),

child: ClipOval(

child: Container(

width: 8,

height: 8,

color: s == widget._images[_curIndex % length]

? Colors.white

: Colors.grey,

),

),

);

}).toList(),

),

);

}

这里的重点是 Container 的 color 属性,判断一下当前的值是否是和当前 index 的值相等,

如果相等则变为白色,如果不相等则是灰色。

如果光写成这样,小圆点是不会变的,所以我们要在 PageView 的  onPageChanged 回调中去  setState()

顺便更新 _curIndex 的值。

重新构建一下刷新页面,这个时候看一下效果:

7neiEnU.gif

这个时候这个 Banner 可以说是很完善了,但是如果我们手动的去干预滑动会出现什么问题呢?

因为我们刚才写的是 3 秒一切换,所以我们在,手动切换的时候,它在到达第三秒后,就会出现连续换页的情况。

人为拖动的时候关闭自动播放

所以,根据上述情况,我们就要在监听到有人为拖动的时候去关闭自动播放,然后在没有人为的情况下打开。

刚才已经在 Image 上面加了一个  GesutreDetector ,正好,我们添加  onPanDown 参数来暂停定时任务。

然后在手指离开的时候恢复任务。

但是!这里有很大的坑!

1. Timer 没有暂停方法 2. 因为用的是  PageView ,有滑动冲突, 所以监听不到手指离开的方法

这里只能采用曲线救国的方法:

1.

虽然 Timer 没有暂停,但是他有取消  cancel() 方法。

2.

虽然监听不到手指离开的方法,但是我们可以监听到手指触碰的方法

所以我们应该这么写:

/// 点击到图片的时候取消定时任务

_cancelTimer() {

if (_timer != null) {

_timer.cancel();

_timer = null;

_initTimer();

}

}


/// ------------------------

return GestureDetector(

onPanDown: (details) {

_cancelTimer();

},

onTap: () {

Scaffold.of(context).showSnackBar(

SnackBar(

content: Text('当前 page 为 ${index % length}'),

duration: Duration(milliseconds: 500),

),

);

},

child: Image.network(

widget._images[index % length],

fit: BoxFit.cover,

),

);

先定义一个方法, _cancelTimer() ,里面首先判断如果  _timer 不是 null 的时候则把 _timer 取消掉,然后置空。

随后再对 _timer 进行初始化。

为什么要这么做?取消的同时进行初始化?

因为我们并不知道什么时候手指离开屏幕,所以我们在手指点击后就 重新开始计时

这样既能保证点击的时候没有定时任务,又能保证在后续的一段时间后会重新开始定时任务。

因为定时任务的时间是3秒,而我们滑动查看图片也就一两秒的时间,这段时间之内如果再次手动滑动,那么也会取消掉之前的任务,重新开始新的任务,这样就达到了我们的效果。

来看一下:

z2Ibu2B.gif

那到现在为止整个 Banner 的封装就结束了。

总结

首先,在封装一个 Widget 的时候,首先要了解该 Widget 的功能,根据功能的需求来实现,

而且在实现的过程中,要考虑到灵活的问题,可以给用户来设置的就要暴露出来,而不能暴露的方法就要写成私有的。

完整代码已经传至GitHub:https://github.com/wanglu1209/WFlutterDemo

YRJf2aj.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK