1+ import 'dart:async' ;
2+ import 'dart:ffi' ;
3+ import 'package:flutter/material.dart' ;
4+
5+ List <T > map <T >(List list, Function handler) {
6+ List <T > result = [];
7+ for (var i = 0 ; i < list.length; i++ ) {
8+ result.add (handler (i, list[i]));
9+ }
10+ return result;
11+ }
12+
13+ class GFSlider extends StatefulWidget {
14+ GFSlider (
15+ {@required this .items,
16+ this .pagerSize,
17+ this .passiveIndicator,
18+ this .activeIndicator,
19+ this .pagination,
20+ this .height,
21+ this .aspectRatio: 16 / 9 ,
22+ this .viewportFraction: 0.8 ,
23+ this .initialPage: 0 ,
24+ int realPage: 10000 ,
25+ this .enableInfiniteScroll: true ,
26+ this .reverse: false ,
27+ this .autoPlay: false ,
28+ this .autoPlayInterval: const Duration (seconds: 4 ),
29+ this .autoPlayAnimationDuration = const Duration (milliseconds: 800 ),
30+ this .autoPlayCurve: Curves .fastOutSlowIn,
31+ this .pauseAutoPlayOnTouch,
32+ this .enlargeMainPage = false ,
33+ this .onPageChanged,
34+ this .scrollPhysics,
35+ this .scrollDirection: Axis .horizontal})
36+ : this .realPage = enableInfiniteScroll ? realPage + initialPage : initialPage,
37+ this .pageController = PageController (
38+ viewportFraction: viewportFraction,
39+ initialPage: enableInfiniteScroll ? realPage + initialPage : initialPage,
40+ );
41+
42+ /// The pagination dots size can be defined using [double] .
43+ final double pagerSize;
44+
45+ /// The slider pagination's active color.
46+ final Color activeIndicator;
47+
48+ /// The slider pagination's passive color.
49+ final Color passiveIndicator;
50+
51+ /// The [GFSlider] shows pagination on state true.
52+ final bool pagination;
53+
54+ /// The widgets to be shown as sliders.
55+ final List <Widget > items;
56+
57+ /// Set slide widget height and overrides any existing [aspectRatio] .
58+ final double height;
59+
60+ /// Aspect ratio is used if no height have been declared. Defaults to 16:9 aspect ratio.
61+ final double aspectRatio;
62+
63+ /// The fraction of the viewport that each page should occupy. Defaults to 0.8, which means each page fills 80% of the slide.
64+ final num viewportFraction;
65+
66+ /// The initial page to show when first creating the [GFSlider] . Defaults to 0.
67+ final num initialPage;
68+
69+ /// The actual index of the [PageView] .
70+ final num realPage;
71+
72+ /// Determines if slides should loop infinitely or be limited to item length. Defaults to true, i.e. infinite loop.
73+ final bool enableInfiniteScroll;
74+
75+ /// Reverse the order of items if set to true. Defaults to false.
76+ final bool reverse;
77+
78+ /// Enables auto play, sliding one page at a time. Use [autoPlayInterval] to determent the frequency of slides. Defaults to false.
79+ final bool autoPlay;
80+
81+ /// Sets Duration to determent the frequency of slides when [autoPlay] is set to true. Defaults to 4 seconds.
82+ final Duration autoPlayInterval;
83+
84+ /// The animation duration between two transitioning pages while in auto playback. Defaults to 800 ms.
85+ final Duration autoPlayAnimationDuration;
86+
87+ /// Determines the animation curve physics. Defaults to [Curves.fastOutSlowIn] .
88+ final Curve autoPlayCurve;
89+
90+ /// Sets a timer on touch detected that pause the auto play with the given [Duration] . Touch Detection is only active if [autoPlay] is true.
91+ final Duration pauseAutoPlayOnTouch;
92+
93+ /// Determines if current page should be larger then the side images,
94+ /// creating a feeling of depth in the carousel. Defaults to false.
95+ final bool enlargeMainPage;
96+
97+ /// The axis along which the page view scrolls. Defaults to [Axis.horizontal] .
98+ final Axis scrollDirection;
99+
100+ /// Called whenever the page in the center of the viewport changes.
101+ final Function (int index) onPageChanged;
102+
103+ /// How the carousel should respond to user input.
104+ ///
105+ /// For example, determines how the items continues to animate after the
106+ /// user stops dragging the page view.
107+ ///
108+ /// The physics are modified to snap to page boundaries using
109+ /// [PageScrollPhysics] prior to being used.
110+ ///
111+ /// Defaults to matching platform conventions.
112+ final ScrollPhysics scrollPhysics;
113+
114+ /// [pageController] is created using the properties passed to the constructor
115+ /// and can be used to control the [PageView] it is passed to.
116+ final PageController pageController;
117+
118+ /// Animates the controlled [GFSlider] to the next page.
119+ ///
120+ /// The animation lasts for the given duration and follows the given curve.
121+ /// The returned [Future] resolves when the animation completes.
122+ Future <void > nextPage ({Duration duration, Curve curve}) {
123+ return pageController.nextPage (duration: duration, curve: curve);
124+ }
125+
126+ /// Animates the controlled [GFSlider] to the previous page.
127+ ///
128+ /// The animation lasts for the given duration and follows the given curve.
129+ /// The returned [Future] resolves when the animation completes.
130+ Future <void > previousPage ({Duration duration, Curve curve}) {
131+ return pageController.previousPage (duration: duration, curve: curve);
132+ }
133+
134+ /// Changes which page is displayed in the controlled [GFSlider] .
135+ ///
136+ /// Jumps the page position from its current value to the given value,
137+ /// without animation, and without checking if the new value is in range.
138+ void jumpToPage (int page) {
139+ final index = _getRealIndex (pageController.page.toInt (), realPage, items.length);
140+ return pageController.jumpToPage (pageController.page.toInt () + page - index);
141+ }
142+
143+ /// Animates the controlled [GFSlider] from the current page to the given page.
144+ ///
145+ /// The animation lasts for the given duration and follows the given curve.
146+ /// The returned [Future] resolves when the animation completes.
147+ Future <void > animateToPage (int page, {Duration duration, Curve curve}) {
148+ final index = _getRealIndex (pageController.page.toInt (), realPage, items.length);
149+ return pageController.animateToPage (pageController.page.toInt () + page - index,
150+ duration: duration, curve: curve);
151+ }
152+
153+ @override
154+ _GFSliderState createState () => _GFSliderState ();
155+ }
156+
157+ class _GFSliderState extends State <GFSlider > with TickerProviderStateMixin {
158+ Timer timer;
159+
160+ @override
161+ void initState () {
162+ super .initState ();
163+ timer = getPlayTimer ();
164+ }
165+
166+ Timer getPlayTimer () {
167+ return Timer .periodic (widget.autoPlayInterval, (_) {
168+ if (widget.autoPlay) {
169+ widget.pageController
170+ .nextPage (duration: widget.autoPlayAnimationDuration, curve: widget.autoPlayCurve);
171+ }
172+ });
173+ }
174+
175+ void pauseOnTouch () {
176+ timer.cancel ();
177+ timer = Timer (widget.pauseAutoPlayOnTouch, () {
178+ timer = getPlayTimer ();
179+ });
180+ }
181+
182+ Widget getPageWrapper (Widget child) {
183+ if (widget.height != null ) {
184+ final Widget wrapper = Container (height: widget.height, child: child);
185+ return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
186+ ? addGestureDetection (wrapper)
187+ : wrapper;
188+ } else {
189+ final Widget wrapper = AspectRatio (aspectRatio: widget.aspectRatio, child: child);
190+ return widget.autoPlay && widget.pauseAutoPlayOnTouch != null
191+ ? addGestureDetection (wrapper)
192+ : wrapper;
193+ }
194+ }
195+
196+ Widget addGestureDetection (Widget child) =>
197+ GestureDetector (onPanDown: (_) => pauseOnTouch (), child: child);
198+
199+ @override
200+ void dispose () {
201+ super .dispose ();
202+ timer? .cancel ();
203+ }
204+
205+ int _current = 0 ;
206+
207+ @override
208+ Widget build (BuildContext context) {
209+ return Stack (
210+ children: < Widget > [
211+ getPageWrapper (PageView .builder (
212+ physics: widget.scrollPhysics,
213+ scrollDirection: widget.scrollDirection,
214+ controller: widget.pageController,
215+ reverse: widget.reverse,
216+ itemCount: widget.enableInfiniteScroll ? null : widget.items.length,
217+ onPageChanged: (int index) {
218+
219+ int currentPage = _getRealIndex (index + widget.initialPage, widget.realPage, widget.items.length);
220+ if (widget.onPageChanged != null ) {
221+ widget.onPageChanged (currentPage);
222+ _current = currentPage;
223+ }
224+ _current = currentPage;
225+ },
226+ itemBuilder: (BuildContext context, int i) {
227+ final int index =
228+ _getRealIndex (i + widget.initialPage, widget.realPage, widget.items.length);
229+
230+ return AnimatedBuilder (
231+ animation: widget.pageController,
232+ child: widget.items[index],
233+ builder: (BuildContext context, child) {
234+ // on the first render, the pageController.page is null,
235+ // this is a dirty hack
236+ if (widget.pageController.position.minScrollExtent == null ||
237+ widget.pageController.position.maxScrollExtent == null ) {
238+ Future .delayed (Duration (microseconds: 1 ), () {
239+ setState (() {});
240+ });
241+ return Container ();
242+ }
243+ double value = widget.pageController.page - i;
244+ value = (1 - (value.abs () * 0.3 )).clamp (0.0 , 1.0 );
245+
246+ final double height =
247+ widget.height ?? MediaQuery .of (context).size.width * (1 / widget.aspectRatio);
248+ final double distortionValue =
249+ widget.enlargeMainPage ? Curves .easeOut.transform (value) : 1.0 ;
250+
251+ if (widget.scrollDirection == Axis .horizontal) {
252+ return Center (child: SizedBox (height: distortionValue * height, child: child));
253+ } else {
254+ return Center (
255+ child: SizedBox (
256+ width: distortionValue * MediaQuery .of (context).size.width, child: child));
257+ }
258+ },
259+ );
260+ },
261+ )),
262+ widget.pagination == true ? Positioned (
263+ left: 0.0 ,
264+ right: 0.0 ,
265+ bottom: 0.0 ,
266+ child: Container (
267+ child: Row (
268+ mainAxisAlignment: MainAxisAlignment .center,
269+ children: map <Widget >(
270+ widget.items,
271+ (indexx, url) {
272+ return Container (
273+ width: widget.pagerSize == null ? 8.0 : widget.pagerSize,
274+ height: widget.pagerSize == null ? 8.0 : widget.pagerSize,
275+ margin: EdgeInsets .symmetric (vertical: 10.0 , horizontal: 2.0 ),
276+ decoration: BoxDecoration (
277+ shape: BoxShape .circle,
278+ color: _current == indexx
279+ ? widget.activeIndicator == null ? Color .fromRGBO (0 , 0 , 0 , 0.9 ) : widget.activeIndicator
280+ : widget.passiveIndicator == null ? Color .fromRGBO (0 , 0 , 0 , 0.4 ) : widget.passiveIndicator,
281+ ),
282+ );
283+ },
284+ ),
285+ ),
286+ ),
287+ ) : Container (),
288+ ],
289+ );
290+ }
291+ }
292+
293+ /// Converts an index of a set size to the corresponding index of a collection of another size
294+ /// as if they were circular.
295+ ///
296+ /// Takes a [position] from collection Foo, a [base] from where Foo's index originated
297+ /// and the [length] of a second collection Baa, for which the correlating index is sought.
298+ ///
299+ /// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
300+ /// We need to repeat the images to give the illusion of a never ending stream.
301+ /// By calling _getRealIndex with position and base we get an offset.
302+ /// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
303+ /// to be placed in the given position.
304+ int _getRealIndex (int position, int base , int length) {
305+ final int offset = position - base ;
306+ return _remainder (offset, length);
307+ }
308+
309+ /// Returns the remainder of the modulo operation [input] % [source] , and adjust it for
310+ /// negative values.
311+ int _remainder (int input, int source) {
312+ final int result = input % source;
313+ return result < 0 ? source + result : result;
314+ }
0 commit comments