当使用ScrollView显示列表数据时,如果数据很少,一般有多少条数据就会创建多少个ItemView,然后一股脑将这些ItemView挂到ScrollView下的Content下;但是当数据量很大时,一下全部创建时压力就会给足到内存这边,这个时候需要动态复用ItemView,合理使用有限资源。
其实网上已经有一些现成的轮子,对于滑动、复用这些基本需求,都已经实现了。如果只有这些基本需求,就没有必要重复造轮子。但是除了这些基本的需求,还有些动画相关的,所以就需要一个可定制化更高的适配器,来应对层出的需求。
一、简介
1. 支持的功能/效果
- 复用ItemView支持
- 多类型ItemView支持
- ItemView出现动画支持
- 列表单ItemView刷新支持
- 列表全ItemView刷新支持
- 高亮效果支持
- 滑动动画支持
- 横向/纵向支持
- 等宽/等高瀑布流支持
2. 实现原理
原理并不复杂,只是细节多些。想要复用ItemView,无非就是监听ScrollView的滑动状态,根据当前的滑动位置来实时更新各个ItemView的状态。至于滑动动画,则是通过DOTween,改变可滑动区域Content的anchorX或者anchorY坐标来实现。
对于每个列表,ItemView至多只会创建填满一个ScrollView时的数量。这些ItemView交替使用,即可展示所有的Data。
3. ViewHolder
ViewHolder是和ItemView在实例数量上是一一对应的关系,对于创建的每个ItemView,都会创建一个ViewHolder与其关联。ViewHolder,顾名思义,其中维护着该ItemView中的所有View。每次需要更新ItemView的显示情况时,则需要将需要展示的Data传入,然后ViewHolder根据Data更新自身之中的View即可。
ViewHolder的职责很单一,只是根据Data更新View的状态,至于其他的逻辑操作,比如ItemView中的按钮点击,以及点击之后的操作等,通通交给外面组件处理。
上面说到,每个ItemView实例对应一个ViewHolder实例。此外,每种ItemView,对应一种ViewHolder,意思即为,当一个列表有多个类型的ItemView时,这时就需要创建多个类型的ViewHolder。
例如,一个商店列表,会有金币样式CoinItemView、钻石样式DiamondItemView、礼包样式BundleItemView,那么就需要有CoinViewHolder、DiamondViewHolder以及BundleViewHolder。
4. Adapter
当Data的数量和ItemView的数量不匹配时,要想通过有限的ItemView展示超出其数量的Data,这个时候就需要有个组件在其中将这二者调和,使其可正常工作,而这个事就是适配器在做。
适配器里维护着所有的ItemView、ViewHolder以及Data;同时监听着ScrollView的滑动状态,根据滑动状态取出合适的Data,并找出空闲的ItemView,使其展示出Data并放到列表的恰当位置;此外还要提供操作列表的方法,如果更新数据、滑动到指定位置,等。
由于对于所有列表,有一些操作和处理是通用的,故将其抽取到一个公共类中,即为StaggeredAdapter,使用时只需继承此类,并实现特有的方法即可。
二、使用示例
在这种模式下,我们可以在意识中不再关注整个列表,而是把精力集中到ItemView上,对于列表要做的事情,就是根据数据正确的显示ItemView中的各个元素。
1. 创建Prefab
如图,在ScrollView的Content下创建一个名为HItemViewIgnore的ItemView。这个命名无要求,只要不重复即可,我的这个是因为要自动生成部分代码,所以要符合其中的一些规则。
其中,这个ItemView有5个元素,包括2个背景图,3个文字标签。
2. 创建ItemData
即为ItemView需要显示的数据,示例中比较简单,只需要展示学号、名字和分数,如下:
1 | public class ItemData |
3. 创建ViewHolder
如上所言,ViewHolder的作用就是维护ItemView中的所有元素,并且根据传入的Data,来正确地显示各个组件。
1 | public class HorizontalViewHolder : ViewHolder |
构造函数中的view参数,即为ItemView,在这里指的是第一步中创建的额HItemViewIgnore。一般来说,拿到了RectTransform,就可以通过Find方法和GetComponent方法获取到下面挂着的所有的元素。因为这些都是样板代码,所以我是通过自动生成代码工具生成的,放在了HItemViewIgnore类中,通过其中的Get方法就可以获得实例,如下:
1 | public class HItemViewIgnore |
ViewHolder中的Bind方法的参数,即为需要展示的数据,这个参数由调用侧按需传入。这个示例中需要做的事情就是把3个参数展示出来,同时根据分数大小控制BgFlunk是否显示,不及格就显示,反之不显示。
如上,维护ViewHolder中的所有元素,并且可以根据Data正确显示,这就是ViewHolder需要做的所有的工作。
4. 创建Adapter
需要创建一个类,继承自StaggeredAdapter,同时实现其中的所有抽象方法。
关于其中涉及到的type参数,指的是这个列表下有几种类型的ViewHolder。通过重写GetItemType方法,可以指定每个ItemView的类型,同时通过GetItemName方法可以为每个类型指定对应的ItemView,这两个方法需要搭配使用。
比如示例中,只有一种类型的ItemView,所以GetItemType方法中,不管哪个position,都是固定返回0;而在GetItemName方法中,对于类型是0的,则是返回名为HItemViewIgnore的ItemView。
当有多个类型的时候,也可以在GetItemType按需返回其类型,然后在GetItemName中返回对应的ItemView的名字即可。
其他几个方法,都是固定的写法,代码如下:
1 | public class HorizontalAdapter : StaggeredAdapter<HorizontalViewHolder> |
5. 显示列表
造了一组随机数据,调用Show方法即可显示。
1 | private void InitHorizontalList() |
附录
详细的代码可查看Git仓库:
https://github.com/oynix/StaggeredAdapter