UGUI ScrollRect使用


第一次在正式項目里用UGUI,遇到不少問題。其中ScrollRect是比較讓人惱火的。看了很多網上已有教程和原代碼,終於做出滿足項目需求的樣子來了。簡單分享一下。如有錯誤的地方,希望大家可以指出,一起進步!
 
制作一個滾動列表,首先就會想到ScrollRect這個組件。但與NGUI的不同,NGUI的UIScrollView把很多功能都寫好了,或者都寫到滾動列表專用的幾個腳本里。但UGUI的各組件更加分離,比如ScrollRect里面的布局,不是在做滾動列表才使用,而是對所有布局情況下都通用的。我們要做的是把幾個通用組件合理組合起來,做出某種特定功能的東西。
 
首先來看下我的功能樹目錄:
 
跟官方的例子很像吧。這里的ScrollPanel是我的一個界面根節點,比如你可以把它當成一個道具界面,ScrollRect是這個界面的主要內容,放了很多道具。整個ScrollRect分了3層,中間層ViewPoint是用來指定滾動窗口大小的,出了ViewPoint范圍的Item就看不見了。Content節點是放布局組件的,你可以用水平布局或垂直布局等。
 
ScrollRect節點上的組件如下: 
主要就是掛ScrollRect腳本,在兩處紅框分別掛Content節點和ViewPoint節點。因為我的例子里是做水平滾動的,所有在Horizontal里打勾就行了。然后MovementType指定為Elastic,就是有彈性,在Content內容大小大小ViewPoint可視區域大小時,會把Content的內容自動彈回邊界位置。
 
ViewPoint節點的組件如下:
這里要做的是指定滾動可視區域的大小,比如我指定大小為720*240,效果如下:
白色區域,就是在ViewPoint里指定的可視區域,Item出超出這個區域就會看不見了。其實白色區域不需要顯示出來,把Mask組件的Show Mask Graphic的勾取消就行了。
 
Content節點的組件如下:
 
這個組件非常關鍵,用了兩個UGUI的重要組件,一個是Horizontal Layout Group,水平布局組。用來把Content下面的子節點自動水平布局,Padding用來指定上下左右的偏移量,注意,Left和Right盡量一致,否則Content在自動調整大小時會有問題。Content Size Fitter組件用於讓Content自動根據其子節點的大小來調整自身的大小。如果沒有這個組件,在動態添加Item時,Content的大小就不會自動調整大小了,在拖動時就會發現有些位置拖不過去。因為Content的大小一定要比所有子節點的大小要大,才能正常顯示。你可以試一下把這個組件去掉,然后在運行時動態添加item,看看有什么情況發生。當然,有些人喜歡自己寫布局或自己寫代碼動態調整Content的大小。我喜歡用UGUI的組件,既然有現成的,就把它用得如魚得水。
 
注意看我這里的Pivot(軸心點),我把它的x設置為0。如果不設置為0會怎樣,看看打開界面后的效果。
這是我運行時打開界面的效果,第一個item沒有顯示在第一的位置,這是為什么。因為我們用了Content Size Fitter組件,這個組件在自動調整UI元素的大小時,是根據UI元素自身的軸心點來擴展大小的,也就是說,如果我Content的軸心點在0.5*0.5位置的話,Content的大小就會向四周擴大。如果我把Pivot設置為0*0.5,效果如下:
Content就會從軸心點(0,0.5)向外擴大,那前面的item就可以在打開界面時就看得到了。
 
有些界面我們會打開了關閉再打開,如果第一次打開這個滾動界面並且發生了拖動,不做任何處理只是簡單隱藏界面的話,下次打開Content還是停留在上一次拖動的位置。
怎么解決?只要在代碼里加一句代碼隨便設置一下Content的位置,它就會自動回到起始位置了。比如:
 
如果我們的Item是可以點擊的,而且是自己重寫了EventTrigger或封裝了點擊回調腳本,那可能會出現一個問題,在item上面拖動時沒有效果,ScrollRect的拖動事件被屏蔽了。如果你的Item直接用Button組件來做的話是沒問題的,但很多同學喜歡根據需求或更加便利地使用點擊事件就自己寫了點擊回調腳本。比如,雨松大神的那個代碼,如果用那種方法做按鈕並把按鈕放到ScrollRect上的話,在item上拖動就沒效果了。為什么?因為EventTrigger繼承了IDragHandler這些接口,如果你自己寫的事件觸發器是繼承了EventTrigger,事件觸發器掛在Item上就會把拖動事件截取,但是自己又沒有實現IDraghandler,所以在item上面拖動就沒任何反應了。所以在自己寫點擊按鈕回調腳本時,可以根據自己需求單獨繼承IPointerClickHandler,不要什么接口都繼承。
 
還有最后一個問題,策划可以需要我們自動把ScrollRect定位到某一個item上,比如在打開界面時ScrollRect就自動滾動到item9的位置,讓item9顯示在ViewPoint中間。這個要怎么做呢?
在NGUI里,UIScrollView有一個參數可以設置當前滾動的百分比,只要計算要滾動到的Item在所有Item中的位置比例就可以了。但在UGUI中沒有這個功能。我們需要手動計算應該把Content定位到哪個位置。
 
    /// <summary>
    /// 指定一個 item 讓其定位到 ScrollRect 中間
    /// </summary>
    /// <param name="target"> 需要定位到的目標 </param>
    public void CenterOnItem ( RectTransform target )
    {
        // Item is here
        var itemCenterPositionInScroll = GetWorldPointInWidget ( scrollRect . GetComponent < RectTransform >(), GetWidgetWorldPoint ( target ));
        Debug . Log ( "Item Anchor Pos In Scroll: " + itemCenterPositionInScroll );
        // But must be here
        var targetPositionInScroll = GetWorldPointInWidget ( scrollRect . GetComponent < RectTransform >(), GetWidgetWorldPoint ( viewPointTransform ));
        Debug . Log ( "Target Anchor Pos In Scroll: " + targetPositionInScroll );
        // So it has to move this distance
        var difference = targetPositionInScroll - itemCenterPositionInScroll ;
        difference . z = 0f ;
 
        var newNormalizedPosition = new Vector2 ( difference . x / ( contentTransform . rect . width - viewPointTransform . rect . width ),
            difference . y / ( contentTransform . rect . height - viewPointTransform . rect . height ));
 
        newNormalizedPosition = scrollRect . normalizedPosition - newNormalizedPosition ;
 
        newNormalizedPosition . x = Mathf . Clamp01 ( newNormalizedPosition . x );
        newNormalizedPosition . y = Mathf . Clamp01 ( newNormalizedPosition . y );
 
        DOTween . To (() => scrollRect . normalizedPosition , x => scrollRect . normalizedPosition = x , newNormalizedPosition , 3 );
     }
 
    Vector3 GetWidgetWorldPoint ( RectTransform target )
    {
        //pivot position + item size has to be included
        var pivotOffset = new Vector3 (
            ( 0.5f - target . pivot . x ) * target . rect . size . x ,
            ( 0.5f - target . pivot . y ) * target . rect . size . y ,
            0f );
        var localPosition = target . localPosition + pivotOffset ;
        return target . parent . TransformPoint ( localPosition );
    }
 
    Vector3 GetWorldPointInWidget ( RectTransform target , Vector3 worldPoint )
    {
        return target . InverseTransformPoint ( worldPoint );
    }
 
我直接上代碼了,就是調用CenterOnItem這個方法,傳入一個item的RectTransform。上面用到了DoTween插件,使得在定位時可以平滑一點,不懂這個的直接百度DoTween。還有一種方法,也是計算好Item的目標位置,直接設置Content的位置
 
    void CenterToSelected ( GameObject selected )
    {
        var target = selected . GetComponent < RectTransform >();
 
        Vector3 maskCenterPos = viewPointTransform . position + ( Vector3 ) viewPointTransform . rect . center ;
        Debug . Log ( "Mask Center Pos: " + maskCenterPos );
        Vector3 itemCenterPos = target . position ;
        Debug . Log ( "Item Center Pos: " + itemCenterPos );
        Vector3 difference = maskCenterPos - itemCenterPos ;
        difference . z = 0 ;
 
        Vector3 newPos = contentTransform . position + difference ;
 
        DOTween . To (() => contentTransform . position , x => contentTransform . position = x , newPos , 5 );
    }
 
其實都差不多,設置ScrollRect.normalizedPosition,在源代碼里也是設置Content的位置。而上一種方法,我把移動比例設置在0到1的范圍,這在定位前面幾個和最后幾個item時,就不會跳(呵呵 呵呵呵呵我都不知道怎么講)。總之自己多試一下吧,我也折騰了一下午,最后看了外國論壇和源代碼才最終搞定了,好累。准備跨年啦,祝同學們新年快樂!
轉載請注明出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM