preface
Flutter_swiper supports infinite card rotation, suitable for Android and iOS.
This article explains how to use the plug-in and takes a closer look at how its code works. Take a look at the ideas of plug-in developers and improve your own code. Well, I’m actually talking about myself. The code is full of bugs, confusing ideas and high maintenance costs, so I want to improve my own level by reading Daishen’s code. I tried to code the plugin again myself and it really improved. And there’s a lot of detail.
Flutter_swiper Current version: flutter_swiper 1.1.6
Pub. Dev: pub. Dev /packages/fl…
Github address: github.com/best-flutte…
Introduction to Use
Swiper has a number of properties, which I won’t list here. It’s available on pub.dev and Github.
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(),
);
Copy the code
Source directory structure
Start by briefly analyzing the main functions of each file so that you can get an outline in your mind.
flutter_swiper
- lib/
- src/
- custom_layout.dart
- swiper.dart
- swiper_control.dart
- swiper_controller.dart
- swiper_pagination.dart
- swiper_plugin.dart
- flutter_swiper.dart
- src/
- pubspec.yaml
pubspec.yaml
There are two dependencies, both of which are from the same developer as Flutter_swiper. Developer github address: github.com/best-flutte… .
Transformer_page_view: ^ 0.1.6 flutter_page_indicator: ^ 0.0.3Copy the code
Transformer_page_view: Page switch and switch animation. Pub. Dev/packages/tr…
Flutter_page_indicator: Supports NONE, SLIDE, WARM, COLOR, SCALE, drop.pub. dev/packages/fl…
swiper.dart
The main class of the plugin has three constructors: the original constructor Swiper(), swiper.children (), and swiper.list (). The latter two methods call Swiper() by receiving the parameter conversion.
Supports four layouts: DEFAULT, STACK, TINDER, CUSTOM.
custom_layout.dart
When the Layout is CUSTOM, CustomLayoutOption and a variety of TransformBuilder are defined. When the page is converted, addOpacity, addTranslate, addScale and addRotate are added.
swiper_controller.dart
SwiperController indirectly inherits From ChangeNotifier to record user action events and notify listeners. The main function is to control the switching of the page. The main methods are as follows:
- StartAutoplay () : auto play
- StopAutoplay () : Stops playing
- Move (int index, {bool animation: true}) : moves to a page
- Next ({bool animation: true}) : Next page
- Previous ({bool animation: true}) : Previous page
Inheritance relationship:
SwiperController extends IndexController...
IndexController extends ChangeNotifier...
Copy the code
SwiperController method example:
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();
}
Copy the code
Caller example:
controller.addListener(_onController);
void _onController() {
switch (_controller.event) {
case SwiperController.START_AUTO_PLAY:
if (_timer == null) _startAutoplay();
break; }}Copy the code
swiper_control.dart
SwiperControl builds left and right buttons that notify listeners when clicked through SwiperController’s Previous and Next methods.
swiper_pagination.dart
SwiperPagination, custom page number indicator.
Hold the SwiperPlugin object and render the page indicator through the Build method of SwiperPlugin.
Widget build(BuildContext context, SwiperPluginConfig config) {
...
}
Copy the code
SwiperPluginConfig objects have many of Swiper’s properties, such as itemCount, loop, PageController, SwiperController, and so on.
The page indicator of the plugin does not implement the clickable function of the page number. If readers want to achieve clickable jump of Dots, Slide, etc., they can add a GestureDetector to the Build method and realize the jump through the SwiperController object.
swiper_plugin.dart
SwiperPlugin is an abstract class that serves SwiperPagination and contains a build method. Note that the build method holds the SwiperPluginConfig object.
Its implementation class SwiperControl FractionPaginationBuilder, RectSwiperPaginationBuilder, DotSwiperPaginationBuilder, SwiperCustomPagination, SwiperPagination.
abstract class SwiperPlugin {
const SwiperPlugin();
Widget build(BuildContext context, SwiperPluginConfig config);
}
Copy the code
flutter_swiper.dart
The DART file imports each of these files for use by callers.
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';
Copy the code
Implementation approach
- The basic layout is still PageView.
- NotificationListener wraps PageView to implement scroll listening.
- SwiperController realizes control of PageView operations, such as previous page, next page, automatic scrolling, stop scrolling and so on.
- SwiperControl switches between previous and subsequent pages. Unlike SwiperController, It renders buttons through builds.
- SwiperPagination Render page number indicator.
- GestureDetector wraps the PageView Item Widget to implement Item click listening.
- ItemCount is the number of actual items. ItemCount in PageView passes the actual ItemCount plus an intermediate value of 1000000000.
- OnPageChanged listening to PageView implements page switch listening.
- Define Timer, implement every autoplayDelay to switch to the next page using SwiperController’s next method.
- AnimatedBuilder, AnimatedBuilder, transform. rotate, transform. translate, transform. scale, etc.
Function implementation
After you read the introduction above, do you want to try to implement it yourself? Let’s go from simple to complex and implement its features step by step (time is limited, not all features are implemented). If you have any questions, we can discuss them together.
Implementation steps
- Step 1: Preliminarily encapsulate PageView
- Implement item click
- Implement switch listening
- Step 2: Customize SwiperController
- The next page
- The previous page
- Step 3: Implement an infinite loop
- Step 4: realize automatic broadcast and conversion animation
- Step 5: Implement page number display
Step 1: Preliminarily encapsulate PageView
Create a program named flutter_swiper, add assets three images, and execute 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,
);
}
Copy the code
Step 2: Customize SwiperController
Custom 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 voidinitState() { 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; }}Copy the code
Step 3: Implement an infinite loop
Loop parameter definitions:
- Bool loop: Indicates infinite loop mode.
For infinite loop mode, pass swiper. itemCount plus a constant value (2000000000) to PageView.itemCount, Swiper. Index and constant value (1000000000) is passed to the PageView. PageController. InitialPage.
Note:
- PageView itemBuilder, index is Swiper index, that is, no constant value added.
- Inside PageView onPageChanged, index is index of Swiper, that is, without constant value.
- Previous and Next of SwiperController are added with constant values.
In general, we need to pay attention to the actual RealIndex of PageView and RenderIndex of Swiper.
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;
}
Copy the code
Step 4: realize automatic broadcast and conversion animation
Definition of round seeding parameters:
- Bool autoPlay: indicates automatic multicast mode.
- Int autoPlayDelay: specifies the duration of the current page stay in multicast mode.
- Bool autoPlayDisableOnInteraction: wheel planting mode, the user whether active sliding stop by.
Transform animation parameter definitions:
- Int duration: the duration of conversion, namely PageController animationTo duration.
- Curve Curve: indicates a conversion Curve
Implementation idea of round seeding:
- Listen for START_AUTO_PLAY and STOP_AUTO_PLAY on SwiperController.
- Create a Timer Timer cycle pageController. AnimateToPage (duration, the curve) method.
Note:
- When the user actively scrolls the PageView, the animation needs to be stopped. That is, a NotificationListener wraps the PageView to listen for the ScrollNotification.
- When the rotation is stopped, the Timer must be destroyed in time.
- Duration is the speed of the conversion process, and autoPlayDelay is the Duration of the current page after the animation ends.
/// 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 voiddispose() { _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; }}}Copy the code
-
transformer_page_view
Page jump animation and loop encapsulation
-
transformer_page_controller
The encapsulation of the infinite loop is mainly the interchange of RealIndex and RenderIndex
DidUpdateWidget (oldWidget). If you maintain variables in the State class, such as _SwiperState, _TransformerPageViewState, etc. Notice how these variables are updated when new values are passed in.
Example:
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 changedif(_swiperController ! = widget.controller) {if(_swiperController ! = null) { _swiperController.removeListener(onChangeNotifier); } _swiperController = widget.controller;if (_swiperController != null) {
_swiperController.addListener(onChangeNotifier);
}
}
super.didUpdateWidget(oldWidget);
}
...
}
Copy the code
Step 5: Implement page number display
Swiper’s widgets and Pagination’s widgets are wrapped in stacks.
SwiperPagination Requires Swiper parameters, such as itemCount, loop, swiperController, and 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 setnull, 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: 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,
),
),
));
}
returnRow( key: key, mainAxisSize: MainAxisSize.min, children: children, ); }}Copy the code
Directory structure
The code address
Github.com/smiling1990…