简介
通过组合
PageView
方式,实现一个自定义的infinity_slider
小部件,完整代码
- 支持无限滚动
- 支持定时器滚动
- 支持曲线滚动
步骤
1)创建一个有状态的小部件
新建infinity_slider.dart文件,添加下方代码
import 'package:flutter/material.dart';class InfinitySlider extends StatefulWidget { @override _InfinitySliderState createState() => _InfinitySliderState();}class _InfinitySliderState extends State{ @override Widget build(BuildContext context) { return Container(); }}复制代码
2)添加可配置参数
- 要知道
PageView
小部件是没有设置可无限滚动属性的 - 我们有一个需要滑动且长度为5的小部件列表
items
PageView.builder
有个itemCount
字段,当我们构建PageView
时,如果没有设置itemCount
的话则PageView
的itemBuilder
会从0-(PageController.initialPage)-infinity
构建滑块,这时items会有溢出的危险。- 你想到了什么?假如我们有一个小部件列表
items
,我们可以通过一个函数计算出当前的items真实下标,这时items就可以向右进行无限滑动而不用担心items下标的溢出,计算函数代码如下
int _calcIndex(int input, int source) { final int result = input % source; return result < 0 ? source + result : result;}/** * _getRealIndex * * @param {int} position - 真实下标 * @param {int} base - 初始真实下标 * @return {int} length 滑块列表长度 * * @since 1.0.0 */int _getRealIndex(int position, int base, int length) { final int offset = position - base; return _calcIndex(offset, length);}复制代码
- 有了这个计算函数,让我们来写一写相关代码
import 'package:flutter/material.dart';import 'dart:async';int kRealPage = 100;int _calcIndex(int input, int source) { final int result = input % source; return result < 0 ? source + result : result;}int _getRealIndex(int position, int base, int length) { final int offset = position - base; return _calcIndex(offset, length);}typedef void UpdatePageCallback(int index);class InfinitySlider extends StatefulWidget { final int initialPage; final Listitems; final double height; final PageController pageController; InfinitySlider({ Key key, @required this.items, this.height: 120, this.initialPage: 0, }) : pageController = new PageController( initialPage: kRealPage + initialPage, ), assert(items != null), assert(items.length > 0), assert(initialPage != null), super(key: key); @override _InfinitySliderState createState() => _InfinitySliderState();}class _InfinitySliderState extends State { @override Widget build(BuildContext context) { return Container( height: widget.height, child: PageView.builder( itemBuilder: (BuildContext context, int i) { final int index = _getRealIndex(i, kRealPage, widget.items.length); return widget.items[index]; }, controller: widget.pageController, ), ); }}复制代码
3)解决向左无限滚动
- 在上面代码中,
kRealPage
被我们设置为100,假如遇到一个无聊的人往左滑动了100次,这时就不能再次往左侧滑动 - 我们可以将kRealPage设置为100000000,虽然不会有那么无聊的人往左滑动一亿次,但是我们要解决这个问题
- 让我们监听PageController滑动事件
import 'package:flutter/material.dart';import 'dart:async';int kRealPage = 100;int _calcIndex(int input, int source) { final int result = input % source; return result < 0 ? source + result : result;}int _getRealIndex(int position, int base, int length) { final int offset = position - base; return _calcIndex(offset, length);}typedef void UpdatePageCallback(int index);class InfinitySlider extends StatefulWidget { final int initialPage; final Listitems; final double height; final PageController pageController; InfinitySlider({ Key key, @required this.items, this.height: 120, this.initialPage: 0, }) : pageController = new PageController( initialPage: kRealPage + initialPage, ), assert(items != null), assert(items.length > 0), assert(initialPage != null), super(key: key); @override _InfinitySliderState createState() => _InfinitySliderState();}class _InfinitySliderState extends State { @override void initState() { super.initState(); widget.pageController.addListener(() { if (widget.pageController.page == (kRealPage - widget.items.length) || widget.pageController.page == (kRealPage + widget.items.length)) { widget.pageController.position .setPixels(MediaQuery.of(context).size.width * kRealPage); } }); } @override Widget build(BuildContext context) { return Container( height: widget.height, child: PageView.builder( itemBuilder: (BuildContext context, int i) { final int index = _getRealIndex(i, kRealPage, widget.items.length); return widget.items[index]; }, controller: widget.pageController, ), ); }}复制代码
4)添加剩余的功能
- 完整的代码如下
import 'package:flutter/material.dart';import 'dart:async';int kRealPage;double kAutoMaxPage;double kAutoMinPage;int _calcIndex(int input, int source) { final int result = input % source; return result < 0 ? source + result : result;}int _getRealIndex(int position, int base, int length) { final int offset = position - base; return _calcIndex(offset, length);}typedef void UpdatePageCallback(int index);class InfinitySlider extends StatefulWidget { final int initialPage; final Listitems; final bool autoPlay; final Duration interval; final Curve transCurve; final Duration transDuration; final double height; final PageController pageController; final UpdatePageCallback updateCallback; InfinitySlider({ Key key, @required this.items, this.height: 120, this.initialPage: 0, this.autoPlay: true, this.interval: const Duration(seconds: 2), this.transDuration: const Duration(milliseconds: 800), this.transCurve: Curves.fastOutSlowIn, this.updateCallback, }) : pageController = new PageController( initialPage: kRealPage + initialPage, ), assert(items != null), assert(items.length > 0), assert(initialPage != null), assert(autoPlay != null), assert(interval != null), assert(transDuration != null), assert(transCurve != null), super(key: key) { kRealPage = items.length; kAutoMinPage = 0.0; kAutoMaxPage = (items.length * 2).toDouble(); } @override _InfinitySliderState createState() => _InfinitySliderState(); Future nextPage({Duration duration, Curve curve}) { return pageController.nextPage(duration: duration, curve: curve); } Future previousPage({Duration duration, Curve curve}) { return pageController.previousPage(duration: duration, curve: curve); }}class _InfinitySliderState extends State { int currentPage; Timer timer; @override void initState() { super.initState(); widget.pageController.addListener(() { if (widget.pageController.page == kAutoMinPage || widget.pageController.page == kAutoMaxPage) { widget.pageController.position .setPixels(MediaQuery.of(context).size.width * kRealPage); } }); currentPage = widget.initialPage; if (widget.autoPlay) { timer = new Timer.periodic(widget.interval, (_) { widget.nextPage( duration: widget.transDuration, curve: widget.transCurve); }); } } @override Widget build(BuildContext context) { return Container( height: widget.height, child: PageView.builder( itemBuilder: (BuildContext context, int i) { final int index = _getRealIndex(i, kRealPage, widget.items.length); return widget.items[index]; }, controller: widget.pageController, onPageChanged: (int index) { currentPage = _getRealIndex(index, kRealPage, widget.items.length); if (widget.updateCallback != null) widget.updateCallback(currentPage); }, ), ); } @override void dispose() { super.dispose(); timer?.cancel(); }}复制代码
5)使用这个小部件
InfinitySlider( items: [1,2,3,4,5].map((i) { return Builder( builder: (BuildContext context) { return Container( width: MediaQuery.of(context).size.width, color: Colors.amber child: new Text('text $i', style: new TextStyle(fontSize: 16.0),) ); }, ); }).toList(),)复制代码