循环逻辑,按照网上说的,例如:有3张图,那么排版出来的应该是这样的:20120。为什么的是这样的呢?首位相连,在BannerView滚动过程中调整Page的索引位置。注意读取数据时一定要注意头尾应该取的数据,以及真实索引的位置。
QQ截图20191229125412.png下面是一个简单的实现过程,支持自动循环:
import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
typedef BannerViewItemBuilder = Widget Function(
BuildContext context, int postion, int realPosition, dynamic itemData);
class BannerView<T> extends StatefulWidget {
BannerView({
Key key,
double width,
double height = 210,
@required this.dataSource,
@required BannerViewItemBuilder itemBuilder,
Duration duration,
}) : assert(height != null && height > 0, '高度不能小于0'),
assert(dataSource?.isNotEmpty == true, '数据源不能为空'),
width = width,
height = height,
virutalDataSource = dataSource?.isNotEmpty == true
? [dataSource[dataSource.length - 1], ...dataSource, dataSource[0]]
: throw Exception('数据源不能为空'),
duration = duration ?? const Duration(seconds: 5),
itemBuilder = itemBuilder,
super(key: key);
final double width;
final double height;
final List<String> dataSource;
final List<String> virutalDataSource;
final Duration duration;
final BannerViewItemBuilder itemBuilder;
@override
_BannerViewState createState() => _BannerViewState();
}
class _BannerViewState extends State<BannerView>
with SingleTickerProviderStateMixin {
PageController _pageController;
int _virtualPosition = 1, _position = 0, _indicatorPosition = 0;
Timer _timer; //动画定时器
/// 初始化
void _initializer() {
_pageController = PageController(initialPage: 1)
..addListener(_onListenScrollOffsetChanged);
_initAndStartTimer(); //初始化并启动动画定时器
}
/// 获取真实的索引位置
/// + `pagePosition` virtual的Page索引位置
int _getRealPagePosition(int pagePosition) {
if (pagePosition == widget.virutalDataSource.length - 1)
pagePosition = 1;
else if (pagePosition == 0)
pagePosition = widget.virutalDataSource.length - 2;
else
pagePosition -= 1;
return pagePosition;
}
/// 监听滚动的Offset的变化
void _onListenScrollOffsetChanged() {
final Decimal curPageOffset =
Decimal.parse(_pageController.offset.toString()) /
Decimal.parse(
(widget.width ?? MediaQuery.of(context).size.width).toString());
//设置indicator所在的位置
setState(() => _indicatorPosition =
_getRealPagePosition(curPageOffset.toDouble().round()));
final curPageInt = int.tryParse(curPageOffset.toString());
if (curPageInt != null) {
_virtualPosition = curPageInt;
//设置Page的逻辑位置
setState(() => _position = _getRealPagePosition(curPageInt));
if (curPageInt == 0)
_pageController.jumpToPage(_position);
else if (curPageInt == widget.virutalDataSource.length - 1)
_pageController.jumpToPage(_position);
}
}
@override
void initState() {
_initializer();
super.initState();
}
@override
void dispose() {
_releaseTimer();
_pageController
..removeListener(_onListenScrollOffsetChanged)
..dispose();
super.dispose();
}
/// 初始化并启动定时器
void _initAndStartTimer() {
_releaseTimer();
_timer = Timer.periodic(widget.duration, (_) {
_pageController.animateToPage(++_virtualPosition,
duration: const Duration(milliseconds: 800),
curve: Curves.easeInOutQuart);
});
}
/// 释放定时器
void _releaseTimer() {
if (_timer != null) _timer.cancel();
_timer = null;
}
@override
Widget build(BuildContext context) =>
Stack(alignment: Alignment.bottomCenter, children: [
GestureDetector(
onTapDown: (_) => _releaseTimer(),
onTapUp: (_) => _initAndStartTimer(),
onTapCancel: () => _initAndStartTimer(),
child: SizedBox(
width: widget.width,
height: widget.height,
child: PageView.builder(
controller: _pageController,
pageSnapping: true,
physics: BouncingScrollPhysics(),
itemCount: widget.virutalDataSource?.length ?? 0,
itemBuilder: (context, position) => widget.itemBuilder(
context,
position,
position == widget.virutalDataSource.length - 1
? 0
: (position == 0
? widget.virutalDataSource.length - 2
: position - 1),
widget.virutalDataSource.elementAt(position)),
))),
_buildIndicator()
]);
/// 构建指示器
Widget _buildIndicator() => Padding(
padding: const EdgeInsets.only(bottom: 25),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (int i = 0; i < widget.dataSource.length; i++)
Container(
height: 3,
width: 15,
margin: EdgeInsets.only(left: i == 0 ? 0 : 8),
decoration: BoxDecoration(
color: _indicatorPosition == i
? Colors.white
: Colors.white60,
borderRadius: BorderRadius.all(Radius.circular(1000))))
]));
}
实例代码:
BannerView<String>(
height: 180,
dataSource: [
'http://e.hiphotos.baidu.com/image/pic/item/4610b912c8fcc3cef70d70409845d688d53f20f7.jpg',
'http://a.hiphotos.baidu.com/image/pic/item/0ff41bd5ad6eddc40189fc4133dbb6fd52663319.jpg',
'http://g.hiphotos.baidu.com/image/pic/item/c2cec3fdfc03924590b2a9b58d94a4c27d1e2500.jpg'
],
itemBuilder: (_, __, ___, itemData) => CachedNetworkImage(
imageUrl: itemData,
imageBuilder: (_, imageProvider) => Padding(
padding: const EdgeInsets.all(15),
child: Container(
height: 180,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(10)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover)))),
placeholder: (_, __) => SizedBox(height: 180),
errorWidget: (_, __, ___) => SizedBox(height: 180)))
网友评论