文章將同步更新到微信公衆號: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(), )); 複製代碼
微信公衆號: