NGUI:关于ScrollView的ResetPosition 不能用于UIWrapContent
之前用ScrollView的resetPosition方法来给ScrollView复位,发现复位后的坐标不是预料中的(0,0,0),并且这个值还会变化,有时候是偏差-12,有时候又偏差-7,这真是很令我困惑啊。今天打开NGUI的源码看了下ResetPosition这块的源码。
public void ResetPosition()
{
if (NGUITools.GetActive(this))
{
// Invalidate the bounds
mCalculatedBounds = false;
Vector2 pv = NGUIMath.GetPivotOffset(contentPivot);
// First move the position back to where it would be if the scroll bars got reset to zero
SetDragAmount(pv.x, 1f - pv.y, false);
// Next move the clipping area back and update the scroll bars
SetDragAmount(pv.x, 1f - pv.y, true);
}
}
看到NGUITools.GetActive(this)这句过滤了active为false的状态,所以ResetPosition必须在activeInHierarchy为true的情况下才能生效。
然后最重要的用来计算复位坐标的主要就是 SetDragAmount(pv.x, 1f - pv.y, false);这句了。继续跟进去看看:
代码有点长,而且都是一些计算,着实有点难看啊。不过没关系,不能看得明明白白,看个大概也就可以了。
public virtual void SetDragAmount (float x, float y, bool updateScrollbars)
{
if (mPanel == null) mPanel = GetComponent();
DisableSpring(); //先取消了SpringPanel的active,防止复位后又被SpringPanel拉回来。
Bounds b = bounds; //计算了当前ScrollView的一个相对边框尺寸,包括所有子级。
if (b.min.x == b.max.x || b.min.y == b.max.y) return;//min和max是边框的两个边界坐标。
Vector4 clip = mPanel.finalClipRegion;//获取了ScrollView最后被渲染出来的中心坐标和裁剪区域尺寸ViewSize
float hx = clip.z * 0.5f;
float hy = clip.w * 0.5f;//得到裁剪区域尺寸的一半 ViewSize/2
float left = b.min.x + hx;
float right = b.max.x - hx;
float bottom = b.min.y + hy;
float top = b.max.y - hy;//各边界坐标分别向中心点偏移了半个ViewSize
if (mPanel.clipping == UIDrawCall.Clipping.SoftClip)
{
left -= mPanel.clipSoftness.x;
right += mPanel.clipSoftness.x;//当选择的裁剪方式为SoftClip时会有一个Softness值,这个就是软裁剪的意思吧。
bottom -= mPanel.clipSoftness.y;//上面计算得到的各边界再减去这个软裁剪的值
top += mPanel.clipSoftness.y;//到此得到的坐标其实就是SV的中心坐标了,只是这个中心坐标偏移了最初的位置
}
// Calculate the offset based on the scroll value
float ox = Mathf.Lerp(left, right, x);
float oy = Mathf.Lerp(top, bottom, y);//根据设置的pivot选取SV的中心应该是在左边还是右边,是在头还是尾。
// Update the position //因为SV的可以从top开始滑,也可以从bottom开始滑。
if (!updateScrollbars)
{
Vector3 pos = mTrans.localPosition;//ScrollView的当前坐标
if (canMoveHorizontally) pos.x += clip.x - ox;//写成pos.x-=ox-clip.x可能更好看点。
if (canMoveVertically) pos.y += clip.y - oy;//clip.xy是SV最后被渲染在屏幕上的中心点,而oxy则是偏移后的中心
mTrans.localPosition = pos; //两者做差正好就是偏移量了。 这步把SV的坐标拉回起始点了。
}
if (canMoveHorizontally) clip.x = ox;
if (canMoveVertically) clip.y = oy;
// Update the clipping offset
Vector4 cr = mPanel.baseClipRegion;//这个是最初设置的SV的中心点,其实这里和finalClipRegion的值是一样的。
mPanel.clipOffset = new Vector2(clip.x - cr.x, clip.y - cr.y);//偏移后的中心点跟最初的中心点做差,得到偏移量,
// Update the scrollbars, reflecting this change //赋值还给clipOffset,把SV拉回起始点。
if (updateScrollbars) UpdateScrollbars(mDragID == -10);
}
大概看明白了SV的复位过程,就知道了所谓的复位其实是把SV所包含的内容拉回到SV裁剪区域,并让渲染的裁剪边界与内容的边界恰好贴合的过程。所以复位得到坐标取决于最初内容边界与裁剪边界的一个差值。放到这里其实就是Grid的坐标值了。当Grid的坐标值刚好为内容边界与裁剪边界的差值时,复位得到的坐标就是0了。(就是把这个差值从SV转移到了Grid)
对于UIGrid,使用ResetPosition确实是可以给ScrollView复位,但是对于UIWrapContent,因为其下的Item是循环利用的,所以内容边界总是固定的,但在边界的那个Item并不一定是第一个Item(就是realIndex为0的那个),每次复位也只是把当前的内容给复位了,而真正的起始Item还在更前面。所以对于UIWrapContent不能使用这个ResetPosition,那就只能直接给SV赋一个起始坐标了。而这个起始坐标则要根据起始的内容边界和裁剪边界的差值计算。但是这里如果我们把这个差值事先赋给了Grid(WrapContent,我习惯也给命名为Grid),那么SV的复位坐标就总是0了。
贴一个给UIWrapContent赋偏移量的方法。可以放在Start或Awake里执行一遍。
public void adjustWrapPos()
{
if (mScroll == null)
CacheScrollView();
Bounds b = NGUIMath.CalculateRelativeWidgetBounds(mScroll.transform,MChildren[0],true);
Vector2 pos = transform.localPosition;
if(mHorizontal)
{
pos.x = -(mPanel.GetViewSize().x / 2f -mPanel.baseClipRegion.x - b.extents.x-mPanel.clipSoftness.x);
}else
{
pos.y = mPanel.GetViewSize().y / 2f +mPanel.baseClipRegion.y - b.extents.y-mPanel.clipSoftness.y;
}
transform.localPosition = pos;
}