flutter做一个banner轮播图片
做商城App,需要用到轮播,发现flutter的控件库里面没有这个控件(当然了,可能是我自己没有找到),于是就决定自己动手做一个banner轮播图片了。
框架
-
整体框架就是一个PageView,Indicator指示器,一个定时器。 -
PageView用来展示需要播放的Widget,此处不一定必须限定要展示Image. -
Indicator作为当前图片的指示器,需要给出当前获取焦点Page的指示。而且Indicator要可以设置半径,选中颜色,未选中的颜色。最后还要可以设置指示器的位置,左上右下。 -
定时器,当设置需要定时播放的时候,就按照设置的间隔时间播放对应的Widget。这里需要考虑的就是当手动点击的时候,需要取消定时循环,点击放开的时候,重新定时。当界面销毁的时候,销毁定时器。 -
Page点击和被展示回调。只需要在Page被点击和被播放展示的时候回调到对应的方法就行了。
实现
1、实现PageView
上代码:
_controller = PageController(initialPage: ,);Widget pageView = GestureDetector(onHorizontalDragDown: _onTaped,onTap: _onPageClicked,child: PageView(children: widget.childWidget,scrollDirection: widget.scrollDirection,onPageChanged: onPageChanged,controller: _controller,),);
PageController是Page控制器,可以设置初始显示的Page,也可以在多个图片轮播的时候,是否缓存页面,也可以设置在滚动方向上,在视图显示区域的比例。initialPage可以给外部调用,默认设置为0。
2、实现Indicator
上代码:
Widget indicatorWidget = Row(mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.max,crossAxisAlignment: CrossAxisAlignment.center,children: widget.childWidget.map((f) {int index = (f);Widget indicatorWidget = Container(width: ().width,height: ().height,margin: (right: ),decoration: new BoxDecoration(shape: BoxShape.circle,color: index == selectedPage? widget.indicatorSelectedColor: widget.indicatorColor,),);return indicatorWidget;},).toList(),);
当需要轮播多个Widget的时候,默认指示器按行排列,一字排开居中显示。单个子Indicator是个圆圈,颜色,半径可以外部设置。并且选中的Page的指示器要是选中的颜色,其他的为未选中颜色。
3、合并Indicator和PageView
将这两个Widget合并起来,一般来说他们是上下级关系,Indicator在PageView之上:
Widget stackWidget = widget.enableIndicator? Stack(alignment: AlignmentDirectional.bottomCenter,children: [pageView,Positioned(bottom: 6,child: Align(alignment: widget.indicatorAlign,child: indicatorWidget,),),],): pageView;
当外部设置展示Indicator的时候,直接就展示PageView,否则的话,需要用Stack将这两个Widget堆积起来。PageView在下,Indicator在上,此时它在什么位置,也是可以外部设置,通过Align可以指定指示器的位置,一般是在底部偏上一点,所以这里将Positioned的bottom参数设置了一个值。当然了,我们也可以将这个位置和位置的偏移距离对外提供设置的接口。
这里做最后一个工作,给这个stackWidget设置一个文字方向,因为有些国家的文字阅读方向是从右往左,此时如果不设置的话,会报错,我这里写死了,从左往右:
Widget parent = Directionality(textDirection: TextDirection.ltr,child: Container(width: widget.width, height: widget.height, child: stackWidget));
当然Android是提供了判断方法的:
Configuration config = getResources().getConfiguration();if(() == ) {//in Right To Left layout}
至此,Widget层面算是写完了。
4、定时器
定时触发,这在flutter里面是提供了方法的:
void _startTimer() {if ( <= 0) {return;}var oneSec = Duration(seconds: );_timer = new Timer.periodic(oneSec, (Timer timer) {++selectedPage;selectedPage = selectedPage % widget.childWidget.length;_controller?.jumpToPage(selectedPage);onPageChanged(selectedPage);});}
autoDisplayInterval时间间隔由外部设置,不设置的话,默认不自动播放,如果设置大于0的数字就是自动播放了。时间间隔是秒。另外每次触发的时候,将选中播放的页面自加1,然后跟总的页面数取余,就是当前应该播放的页面,同时需要手动设置跳转到指定的页面,同时将播放页的序数回调给调用者。
在这里,当我们滑动页面的时候,如果不处理,就会出现滑动混乱的情况。这里我们需要做出处理。记得在PageView定义的时候,最外层包裹了一个GestureDetector,我们需要处理Page被点击和被拖住水平滑动的场景,于是就有了 处理onHorizontalDragDown和onTap这两个回调函数的场景了。
onTap用于处理点击,直接携带序号参数回调给调用者。
onHorizontalDragDown用于处理水平滑动按下,具体的处理是:
void _onHorizontalDragDown(DragDownDetails details) {if (_timer == null) {return;}_releaseTimer();(Duration(seconds: 2));_startTimer();}
每次按下点击的时候,停止计时,将计时器释放掉,同时延时2秒,如果在这期间有新的拖动事件进来,再次取消。
如果延时2秒,没有其他处理,继续定时播放处理。看看定时销毁的方法:
void _releaseTimer() {if (_timer != null && !) {return;}_timer?.cancel();_timer = null;}
对外提供
下一步就是将定义的过程中需要调用者传递的参数对外暴露,通过构造的时候传递进来:
BannerWidget({Key key,@required this.childWidget,//子视图列表this.enableIndicator = true,//是否允许显示指示器this.initPage = 0,//初始页面序号this.height = 36,//高度this.width = 340,//宽度this.indicatorRadius = 4,//指示器半径this.indicatorColor = Colors.white,//指示器默认颜色this.indicatorSelectedColor = Colors.red,//页面被选中指示器颜色this.scrollDirection = Axis.horizontal,//滚动方向(默认水平)this.indicatorSpaceBetween = 6,//指示器之间的间距this.autoDisplayInterval = 0,//自动播放时间间隔(不设置或设置为0,则不自动播放)页面被展示回调this.indicatorAlign = Alignment.bottomCenter,//指示器位置})//页面被点击回调: super(key: key);
被播放的子视图列表必须提供。从上到下依次是:是否允许显示指示器,初始页面序号,高度,宽度,指示器半径,指示器默认颜色,页面被选中指示器颜色,滚动方向(默认水平),指示器之间的间距,自动播放时间间隔(不设置或设置为0,则不自动播放),页面被展示回调,指示器位置,页面被点击回调。
如何使用
var images = {"","","","","","","","","","","","",};void main() => runApp(BannerWidget(width: 340,height: 56,autoDisplayInterval: 2,childWidget: ((f) {return Image.network(f,width: 340,height: 48,);}).toList(),));
最后一公里
import 'dart:async';import 'package:flutter/';class BannerWidget extends StatefulWidget {BannerWidget({Key key,@required this.childWidget,this.enableIndicator = true,this.initPage = 0,this.height = 36,this.width = 340,this.indicatorRadius = 4,this.indicatorColor = Colors.white,this.indicatorSelectedColor = Colors.red,this.scrollDirection = Axis.horizontal,this.indicatorSpaceBetween = 6,this.autoDisplayInterval = 0,this.onPageSelected,this.indicatorAlign = Alignment.bottomCenter,}): super(key: key);final double width;final double height;final List childWidget;final Axis scrollDirection;final ValueChanged onPageSelected;final ValueChanged onPageClicked;final int initPage;final bool enableIndicator;final Color indicatorSelectedColor;final Color indicatorColor;final double indicatorRadius;final double indicatorSpaceBetween;final int autoDisplayInterval;final Alignment indicatorAlign;@override__BannerWidgetState createState() => __BannerWidgetState();}class __BannerWidgetState extends State {int selectedPage = 0;PageController _controller;Timer _timer;int lastTapDownTime = 0;void onPageChanged(int index) {setState(() {selectedPage = index;});widget?.onPageSelected(index);}void _startTimer() {if ( <= 0) {return;}var oneSec = Duration(seconds: );_timer = new Timer.periodic(oneSec, (Timer timer) {++selectedPage;selectedPage = selectedPage % widget.childWidget.length;_controller?.jumpToPage(selectedPage);onPageChanged(selectedPage);});}void _releaseTimer() {if (_timer != null && !) {return;}_timer?.cancel();_timer = null;}void _onHorizontalDragDown(DragDownDetails details) {if (_timer == null) {return;}_releaseTimer();(Duration(seconds: 2));_startTimer();}void _onPageClicked() {widget?.onPageClicked(selectedPage);}@overridevoid initState() {();_startTimer();}@overrideWidget build(BuildContext context) {_controller = PageController(initialPage: ,);Widget pageView = GestureDetector(onHorizontalDragDown: _onHorizontalDragDown,onTap: _onPageClicked,child: PageView(children: widget.childWidget,scrollDirection: widget.scrollDirection,onPageChanged: onPageChanged,controller: _controller,),);Widget indicatorWidget = Row(mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.max,crossAxisAlignment: CrossAxisAlignment.center,children: widget.childWidget.map((f) {int index = (f);Widget indicatorWidget = Container(width: ().width,height: ().height,margin: (right: ),decoration: new BoxDecoration(shape: BoxShape.circle,color: index == selectedPage? widget.indicatorSelectedColor: widget.indicatorColor,),);return indicatorWidget;},).toList(),);Widget stackWidget = widget.enableIndicator? Stack(alignment: AlignmentDirectional.bottomCenter,children: [pageView,Positioned(bottom: 6,child: Align(alignment: widget.indicatorAlign,child: indicatorWidget,),),],): pageView;Widget parent = Directionality(textDirection: TextDirection.ltr,child: Container(width: widget.width, height: widget.height, child: stackWidget));return parent;}@overridevoid dispose() {();_releaseTimer();}}var images = {"","","","","","","","","","","","",};void main() => runApp(BannerWidget(width: 340,height: 56,autoDisplayInterval: 2,childWidget: ((f) {







