文章将同步更新到微信公众号:Android部落格缓存
由于最近作商城App,须要用到轮播,发现flutter的控件库里面没有这个控件(固然了,多是我本身没有找到),因而就决定本身动手作一个banner轮播图片了。微信
总体框架就是一个PageView,Indicator指示器,一个定时器。markdown
PageView用来展现须要播放的Widget,此处不必定必须限定死要展现Image.框架
Indicator做为当前图片的指示器,须要给出当前获取焦点Page的指示。并且Indicator要能够设置半径,选中颜色,未选中的颜色。最后还要能够设置指示器的位置,左上右下。async
定时器,当设置须要定时播放的时候,就按照设置的间隔时间播放对应的Widget。这里须要考虑的就是当手动点击的时候,须要取消定时循环,点击放开的时候,从新定时。当界面销毁的时候,销毁定时器。ide
Page点击和被展现回调。只须要在Page被点击和被播放展现的时候回调到对应的方法就好了。函数
上代码:ui
_controller = PageController(
initialPage: widget.initPage,
);
Widget pageView = GestureDetector(
onHorizontalDragDown: _onTaped,
onTap: _onPageClicked,
child: PageView(
children: widget.childWidget,
scrollDirection: widget.scrollDirection,
onPageChanged: onPageChanged,
controller: _controller,
),
);
复制代码
PageController是Page控制器,能够设置初始显示的Page,也能够在多个图片轮播的时候,是否缓存页面,也能够设置在滚动方向上,在视图显示区域的比例。initialPage能够给外部调用,默认设置为0。this
上代码:spa
Widget indicatorWidget = Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: widget.childWidget.map( (f) { int index = widget.childWidget.indexOf(f); Widget indicatorWidget = Container( width: Size.fromRadius(widget.indicatorRadius).width, height: Size.fromRadius(widget.indicatorRadius).height, margin: EdgeInsets.only(right: widget.indicatorSpaceBetween), decoration: new BoxDecoration( shape: BoxShape.circle, color: index == selectedPage ? widget.indicatorSelectedColor : widget.indicatorColor, ), ); return indicatorWidget; }, ).toList(), ); 复制代码
当须要轮播多个Widget的时候,默认指示器按行排列,一字排开居中显示。单个子Indicator是个圆圈,颜色,半径能够外部设置。而且选中的Page的指示器要是选中的颜色,其余的为未选中颜色。
将这两个Widget合并起来,通常来讲他们是上下级关系,Indicator在PageView之上:
Widget stackWidget = widget.enableIndicator ? Stack( alignment: AlignmentDirectional.bottomCenter, children: <Widget>[ 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(config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { //in Right To Left layout } 复制代码
至此,Widget层面算是写完了。
定时触发,这在flutter里面是提供了方法的:
void _startTimer() { if (widget.autoDisplayInterval <= 0) { return; } var oneSec = Duration(seconds: widget.autoDisplayInterval); _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(); Future.delayed(Duration(seconds: 2)); _startTimer(); } 复制代码
每次按下点击的时候,中止计时,将计时器释放掉,同时延时2秒,若是在这期间有新的拖动事件进来,再次取消。
若是延时2秒,没有其余处理,继续定时播放处理。 看看定时销毁的方法:
void _releaseTimer() { if (_timer != null && !_timer.isActive) { 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.onPageSelected,//页面被展现回调 this.indicatorAlign = Alignment.bottomCenter,//指示器位置 this.onPageClicked})//页面被点击回调 : super(key: key); 复制代码
被播放的子视图列表必须提供。从上到下依次是:是否容许显示指示器,初始页面序号,高度,宽度,指示器半径,指示器默认颜色,页面被选中指示器颜色,滚动方向(默认水平),指示器之间的间距,自动播放时间间隔(不设置或设置为0,则不自动播放),页面被展现回调,指示器位置,页面被点击回调。
var images = { "https://ws1.sinaimg.cn/large/610dc034ly1fitcjyruajj20u011h412.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fjfae1hjslj20u00tyq4x.jpg", "http://ww1.sinaimg.cn/large/610dc034ly1fjaxhky81vj20u00u0ta1.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fivohbbwlqj20u011idmx.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fj78mpyvubj20u011idjg.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fj3w0emfcbj20u011iabm.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fis7dvesn6j20u00u0jt4.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fir1jbpod5j20ip0newh3.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fik2q1k3noj20u00u07wh.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fiednrydq8j20u011itfz.jpg", "http://ww1.sinaimg.cn/large/610dc034ly1ffmwnrkv1hj20ku0q1wfu.jpg", }; void main() => runApp(BannerWidget( width: 340, height: 56, autoDisplayInterval: 2, childWidget: images.map((f) { return Image.network( f, width: 340, height: 48, ); }).toList(), )); 复制代码
import 'dart:async'; import 'package:flutter/material.dart'; 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, this.onPageClicked}) : super(key: key); final double width; final double height; final List<Widget> childWidget; final Axis scrollDirection; final ValueChanged<int> onPageSelected; final ValueChanged<int> 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<BannerWidget> { int selectedPage = 0; PageController _controller; Timer _timer; int lastTapDownTime = 0; void onPageChanged(int index) { setState(() { selectedPage = index; }); widget?.onPageSelected(index); } void _startTimer() { if (widget.autoDisplayInterval <= 0) { return; } var oneSec = Duration(seconds: widget.autoDisplayInterval); _timer = new Timer.periodic(oneSec, (Timer timer) { ++selectedPage; selectedPage = selectedPage % widget.childWidget.length; _controller?.jumpToPage(selectedPage); onPageChanged(selectedPage); }); } void _releaseTimer() { if (_timer != null && !_timer.isActive) { return; } _timer?.cancel(); _timer = null; } void _onHorizontalDragDown(DragDownDetails details) { if (_timer == null) { return; } _releaseTimer(); Future.delayed(Duration(seconds: 2)); _startTimer(); } void _onPageClicked() { widget?.onPageClicked(selectedPage); } @override void initState() { super.initState(); _startTimer(); } @override Widget build(BuildContext context) { _controller = PageController( initialPage: widget.initPage, ); 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 = widget.childWidget.indexOf(f); Widget indicatorWidget = Container( width: Size.fromRadius(widget.indicatorRadius).width, height: Size.fromRadius(widget.indicatorRadius).height, margin: EdgeInsets.only(right: widget.indicatorSpaceBetween), 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: <Widget>[ 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; } @override void dispose() { super.dispose(); _releaseTimer(); } } var images = { "https://ws1.sinaimg.cn/large/610dc034ly1fitcjyruajj20u011h412.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fjfae1hjslj20u00tyq4x.jpg", "http://ww1.sinaimg.cn/large/610dc034ly1fjaxhky81vj20u00u0ta1.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fivohbbwlqj20u011idmx.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fj78mpyvubj20u011idjg.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fj3w0emfcbj20u011iabm.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fis7dvesn6j20u00u0jt4.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fir1jbpod5j20ip0newh3.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fik2q1k3noj20u00u07wh.jpg", "https://ws1.sinaimg.cn/large/610dc034ly1fiednrydq8j20u011itfz.jpg", "http://ww1.sinaimg.cn/large/610dc034ly1ffmwnrkv1hj20ku0q1wfu.jpg", }; void main() => runApp(BannerWidget( width: 340, height: 56, autoDisplayInterval: 2, childWidget: images.map((f) { return Image.network( f, width: 340, height: 48, ); }).toList(), )); 复制代码
微信公众号: