木骰

UGUI:一个可以自定义item尺寸的 循环滚动框

以前写的一个循环滚动框,但是其下item的尺寸都是固定的。最近遇到新需求,需要一个item尺寸可变的循环滚动框。
不过只支持横向或纵向只有一行或一列的情况。需要多行多列的,只能自己将单个Item再拆分为需要的子级items

分成GridAndLoopAdapt 与 ScrollRestricter两个部分
GridAndLoopAdapt主要用来控制item的排列,刷新
ScrollRestrictor主要用来控制Scroll的滑动限制

GridAndLoopAdapt

挂到ScrollRect的Content上,与GridLayoout类似。

Alt text
Start Corner:指定排列方向,与GridLaout相同
Is Loop Mode:是否为循环模式,即无minIndex与maxIndex, realIndex可为负
Min Index: realIndex的最小取值
Max Index: realIndex的最大取值
Spacing: 行距
Cull Offset: 超出ViewPort区裁剪的一个偏移值 注意这个 偏移值实际上会加上 Spacing。(之后所有对于Item超出ViewPort的情况的说明 都是包括了这个CullOffset+Spacing的)
Min Size: 避免 因Item尺寸为0 导致出现死循环的情况 该值的设定不能小于1

与GridAndLoop一样也使用realIndex来标记每各Item对应到数据列表的下标,
所以上面的minIndex 与 maxIndex将会决定 scroll能够滑动到的最小段与最大端,
最小端取到minIndex,最大端取到maxIndex-1

ScrollRestrictor

GridAndLoopAdapt会将scroll的moveMentType改为Unrestricted,并将content尺寸设为0,0。这样scroll的滑动就不受scroll本身控制了,需要自己来处理。 ScrollRestrictor就是干的这个事。

Alt text
ScrollRestrictor的代码基本上是从ScrillRect中拷出来的,所以参数和效果也基本一致。
Movement Type: 同ScrollRect 分为 Elastic,Clamp和Unrestricted
Elasticity: 同ScrollRect
Elastic Damp: Elastic模式计算公式的一各系数,原本在ScrollRect里的一个常量,我把它开放出来,可以调节滑到两端之后力的衰减强弱
StartCorner: 设定起始位置

具体用法

GridAndLoopAdapt挂到ScrollRect的Content下
设定好StartCorner,LoopMode ,Spacing,CullOffset 等参数,MinIndex与MaxIndex可在代码里设置,这两个参数标识了要加载到Scroll里的下标区段,一般会在代码里获取到数据后,根据list的大小来设置
ScrollRestrictor挂到ScrollRect下
MovementType与Elasticity的设置依照平常ScrollRect的用法来设置就好。
ElasticDamp按照实际效果调整
StartCorner依照Grid的设定设置,一般与Grid一致

附一个Demo

运行Demo建议打开GridAndLoopAdapt中 UpdateItem方法里的 测试代码

关于Item的加载:

原先GridAndLoop的做法是事先加载好一屏所需的item数量,之后这些item就将一直存在,然后就一直循环使用这批item(顶部超出ViewPort就将顶部的Item移到底部,底部超出则移到顶部,同时刷新Item的数据就行了)。而GridAndLoopAdapt的做法是,当有Item超出了ViewPort就Detroy掉,同时检查是否出现空缺,是则 加载一个新的Item进来。
这里对于Item的加载有两种:
一是通过Copy ItemSeed的方式,像上面的Demo那样,预先在Grid下放置一个Item作为Seed,或者也可以通过调用Grid的SetItemSeed(itemSeed)方法指定一个Seed。
之后每次加载新的Item都将Copy这个seed, 并且这个seed在运行之后将被隐藏起来,并且不会在滑动过程中被Destroy。当然它也不会作为一个item显示数据。

二是实现Grid中提供的CustomNewChildItem委托,自定义加载Item。这种方式的优先级要高于ItemSeed的方式,若在实现了该委托的同时 在 Grid下预先放置了ItemSeed,那么这个Seed 将被隐藏,不会产生任何作用。采用这种方式 在 实现了委托之后 再调一下Grid的 InitChildItems接口,初始化当前一屏所需的Item

关于Item数据的填充:

与GridAndLoop的做法一样,GridAndLoopAdapt里也提供了一个OnUpdateItem(obj,realIndex)的委托,不同的是抛弃了 index参数, 只保留GameObject和RealIndex两个参数。 原因是后者调到UpdateItem的将始终是Grid子级中的第一个或最后一个,Grid子级在Hierarchy中的排列顺序即为item实际在场景中的排列顺序。每新增一个Item都将会调起一次这个OnUpdateItem方法。

OnDestroyChildItem委托

考虑到有些情况需要自定义对item的裁剪方式,所以提供了这个委托。若没有实现,将会直接调Destroy。 实现上面的CustomNewChildItem加上这个OnDestroyChildItem,配合内存池,可以免去每次Destroy 再重新Clone。提高一些效率。

OnChildEnterViewPort委托

用于监听进入到ViewPort之内的那些Item。(此处的ViewPort不包括CullOffset+Spacing的偏移)

OnChildExitViewPort委托

用于监听离开ViewPort视区的那些Item。(此处的ViewPort不包括CullOffset+Spacing的偏移)

其他一些比较重要的接口:

public void SetItemSeed(GameObject itemSeed)
用于设定一个自定义的ItemSeed

public void InitChildItems()
初始化ChildItems。 若采用预先在Grid下放置一个ItemSeed的方式 则会在Awake的时候自动调起这个方法

public void CheckWrapContent()
执行一次ChildItem的裁剪,超出ViewPort则裁剪,空缺则补上

public void SetWrapLimit(int minIndex, int maxIndex)
设置滑动的minIndex与maxIndex,依据数据列表来设定,将会决定 Scroll能够滑动到的最小端与最大端

public void ResetTopRealIndex(int realIndex)
Grid的代码里会维护一个topRealIndex,即 Grid下 第一个孩子的realIndex,其他所有item的realIndex都是依据它计算的(Grid下孩子的层级顺序直接就是items在场景中的顺序,除开itemSeed)修改这个topRealIndex等同于修改了当前已经创建的所有item对应的数据区段。

public void LocationToRealIndexOnTop(int realIndex)
把一个realIndex对应的item定位到scroll顶部。 若有定位到非顶部的需求,(如聊天 这里需要定位到底部) 可以根据当前childItems的数量 计算出 当达到预期定位位置时 对应顶部的realIndex , 则将预期的顶部realIndex定位到顶部即可。不过位置上可能会有些偏差。因为定位之后的顶部是完全贴合的。 当时像 定位底部的话 可以再另外调一次SetContentToCurTrail() 让scroll滑倒当前childItems的底部

public void RefreshChildData()
主动刷新一次当前的ChildItems

public void Reposition()
根据当前topRealIndex所在的位置,重新排列ChildItems

public void ResetPosition()
将Grid拉回起始坐标(运行之前摆的UI坐标),并重新排列ChildItems。
注意该接口不会让topRealIndex回到minIndex,需要的应该调用LocationToRealIndexOnTop(minIndex)

public void SetContentToCurTop()
将Grid依照当前的ChildItems移动到Scroll顶部

public void SetContentToCurTrail()
将Grid依照当前的ChildItems 移动到Scroll底部

public bool CheckScrollInTop()
检查当前Scroll是否位于顶部位置

public bool CheckScrollInTrail()
检查当前Scroll是否位于底部位置

— 于 共写了3785个字
— 文内使用到的标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*