转盘式循环滚动框:RoundLoopLayout
上一篇的ScrollRound主要是将ScrollRect的坐标滑动改为角度的转动,而关于滑动列表的item循环复用则由这个组件实现。
主要思路:
类比列表式的滑动列表会有一个头部和尾部,列表元素在滑动的时候检查是否超出头部和尾部,然后通过移除超出边界的元素,补到另一端,同时刷新元素内容的方式。
虽然变成了转盘式的列表,但是同样的也会有列表的头部和尾部,原本限定头部和尾部用的是坐标,而这里要改为角度,设置一个头部裁剪角度和尾部裁剪角度,滑动的时候将超出角度范围的元素移除复用。
维护一个topRealIndex,用来标记当前头部元素的真实下标(因为元素是复用的,头部的元素下标始终为0,真实下标则会随着滑动累加),所有列表元素的角度,坐标,和真实下标,以及后续的一些跳转定位到某个元素位置等都将基于这个topRealIndex计算。
主要的几个方法:
bool CheckTop()
{
int count = m_ChildItems.Count;
bool have = false;
for(int i = 0; i < count ; i++)
{
if(CheckOutOfTop(0))
{
RectTransform item = m_ChildItems[0];
m_ChildItems.RemoveAt(0);
m_ChildItems.Add(item);
topRealIndex++;
int wrapIndex = count - 1;
SetChildPosition(wrapIndex);
item.transform.SetAsLastSibling();
UpdateItemData(wrapIndex);
have = true;
}else
{
break;
}
}
return have;
}
bool CheckOutOfTop(int index)
{
RectTransform item = m_ChildItems[index];
float angle = GetItemAngleRelaScroll(item.gameObject);
if(m_IsClockwise)
{
if (m_CullTrailAngle > m_CullTopAngle)
{
return angle > m_CullTopAngle && angle <= m_CullTrailAngle;
}
else
{
return angle > m_CullTopAngle || angle <= m_CullTrailAngle;
}
}else
{
if(m_CullTrailAngle > m_CullTopAngle)
{
return angle < m_CullTopAngle || angle >= m_CullTrailAngle;
}else
{
return angle < m_CullTopAngle && angle >= m_CullTrailAngle;
}
}
}
检查头部元素是否超出设定的头部裁剪角度范围m_CullTopAngle,超出则将头部元素移除放到尾部,并重新按照元素RealIndex设置元素坐标,然后回调UpdateItemData事件,通知业务层刷新元素内容。
注意这里的角度计算方式是按照x轴正方向为0度轴,逆时针方向计算的,也就是(0,0)=>0度,(0,1)=>90度,(-1,0)=>180度,(1,0)=>270度,这样,整个计算过程中需要限定所有的角度在(0,360)之间。
m_IsCloclwise表示圆盘中的元素排列方向是顺时针还是逆时针,排列方式不同,关于边界的判定也会不同,相当于做了一个镜像翻转。大概像这样。
关于尾部的检查跟头部类似,就不放了。把这两个方法注册到ScrollRound的滑动回调事件中,每次滑动的时候检查是否超边界,然后重置刷新元素即可。
看下获取元素角度的方法
public float GetItemAngleRelaScroll(GameObject go)
{
Vector3 pos = m_Scroll.transform.InverseTransformPoint(go.transform.position);
float angle;
if (pos.x == 0)
{
angle = pos.y > 0 ? 90 : 270;
}else if(pos.y == 0)
{
angle = pos.x > 0 ? 0 : 180;
}
else
{
float rad = Mathf.Atan2(pos.y, pos.x);
angle = Mathf.Rad2Deg * rad;
//因为Atan2得出的角度在不同象限会相同,这里根据y轴的正负情况 把角度限定在0 - 360度之间
if (angle < 0)
{
angle += 180;
}
if (pos.y < 0)
{
angle += 180;
}
}
return angle;
}
获取元素相对于ScrollRound的角度值,为了方便计算,需要将所有角度限定在0,360度之间,由于Atan2在不同象限计算值可能会相同,并且对于钝角可能得出负数角度,所以这里做了一个校正。
void SetChildPosition(int index)
{
if (index < 0 || index >= m_ChildItems.Count) return;
int realIndex = GetRealIndex(index);
float angle = m_StartAngle + (realIndex * m_CellAngle) * (m_IsClockwise ? -1 :1);
float rad = Mathf.Deg2Rad * angle;
RectTransform child = m_ChildItems[index];
float x = m_Radius * Mathf.Cos(rad);
float y = m_Radius * Mathf.Sin(rad);
child.localPosition = new Vector3(x, y, 0);
}
根据元素realIndex计算对应坐标值。
再看一个关于滑动元素定位的方法
public void LocationToRealIndexOnTop(int realIndex)
{
ResetTopRealIndex(realIndex);
Vector3 angles = transform.localEulerAngles;
float topAngle = m_OrgAngle + (topRealIndex * m_CellAngle) * (m_IsClockwise ? -1 : 1);
angles.z = -topAngle;
transform.localEulerAngles = angles;
RePosition();
RefreshChildData();
}
有时候会有将元素刷新到指定第n个元素的位置(滑过n个元素的位置),因为关于滑动元素与数据层的关系是通过这个realIndex做的映射,所以需要跳转到指定位置的时候可以直接设置当前头部topRealIndex为指定realIndex,然后刷新整个列表即可。
结论
现在理理当时的这个思路,感觉还是挺清晰的,做好边界和realIndex的维护可以了。不过最初调试的时候因为Unity本身对EulerAngles 值在正负间变换的修改,还有一些角度在不同象限之间的特殊情况,着实让我调了好久。
- 上一篇: 转盘式循环滚动框:ScrollRound
- 下一篇: 关于TextMeshPro
您好,看了您的博客感觉很受用,想认识您一下,请问您现在还在网易吗?是否最近有意向换个工作?