flutter_swiper支持卡片輪播無限循環,適用於於Android端、iOS端。git
本文介紹插件的具體使用方法及深刻分析它的代碼是如何運做的。領會一下插件開發者的思想,並從中改進自身代碼的不足。好吧,其實在說我本身啦,代碼毛病多,思路亂,維護成本高,因此想經過閱讀大神的代碼提高一下自身水平。我嘗試了本身再寫一遍插件的代碼,感受確實有所提高。並且,細節確實很是多。github
flutter_swiper當前版本:flutter_swiper 1.1.6bash
pub.dev地址:pub.dev/packages/fl…async
github地址:github.com/best-flutte…ide
Swiper的屬性仍是比較多的,在這就不列舉了,pub.dev和github上都有展現。oop
import 'package:flutter_swiper/flutter_swiper.dart';
Swiper(
itemBuilder: (BuildContext context, int index) {
return Image.asset(
images[index],
fit: BoxFit.fill,
);
},
indicatorLayout: PageIndicatorLayout.COLOR,
autoplay: true,
itemCount: images.length,
pagination: SwiperPagination(),
control: SwiperControl(),
);
複製代碼
先簡要分析一下每一個文件的主要功能,在腦海中能有一個大體的輪廓。佈局
flutter_swiper動畫
有兩個依賴項,這兩個插件和flutter_swiper是同一個開發者。開發者github地址:github.com/best-flutte….ui
transformer_page_view: ^0.1.6
flutter_page_indicator: ^0.0.3
複製代碼
transformer_page_view:頁面切換及切換動畫。pub.dev/packages/tr…this
flutter_page_indicator:頁面指示器,支持NONE、SLIDE、WARM、COLOR、SCALE、DROP. pub.dev/packages/fl…
插件最主要的類,有三個構造方法,原始構造方法Swiper(), Swiper.children()方法, Swiper.list()方法, 後兩個方法經過接收參數轉換,調用了Swiper().
支持四種Layout佈局,DEFAULT, STACK, TINDER, CUSTOM.
當Layout爲CUSTOM時,定義了CustomLayoutOption及多種TransformBuilder, 當頁面轉換時,增長了addOpacity, addTranslate, addScale, addRotate多種動畫效果。
SwiperController間接方式繼承了ChangeNotifier,記錄用戶執行的動做event,並通知監聽者。主要功能是,控制頁面的切換,主要方法有:
繼承關係:
SwiperController extends IndexController...
IndexController extends ChangeNotifier...
複製代碼
SwiperController方法示例:
static const int START_AUTO_PLAY = 2;
static const int STOP_AUTO_PLAY = 3;
int index;
bool autoplay;
void startAutoplay() {
event = SwiperController.START_AUTO_PLAY;
this.autoplay = true;
notifyListeners();
}
複製代碼
調用者示例:
controller.addListener(_onController);
void _onController() {
switch (_controller.event) {
case SwiperController.START_AUTO_PLAY:
if (_timer == null) _startAutoplay();
break;
}
}
複製代碼
SwiperControl主要是構建向左向右的按鈕,點擊按鈕的時候,經過SwiperController的previous和next方法,通知監聽者。
SwiperPagination,自定義頁碼指示器。
持有SwiperPlugin對象,並經過SwiperPlugin的build方法渲染頁面指示器。
Widget build(BuildContext context, SwiperPluginConfig config) {
...
}
複製代碼
SwiperPluginConfig對象擁有Swiper的不少屬性,像itemCount, loop, PageController, SwiperController等。
插件的頁碼指示器並未實現頁碼的點擊功能,若是讀着想實現Dots, Slide等的點擊後跳轉,能夠在Build方法中增長GestureDetector,經過SwiperController對象實現跳轉。
SwiperPlugin是一個抽象類,主要服務於SwiperPagination,包含一個build方法,注意build方法持有SwiperPluginConfig對象。
它的實現類有SwiperControl, FractionPaginationBuilder, RectSwiperPaginationBuilder, DotSwiperPaginationBuilder, SwiperCustomPagination, SwiperPagination.
abstract class SwiperPlugin {
const SwiperPlugin();
Widget build(BuildContext context, SwiperPluginConfig config);
}
複製代碼
這個dart文件主要是導入以上各個文件,供調用者插件者使用。
library flutter_swiper;
export 'src/swiper.dart';
export 'src/swiper_pagination.dart';
export 'src/swiper_control.dart';
export 'src/swiper_controller.dart';
export 'src/swiper_plugin.dart';
複製代碼
當你閱讀上面的介紹以後,有沒有想試一下本身實現一下。接下來咱們一塊兒從簡單到複雜,一步步實現它的功能(時間有限,並未實現全部功能)。若是你有什麼問題能夠一塊兒討論。
建立程序名爲flutter_swiper,並添加assets三張輪播圖片,執行Package get。
Widget _buildSwiper() {
IndexedWidgetBuilder itemBuilder;
if (widget.onTap != null) {
itemBuilder = _wrapTap;
} else {
itemBuilder = widget.itemBuilder;
}
return PageView.builder(
controller: _pageController,
itemCount: widget.itemCount,
itemBuilder: itemBuilder,
onPageChanged: widget.onIndexChanged,
);
}
複製代碼
自定義SwiperController,實現Next,Previous.
import 'dart:async';
import 'package:flutter/foundation.dart';
class SwiperController extends ChangeNotifier {
/// Next page
static const int NEXT = 1;
/// Previous page
static const int PREVIOUS = -1;
Completer _completer;
/// Current index
int index;
/// Current event
int event;
SwiperController();
Future next() {
this.event = NEXT;
_completer = Completer();
notifyListeners();
return _completer.future;
}
Future previous() {
this.event = PREVIOUS;
_completer = Completer();
notifyListeners();
return _completer.future;
}
void complete() {
if (!_completer.isCompleted) {
_completer.complete();
}
}
}
class _SwiperState extends State<Swiper> {
...
@override
void initState() {
super.initState();
...
// SwiperController
_swiperController = widget.swiperController;
_swiperController.addListener(_onController);
}
void _onController() {
int event = widget.swiperController.event;
int index = _pageController.page.floor();
switch (event) {
case SwiperController.PREVIOUS:
index--;
break;
case SwiperController.NEXT:
index++;
break;
}
if (index < 0 || index >= widget.itemCount) return;
_pageController.jumpToPage(index);
widget.swiperController.complete();
_activeIndex = index;
}
}
複製代碼
循環參數定義:
若是是無限循環模式,把Swiper.itemCount加上一個常量值(2000000000)傳遞給PageView.itemCount,Swiper.index加上常量值(1000000000)傳遞給PageView.PageController.initialPage.
注意點:
總之,須要注意PageView的實際RealIndex,和Swiper的RenderIndex.
const int kMaxValue = 2000000000;
const int kMiddleValue = 1000000000;
int getRealItemCount() {
if (widget.itemCount == 0) return 0;
return widget.loop ? widget.itemCount + kMaxValue : widget.itemCount;
}
int getRealIndexFromRenderIndex({int index, bool loop}) {
int initPage = index;
if (loop) {
initPage += kMiddleValue;
}
return initPage;
}
int getRenderIndexFromRealIndex({int index, bool loop, int itemCount}) {
if (itemCount == 0) return 0;
int renderIndex;
if (loop) {
renderIndex = index - kMiddleValue;
renderIndex = renderIndex % itemCount;
if (renderIndex < 0) {
renderIndex += itemCount;
}
} else {
renderIndex = index;
}
return renderIndex;
}
複製代碼
輪播參數定義:
轉換動畫參數定義:
輪播實現思路:
注意點:
/// Controller auto play and stop
abstract class _SwiperTimerMixin extends State<Swiper> {
Timer _timer;
SwiperController _controller;
@override
void initState() {
_controller = widget.controller;
_controller ??= SwiperController();
_controller.addListener(_onController);
_handleAutoPlay();
super.initState();
}
void _onController() {
switch (_controller.event) {
case SwiperController.START_AUTO_PLAY:
if (_timer == null) _startAutoPlay();
break;
case SwiperController.STOP_AUTO_PLAY:
if (_timer != null) _stopAutoPlay();
break;
}
}
@override
void didUpdateWidget(Swiper oldWidget) {
if (_controller != oldWidget.controller) {
if (oldWidget.controller != null) {
oldWidget.controller.removeListener(_onController);
_controller = oldWidget.controller;
_controller.addListener(_onController);
}
}
_handleAutoPlay();
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
_controller?.removeListener(_onController);
_stopAutoPlay();
super.dispose();
}
bool get autoPlayEnabled => _controller.autoPlay ?? widget.autoPlay;
void _handleAutoPlay() {
if (autoPlayEnabled && _timer != null) return;
_stopAutoPlay();
if (autoPlayEnabled) _startAutoPlay();
}
void _startAutoPlay() {
assert(_timer == null, "Timer must be stopped before start!");
_timer = Timer.periodic(
Duration(milliseconds: widget.autoPlayDelay),
_onTimer,
);
}
void _onTimer(Timer timer) => _controller.next(animation: true);
void _stopAutoPlay() {
if (_timer != null) {
_timer.cancel();
_timer = null;
}
}
}
複製代碼
transformer_page_view
對頁面跳轉動畫及循環的封裝
transformer_page_controller
對無限循環的封裝,主要是RealIndex和RenderIndex的互轉
這裏面須要注意的是didUpdateWidget(oldWidget)方法,若是在State類,例如_SwiperState,_TransformerPageViewState等,這些類中維護了一些變量,就要注意這些變量在傳入新值時候的更新處理。
示例:
class _TransformerPageViewState extends State<TransformerPageView> {
...
@override
void didUpdateWidget(TransformerPageView oldWidget) {
int index = widget.index ?? 0;
bool created = false;
// PageController changed
// Here '_pageController' is oldWidget.pageController
if (_pageController != widget.pageController) {
if (widget.pageController != null) {
_pageController = widget.pageController;
} else {
created = true;
_pageController = TransformerPageController(
initialPage: widget.index,
itemCount: widget.itemCount,
loop: widget.loop,
);
}
}
// Index changed
if (_pageController.getRenderIndexFromRealIndex(_activeIndex) != index) {
_activeIndex = _pageController.initialPage;
if (!created) {
int initPage = _pageController.getRealIndexFromRenderIndex(index);
_pageController.animateToPage(
initPage,
duration: widget.duration,
curve: widget.curve,
);
}
}
// SwiperController changed
if (_swiperController != widget.controller) {
if (_swiperController != null) {
_swiperController.removeListener(onChangeNotifier);
}
_swiperController = widget.controller;
if (_swiperController != null) {
_swiperController.addListener(onChangeNotifier);
}
}
super.didUpdateWidget(oldWidget);
}
...
}
複製代碼
主要是把swiper的widget和pagination的widget用Stack包裹起來。
SwiperPagination須要持有Swiper的參數,如itemCount, loop, swiperController, pageController等參數。
class SwiperPaginationConfig {
final int activeIndex;
final int itemCount;
final bool loop;
final PageController pageController;
final SwiperController swiperController;
const SwiperPaginationConfig({
this.activeIndex,
this.itemCount,
this.swiperController,
this.pageController,
this.loop,
}) : assert(swiperController != null);
}
/// Here only Dots Pagination
class SwiperPagination {
/// color when current index,if set null, will be Theme.of(context).primaryColor
final Color activeColor;
/// if set null, will be Theme.of(context).scaffoldBackgroundColor
final Color color;
///Size of the dot when activate
final double activeSize;
///Size of the dot
final double size;
/// Space between dots
final double space;
/// Distance between pagination and the container
final EdgeInsetsGeometry margin;
final Key key;
const SwiperPagination({
this.key,
this.activeColor,
this.color,
this.size: 10.0,
this.activeSize: 10.0,
this.space: 3.0,
this.margin: const EdgeInsets.all(10.0),
});
Widget build(BuildContext context, SwiperPaginationConfig config) {
Color activeColor = this.activeColor;
Color color = this.color;
// Default
activeColor ??= Theme.of(context).primaryColor;
color ??= Theme.of(context).scaffoldBackgroundColor;
List<Widget> children = [];
int itemCount = config.itemCount;
int activeIndex = config.activeIndex;
for (int i = 0; i < itemCount; ++i) {
bool active = i == activeIndex;
children.add(Container(
key: Key("Pagination_$i"),
margin: EdgeInsets.all(space),
child: ClipOval(
child: Container(
color: active ? activeColor : color,
width: active ? activeSize : size,
height: active ? activeSize : size,
),
),
));
}
return Row(
key: key,
mainAxisSize: MainAxisSize.min,
children: children,
);
}
}
複製代碼