NGUI:关于ScrollView的ResetPosition不能用于UIWrapContent
发表于2019-01-18
之前用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<UIPanel>(); 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; }
关于UIWrapContent,我还加了一些其他的工具方法,像是计算一个realIndex的Item对应的相对坐标,或是判定当前SV是否在底部等。放到了github上,以后用的时候可以随时下载。
链接:https://github.com/mutou1994/NGUI_UIWrapContent