效果图
这是看了RenderObject的源码,写的一个总结性的小widget。运用到了Flutter里的自定义RenderObjectWidget
手势
动画
等知识点。
该widget完成的功能
- 左右无限循环翻滚
- 模仿物理阻尼滑动
- 滑动结束主动归位
缘起
之前项目中有用到首页Banner的组件(左右无限滑动,主动切换),那会一向奉行拿来主义有轮子能跑就行,不去重复造轮子。所以在网上找了受欢迎的几个组件,比较猎奇都是怎么完成的无限切换。大致分为两类:
- 定义一个
很大的ListView比方30w个
,然后把当时的方位定位到中间第15w个下标
,以这样的方法完成左右无限滑动,左右两边都能够滑15w次
,从日常运用的视点来讲完成了无限滑动。 - 在实践的轮播数量之上额定再加上2个widget,
第1项前面参加最终1项
widget,最终1项的后边参加第1项
widget。比方有6个轮播图,那实践顺序便是F A B C D E F A
。默许显现下标1
的widgetA
,当往右边滑动时就会到下标0
显现出F
,在完结切换到下标0
后重新设置PageView的方位为(n-2),也便是下标6
对应的F
完成无限滑动, 左滑同理
这两种方法都是运用结构提供的根底组建封装而成的,榜首种方法从程序的视点来讲是伪无限,第二种方法是真无限可是在榜首项和最终一项会停住没法接连翻滚 像这个姿态:
我期望的姿态是这样:
无限滑动
前面有说到的两种计划比较容易完成,代码量很少运用现有的滑动widget(ListView和PageView)即可。这里开端正式讲不凭借ListView、PageView来完成无限滑动的逻辑。
忘掉上面的两种方法,咱们这里是经过RenderObject完成那么一切的widget都听我号令。只需想通什么是无限循环—-即首位相连
,榜首个前面的永远是最终一个,最终一个的前面永远是榜首个。
先看完好的布局进程,实践只会显现中间无阴影的部分。这里是为了便于了解,将布局都绘制出来,用阴影遮挡表明该区域不行见。
为了便利描绘红色的1取名为firstChild
当firstChild
处于可见区域时布局应该是这个姿态,才能确保右滑的时分左面能显现出正确的widget。
初始状况的姿态
firstChild右滑一格
firstChild左滑一格
firstChild左滑两格
为了便利核算没有采用两边widget平均摆放的布局方法。而是左面只放一个,右边
按顺序摆放。附上动态滑动的gif
右滑
左滑
看到这里其实无限滑动的原理现已十分显着了,依据左右滑动的方向,和间隔来动态的布局就能够了
所谓知易行难,原理是看理解了,那么这个动态布局怎么去完成呢?
布局
要害点在于红色1的wiget
下文用firstChild
来代替。firstChild
是榜首个子widget,后续的其他子widget布局方位都依赖于firstChild
。也便是说咱们只需要管理好firstChild
的方位就能够了。管理firstChild
也便是管理好它在左滑和右滑时的方位改换
前置条件:子widget同宽,可见区域的巨细等于子widget的巨细
左右鸿沟:左面界(-size.width) 右鸿沟((n-1)*size.width)
firstChild左滑的方位改换
当firstChild
超越黄线的方位-size.width
时(上图),就把它放到紫6的后边(n-1)*size.width
(下图)
firstChild右滑的方位改换
当firstChild超越黄线方位(n-1)*size.width
时(上图),又把它放到最左面的方位-size.width
(下图)
正常滑动
其他情况下就依据手势滑动的方向和间隔线性加减就行了。仅仅到了这两个鸿沟值时进行方位互换代码如下
//firstChild到达右鸿沟
if (_offset.dx >= (count - 1) * size.width) {
double d = _offset.dx - (count - 1) * size.width;
_offset = Offset(-size.width + d, 0);
data.offset = _offset;
}
//first到达左面界
else if (_offset.dx < -size.width) {
double d = _offset.dx + size.width;
_offset = Offset((count - 1) * size.width + d, 0);
}
其他子widget的方位处理
前面提过只需要处理好firstChild
的方位就行了,为什么呢?由于咱们的布局是一个线性摆放的布局而且子widget宽高相等,只需知道开始方位
和当时是第几个
就能够算出来精确的方位。比方第n个child在x轴上的偏移量便是double currentDx = n * wdith + firstChild.dx
乘以n个宽度+firstChild在x轴的偏移量。然后处理下超越右鸿沟的情况即可:用左面界的方位+超出右鸿沟的间隔
//核算i的方位:乘以i个宽度+firstChild在x轴的偏移量
Offset _next = Offset(i * size.width + _offset.dx, 0);
//超越右鸿沟,
if (_next.dx >= (count - 1) * size.width) {
//核算溢出右鸿沟的间隔
double overflowOffset = _next.dx - (count - 1) * size.width;
//左面界的方位+超出的间隔
_next = Offset(overflowOffset - size.width, 0);
}
到这里一个无限滑动的widget现已完结百分之99了,剩余的便是加上水平滑动手势。把滑动的数据间隔传给firstChild
即可
void _dragOnUpdate(DragUpdateDetails details) {
_offset = Offset(details.delta.dx + _offset.dx, 0);
markNeedsLayout();}
阻尼滑动
想要在松开手指后,使widget继续坚持滑动就需要在手势识别器的onEnd方法上做文章。在onEnd回调中会传一个手指脱离时滑动的速度primaryVelocity
有了初始速度咱们就能够模仿出列表滑动的整个衰减进程。
依据物理公式v = v0+at。能够假定一个加速度a=300,先核算出动画执行的时刻t。
t = (v-v0)/a
最终的速度肯定为0,所以直接用v0/a的绝对值便是t。也便是primaryVelocity/300。由于快速滑动时permiaryVelocity很大,简单点便是把t约束在1-3秒以内。
依据位移公式:s=v0t+at
核算滑动的间隔s
:
double a = 300;
double t = (math.max(math.min((v / a).abs(), 3), 1));
double s = 0;
// 位移公式:s=v0t+at
s = v.abs() * t + a * t * t / 2;
//s缩小十倍,恢复运动的方向
s = s / 10 * (v > 0 ? 1 : -1);
s缩小十倍是由于核算出来的间隔太大了,导致动画播映的时分特别快。 有了动画时刻t和手指脱离后需要滑动的间隔就能够写动画了,在手指脱离后播映动画。要害代码如下:
//用于估值当时动画值所滑动的间隔
var tween = Tween<double>(begin: 0.0, end: s)
.chain(CurveTween(curve: Curves.easeOutExpo));
animation?.dispose();
animation = null;
//上次翻滚的间隔
double lastS = 0;
animation ??= AnimationController(
vsync: ticker, duration: Duration(seconds: t.abs().ceil()))
..addListener(() {
double currentS = -tween.evaluate(animation!);
if (currentS != 0) {
//增量核算滑动的间隔(_offset是firstChild的坐标)
_offset = Offset(_offset.dx + (lastS - currentS), 0);
}
lastS = currentS;
markNeedsLayout();
});
animation!.forward(from: 0);
到这里手指脱离屏暗地,模仿阻尼滑动的动画也现已完结了,仅仅s
的随机性不能确保widget和可视区域对齐。接下来便是最终一步,主动对齐
主动归位
前面说到在手指脱离后的阻尼滑动结束时无法对齐,是由于s
是依据primaryVelocity
核算的,每一次速度不同就会导致停在不同的方位。那么想要他主动对齐也很简单,便是让firstChild
的_offset
+s
的值是可见区域width
的倍数就行了。
代码如下:
//中止的方位
double endPos = _offset.dx + s;
//取余数
double remPos = endPos % size.width;
//补整
double complement =
remPos < (size.width / 2) ? -remPos : size.width - remPos;
s = s + complement;
4行代码搞定主动归位。
写在最终
这两天一向在构思这个循环滑动的widge怎么完成,在笔记本上整整图画了两页手稿,最终决定以这种方法完成,算是比较偏高级一点的自定义widget,包容的内容也比较丰富(手势、动画、RenderObject),可是整个代码量才100多行,可读性还是很强的,后续链接贴在谈论区供有需要的同学浏览。欢迎小伙伴在谈论区留言评论