FishPlayer

一个喜欢摸鱼的废物

0%

UGUI简易循环滚动列表

由于个人发展方向的原因,上个月选择裸辞跑路。
跑路前有个任务是把支持动态尺寸元素的循环滚动列表接入(滚动列表可以找插件也可以自己搓),因为网上找来的感觉接入要时间而且自己原本有简单的半成品(很早以前做的,感觉设计思路和当前在项目中UI框架比较匹配)接入更快所以想自己搓完再接入。
结果最后时间活儿还是多又杂,只交了个半成品。跑路以后花了点时间整理一下,感觉也算是差不多能用了,基本的功能都有,这边打算简单的分享一下。

理念

在制作前我也有大概看过诸如 LoopScrollRect 或 FancyScrollView 这些比较成功的插件,他们都实现了一个自己的 Scroller。
我浅显的认为,Scroll 的作用是模拟大内容滚动并计算进度。
那时的我才刚入行没多久,工程能力比较烂,看到1000多行的 Scroller 直接晕了。最后决定要尽可能直接使用 Unity 自带的 ScrollRect 来完成这个需求。

在后来的开发过程中,我发现 Unity 自带的 ScrollRect 在功能上确实无法满足我的需求,我选择直接继承它并做了一些小的修改。

  • 添加一个简单的速度检测器,用于限制移速的最大最小值,防止难以处理的超快速滚和莫修有超低速滚动。
  • 新增一个属性用于读写 m_ContentStartPosition 这个字段,为了防止修改原滚动物体尺寸和位置后引起的基类内的莫须有运算。
  • 提供一些滚动相关时机的事件。

实现思路

当前仅实现了3种滚动视图,因为这3种都是项目里要用的。都是用我自认为比较简单移动的思路去实现的。
不过在实际实现的时候,还是遇到了不少不好处理的麻烦,搞了一些不是很清晰的处理方法。

Grid scroll

GridSample

这个功能就相当于在原本的 ScrollRect Content 里使用 GridLayout,用简单的做法做应该就会简单。
我的实现思路是这样

  • 开放滚动配置以及网格布局给让交互/程序。
  • 根据填的布局配置和数据源的数据量来计算滚动内容的尺寸,并把这个尺寸直接赋给 Unity ScrollRect 的滚动内容。
  • 根据滚动内容的位置的去计算当前应该显示哪行哪列的哪些 Grid。
  • 把缓存着的 Grid 摆到 Content 中。

在这个思路下,难点或说麻烦的点应该是自己实现 Grid 的布局排布。既然是内置的 Grid 布局计算,跳转到特定元素时也能很方便算出对应的横纵滚动进度。

Radial scroll

VerticalSample

这个功能的思路和上一个基本一致

  • 开放配置滚动和弧形布局相关的参数给交互/程序。
  • 根据弧形布局的参数和数据源的数据量来计算滚动内容的尺寸(这里用的弦长)以及圆弧总共需要滚动的角度综合。
  • 根据 ScrollRect 的滚动进度的去旋转圆弧,根据旋转的角度来计算圆弧中需要显示的元素序列。
  • 把缓存着的元素根据弧形布局参数摆到一个专门用来放置元素的 Transform 下。

这个功能本身就比较简单,但如果没有在代码中把弧形布局相关的一些处理写清晰的话,处理元素跳转的时候会比较麻烦。

Single direction scroll

ChatSample

这个功能是最复杂最困难的。需要支持尺寸待定的元素(视图层刷新后才能计算出确切尺寸),在使用 Unity ScrollRect和相关的布局组件的情况下,我添加了多且杂乱的处理和检查给ScrollRect Content来尽可能保持看起来正确。
基本思路不是很复杂,但是实际是实现的时候真的有比较多的细节需要处理

  • 开放非常简单的滚动和布局配置给交互/程序(仅有滚动方向和间隔)。
  • 根据视口的尺寸大小以及当前的滚动内容来检测是需要从滚动内容的头/尾回收元素或是添加新元素。回收和添加都是分开两轮的操作,每轮操作完后要及时计算并调整滚动内容的尺寸(不等帧末的布局计算)。
  • 滚动条和滚动进度的计算方式和前两种不同(滚动条和滚动进度无法从 ScrollRect中获得),根据滚动内容里的元素的位置和自定义的相对位置来计算滚动进度和滚动条值。

这个思路最麻烦的点就是要根据视口尺寸和当前滚动内容的位置和尺寸来增减元素,处理首尾以及单帧内多次增减会变得非常繁琐。
当前的做法不太好,我原本预想的是仅根据滚动内容的位置和自己维护的一个虚拟的尺寸来做检测和调整,但这是我快做完了才想到,因为赶着给前同事一个差不多能用的版本所以暂时还没改。

除此之外,滚动进度的计算也很麻烦。因为元素尺寸是待定的,所以无法再预先计算完整的滚动内容尺寸。为了尽可能避免进度条反复跳动(使用LoopScrollRect和待定尺寸元素的情况下会出现反复跳动),我换了一种计算进度的方法。
我选择计算元素和其相对参考线的位置。
什么是相对参考线呢,举个简单的例子,对于顶部往下排的布局,视口的最高点就是相对参考线,此时的元素自身的参考线也是元素的最高点。
那么对于数据源里的最后一个元素,视口的底部就是它的参考线,其元素参自身的考线则是这个元素的最低点。
对于数据源的中间的元素(如果有的话),视口的中间就是它的参考线,元素自身的参考线是元素的中间。

当元素的参考线和视口里的参考线重合时,以上三个情况分别对应滚动进度的 0, 0.5, 1。如果数据量为n,则滚动进度则是从0~1里切n-1个格子。滚动进度则是根据当前显示的元素与其参考线的距离来计算。
元素跳转则是上面这个进度计算的相反计算。
说起来比较抽象难懂,但是我代码写得更抽象还是不直接贴出来了,大概就是这么个思路。总之这样是尽量避免了滚动条的反复跳动,但内容的滚动速度看起来就不线性了。

结尾

最后放上仓库。
现在还是个比较简陋的状态,对这套功能我也暂时没有别的目标。
以后应该还是会逐步完善功能,然后把一些简单的通用功能也尽可能补上。

SimpleRecycScroll