flutter banner輪播

文章將同步更新到微信公衆號:Android部落格緩存

問題背景

由於最近作商城App,須要用到輪播,發現flutter的控件庫裏面沒有這個控件(固然了,多是我本身沒有找到),因而就決定本身動手作一個banner輪播圖片了。微信

框架

  • 總體框架就是一個PageView,Indicator指示器,一個定時器。markdown

  • PageView用來展現須要播放的Widget,此處不必定必須限定死要展現Image.框架

  • Indicator做爲當前圖片的指示器,須要給出當前獲取焦點Page的指示。並且Indicator要能夠設置半徑,選中顏色,未選中的顏色。最後還要能夠設置指示器的位置,左上右下。async

  • 定時器,當設置須要定時播放的時候,就按照設置的間隔時間播放對應的Widget。這裏須要考慮的就是當手動點擊的時候,須要取消定時循環,點擊放開的時候,從新定時。當界面銷燬的時候,銷燬定時器。ide

  • Page點擊和被展現回調。只須要在Page被點擊和被播放展現的時候回調到對應的方法就好了。函數

實現

一、實現PageView

上代碼: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

二、實現Indicator

上代碼: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的指示器要是選中的顏色,其餘的爲未選中顏色。

三、合併Indicator和PageView

將這兩個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(),
    ));

複製代碼

微信公衆號:

相關文章
相關標籤/搜索