NGUI:关于ScrollView的ResetPosition不能用于UIWrapContent

发表于2019-01-18
评论0 6.5k浏览
之前用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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引