之前用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
新聞熱點
疑難解答