节省内存的常用滑动列表还有一种形式,上下滑动的GridView。这种格式的滑动列表可用于移动设备的背包,仓库,商店UI等数据可能海量从而导致产生特别多但又看不见的UI的情况。
于是基于 UGUI进阶知识[八]循环利用滑动列表的循环ListView工程做了个更改。
主要工作在格子的初始化排布,整体区域的确定, 进入重新展示的循环之后,水平位置和对应数据的确定。
在listview总体逻辑思路确定了之后,根据思路在原代码基础上对应做这种改动还是比较容易的。
初始化排布
可见总项数的确定
将之前的水平计算变为水平和垂直计算,垂直计数+1是为了上下滑动的时候,格子位置突变的时候,还没有给用户划到,否则格子闪现会比较很违和。当滑动列表是水平滑动,则考虑水平计数加一。
/// <summary>
/// 获得总共需要的项数 包括多余的一项
/// </summary>
/// <param name="itemHeight"></param>
/// <param name="verticalOffset"></param>
/// <returns></returns>
private int GetTotalShowItemNum(float itemHeight, float itemWidth,
float verticalOffset, float horizontalOffset)
{
var height = GetComponent<RectTransform>().rect.height;
var width = GetComponent<RectTransform>().rect.width;
//这里多加的1是在显示区域外部 用来准备显示数据的一项
verticalItemCount = Mathf.CeilToInt(height / (itemHeight + verticalOffset)) + 1;
horizontalItemCount = Mathf.CeilToInt(width / (itemWidth + horizontalOffset)) ;
int totalCount = horizontalItemCount * verticalItemCount;
return totalCount;
}
总大小确定
总数据数除以单行显示数据数等于数据行数,数据行数*单个item高度等于总高度,另外计入间隙。
/// <summary>
/// 设置总内容区域的大小为所有数据加载之后的大小
/// 只是超出可显示部分是没有LoopGridListItem的
/// </summary>
private void SetContentSize()
{
int dataRowCount = loopGridListData.GetDataCount() / horizontalItemCount;
var y = dataRowCount * _itemHeight +
(dataRowCount - 1) * itemHorizonInterval;
scrollRectContent.sizeDelta = new Vector2(scrollRectContent.sizeDelta.x, y);
}
单个item的计算
是比起上面稍微烧脑一点的地方,按照习惯默认从上到下,从左到右id增大。计算的id用来确定位置和读取对应数据。
可见最大id和最小id的计算
private void UpdateIdRange(out int toppestLeftId, out int lowestRightId)
{
//当scrollRectContent对齐ScrollRect的时候 scrollRectContent.anchoredPosition.y是0
//当scrollRectContent往上超出ScrollRect的时候 scrollRectContent.anchoredPosition.y大于0
//toppestVisibleId表示 scrollRectContent滑动到当前位置 时候
//可见的最顶部的item的id 部分可见也是可见 不是超出的item的id
//FloorToInt是考虑到了顶部只显示部分item的情况
//此时toppestVisibleId应该是显示部分的item的时候,最左边的id
toppestLeftId = Mathf.FloorToInt(scrollRectContent.anchoredPosition.y /
(itemHeight + itemVerticalInterval));
//这里的计算表示的是最左边的id
toppestLeftId *= horizontalItemNum;
//lowestVisibleId表示可见的最底部的item的id
//越往下 item的id越大
lowestRightId = toppestLeftId + totalItemNum - 1;
}
重新计算id
如图,顶部的三个是超出范围用户已经不可见的,下面是对这三个超出重新分配的id,根据id再去拿数据和设置位置。方法在OverflowRemedy中。
根据id重新设置位置
根据id对水平item个数求余求对应的列数,以及对水平item个数做除法求对应的行数。
然后根据UI的排布计算位置
private void SetPos()
{
int horizonIndex = selfId % horizontalItemNum;
int verticalIndex = Mathf.FloorToInt(selfId / horizontalItemNum) ;
//每个LoopGridListItem所挂物体即预制体的Pivot是在自身的左上角 ,即滑动列表Content的原点在Content的左上角
//每个LoopGridListItem从上到下的selfId是递增的
//每个LoopGridListItem从上到下的anchoredPosition的y值是递减的
//每个LoopGridListItem从左到右的anchoredPosition的x值是递增的
//所以采用了这个计算方法
Rect.anchoredPosition = new Vector2(horizonIndex * (itemWidth + itemHorizontalInterval),
- verticalIndex * (itemHeight + itemVerticalInterval));
}
对应数据的确定
在id计算好之后,对应的取数据逻辑不用改。
问题
-
在锚点设置为某种情况的时候,recttransform是没有任何属性和方法以及他们的组合计算能够获取实例化出来已经在canvas下的UI的宽高,这时的获取的值的规律和SizeDelta是一致的。 考虑的解决方法是重新临时设置锚点,在获取后再设置回去;或者在下一帧的时候再获取值,这明显是来自unity的bug了。补充在了 UGUI进阶知识[四]关于UI定位和适配。这里出问题的UI是实时实例化出来的,并非运行前摆放好。
-
item的anchor和pivot应该调节成如下图所示,首先anchor应该聚集于一点,否则实例化item 的时候其宽高会因为父物体宽高不同而变得与预设体的宽高不一致,第一点已经验证了在这种情况下实例化UI的时候,不论anchor是否聚集还是散开,没有任何办法能拿到宽高,所以只有在预制体里面人工记录宽高。其他原因参考UGUI进阶知识[四]关于UI定位和适配
-
在整个items的顶端或者底端,会有没有数据的部分,也就是说,已经滑动到了数据的底部部分没有数据了,这时再往上拖拽,还是会有循环的item出来。目前没有对这部分做处理,最简单的做法是限制ScrollRect在这部分不进行拉出。
工程地址
Unity UGUI循环GridView