天天看點

NGUI 無限滑動,支援定位顯示指定資料

為什麼需要無限滑動:

1:背包,玩家如果擷取了1000個道具,如果沒有無限滑動的話就隻能做成按頁來顯示或者生成1000個格子來顯示

2:排行榜,和背包同理

無限滑動應用場景在與需要顯示非常多的item的時候

百度了很多ngui的無限滑動都不支援定位顯示資料,這個需求一般用在新手引導或者需要直接跳到某條資料顯示在目前界面顯示的場景

無限滑動思路:

NGUI 無限滑動,支援定位顯示指定資料

計算出四個邊角的局部坐标 再根據是左右滑動還是上下滑動來确定邊角離中心的距離,當滑動的時候,item離中心點的距離大于的邊角離中心點的距離,item就設定坐标在反方向,例子:如果是向左滑動,當item離中心點的距離大于邊角離中心點的距離,該item就設定坐标在最右邊。

下面放代碼:

定義變量

/// <summary>
    /// item , item下标 , 資料下标 (都是從0開始)
    /// </summary>
    public Action<Transform,int,int> renderItem; //渲染item
    public Action renderAllItemCallBack; // 渲染完所有item的回調

    public UIGrid grid;// 排序元件
    public GameObject itemPrefab; //item預制體
    public UIScrollView scrollView; //滑動元件

    private int dataCount; //資料個數
    private List<Transform> childers; //item清單
    private UIPanel panel; //滑動的panel元件

    private float width { get { return grid.cellWidth; } } // item寬度
    private float height { get { return grid.cellHeight; } } //item高度

    //If the arrangement is horizontal, this denotes the number of columns.
    // If the arrangement is vertical, this stands for the number of rows.
    private int maxPerLine { get { return grid.maxPerLine; } }
    private int rows = ; //行數 (預制體所占的總行數)
    private int columns = ; //列數 (預制體所占的總列數)
    private float extents = ; //所有預制體所占長度或者是高度的一半 用在循環的時候計算item的坐标
    private int itemCount = ; //預制體item的數量
           

初始化資料:

/// <summary>
    /// 初始化資料
    /// </summary>
    public void initData ()
    {
        int itemCount = childers.Count;
        if (scrollView.movement == UIScrollView.Movement.Horizontal)
        {
            rows = maxPerLine; //行數
            columns = itemCount / maxPerLine;
            extents = columns * width * f;
        }
        else
        {
            columns = maxPerLine; //列數
            rows = itemCount / maxPerLine;
            extents = rows * height * f; 
        }
        updateAllItem();
    }
      /// <summary>
    ///建立item
    /// </summary>
    private void creatItem()
    {
        if (itemCount == )
        {
            itemCount = grid.transform.childCount;
        }
        else
        {
            int childCount = grid.transform.childCount;
            int count = itemCount - childCount;
            for (int i = ; i < count; i++)
            {
                GameObject go = GameObject.Instantiate(itemPrefab) as GameObject;
                go.transform.SetParent(grid.transform, false);
                childers.Add(go.transform);
            }
        }
        grid.pivot = UIWidget.Pivot.TopLeft;  //強制錨點為左上
        grid.Reposition();
    }
           

無限滑動重點代碼:

/// <summary>
    /// 滑動回調
    /// </summary>
    /// <param name="panel"></param>
    private void onMove(UIPanel panel)
    {
        Vector3[] corners = panel.worldCorners;

        for (int i = ; i < ; ++i) //轉換成局部坐标
        {
            Vector3 v = corners[i];
            v = grid.transform.InverseTransformPoint(v);
            corners[i] = v;
        }

        Vector3[] localCorners = corners;
        Vector3 center = (localCorners[] + localCorners[]) * f; // 邊角的中心點坐标
        bool allWithinRange = true;
        //0:bottom - left    1:top - left     2:top - right     3:bottom - right
        if (scrollView.movement == UIScrollView.Movement.Horizontal)
        {
            float min = localCorners[].x - width;
            float max = localCorners[].x + width;
            int count = childers.Count;
            for (int i = ; i < count; i++)
            {
                Transform item = childers[i];
                Vector3 localPos = item.localPosition;
                float distance = localPos.x - center.x;  //用來判斷是在左邊還是右邊  計算item坐标離中心的距離

                Vector2 pos = item.localPosition;
                int realIndex = ;
                if (distance < -extents || distance > extents)
                {
                    if (distance < -extents) // 向左拉的時候(向右移動)
                    {
                        pos.x += extents * ;
                        realIndex = getRealIndexByPos(pos);
                    }
                    else if (distance > extents) //向右拉 (向左移動)
                    {
                        pos.x -= extents * ;
                        realIndex = getRealIndexByPos(pos);
                    }
                    if (realIndex >= && realIndex < dataCount)
                    {
                        item.localPosition = pos;
                        updateItem(item,i, realIndex);
                    }
                    else allWithinRange = false;
                }

            }
        }
        else if (scrollView.movement == UIScrollView.Movement.Vertical)
        {
            float min = localCorners[].x - width;
            float max = localCorners[].x + width;
            int count = childers.Count;
            for (int i = ; i < count; i++)
            {
                Transform item = childers[i];
                Vector3 localPos = item.localPosition;
                float distance = localPos.y - center.y;  //用來判斷是在上邊還是下邊  計算item坐标離中心的距離

                Vector2 pos = item.localPosition;
                int realIndex = ;
                if (distance < -extents || distance > extents)
                {
                    if (distance < -extents) // 向左拉的時候(向右移動)
                    {
                        pos.y += extents * ;
                        realIndex = getRealIndexByPos(pos);
                    }
                    else if (distance > extents) //向右拉 (向左移動)
                    {
                        pos.y -= extents * ;
                        realIndex = getRealIndexByPos(pos);
                    }
                    if (realIndex >=  && realIndex < dataCount)
                    {
                        item.localPosition = pos;
                        updateItem(item, i, realIndex);
                    }
                    else allWithinRange = false;
                }
            }
        }
        onRenderCompelte();
        scrollView.restrictWithinPanel = !allWithinRange;
        scrollView.InvalidateBounds();
    }
           

根據item的局部坐标來擷取對應的資料下标:

private int getRealIndexByPos(Vector2 pos)
    {
        int realIndex = ;  // 0 - dataCount-1
        int currentRows = (int)(-pos.y / height) ; //行數
        int currentColumns = (int)(pos.x / width); //列數

        if (scrollView.movement == UIScrollView.Movement.Horizontal)
        {
            realIndex = currentRows + maxPerLine * currentColumns;
        }
        else
        {
            realIndex = currentRows * maxPerLine + currentColumns;
        }
        return realIndex;
    }
           

顯示item的時候調用注冊的方法以及顯示完成的回調:

private void updateItem(Transform item,int itemIndex, int index)
    {
        if (renderItem != null)
        {
            renderItem(item,itemIndex,index);
        }
    }
    private void onRenderCompelte()
    {
        if (renderAllItemCallBack != null)
        {
            renderAllItemCallBack();
        }
    }
           

根據需要顯示的資料下标來定位的重新整理item

public void renderItemByIndex(int dataIndex)
    {
        if (dataIndex < )
        {
            dataIndex = ;
        }
        else if(dataIndex >= dataCount)
        {
            dataIndex = dataCount - ;
        }
        int currentColumns = ; //列數 從0開始
        int currentRows = ; //行數 從0開始
        int maxRows = ; //最大行數
        int maxColumns = ; //最大列數
        float posY = ; 
        float posX = ;
        int showCountItem = rows * columns;
        if (scrollView.movement == UIScrollView.Movement.Horizontal)
        {
            currentColumns = dataIndex / maxPerLine;
            maxColumns = (dataCount - ) / maxPerLine;
            currentRows = dataIndex % maxPerLine;

            int startColumns = ; //開始的列數
            int offsetColums = ; //偏移的列數
            int maxShowColumns = (int)(panel.width / width);

            if (currentColumns + maxShowColumns /  < maxShowColumns)//如果要顯示的列數小于螢幕顯示的最大列數 就直接從0列開始顯示
            {
                startColumns = ;
                offsetColums = startColumns;
            }
            else if (currentColumns + maxShowColumns/ >= maxColumns) //如果目前顯示的列數加上螢幕顯示的最大列數大于最大的列數就從 最大列數-螢幕顯示列數 開始顯示
            {
                startColumns = maxColumns - columns + ;
                offsetColums = maxColumns - maxShowColumns + ;
            }
            else //正常顯示 這裡可以改成- N 
            {
                //(很多時候坑逼策劃會讓你居中顯示, 下面代碼可以改成 startColumns = currentColumns - (columns / 2) +1
                startColumns = currentColumns - ;
                offsetColums = startColumns;
            }
            int line = -;

            //計算裁剪區域offset以及panel的坐标
            Vector2 offset = new Vector2((offsetColums) * width, );
            float x = panel.transform.localPosition.x + panel.clipOffset.x;
            panel.clipOffset = offset;
            panel.transform.localPosition = new Vector3(x - offset.x, , );

            for (int i = ; i < showCountItem; i++)
            {
                Transform item = childers[i];

                if (i % rows == ) //下一列了
                {
                    line += ;
                }
                int column = (startColumns + line);
                //if (column > maxColumns)
                //{
                //    break;//超标了 直接退出
                //}

                posX = column * width;
                posY = i % rows * -height;
                Vector3 newPos = new Vector3(posX,posY);
                item.localPosition = newPos;
                int realIndex = getRealIndexByPos(newPos);
                updateItem(item, i, realIndex);
            }
        }
        else
        {
            currentRows = dataIndex / maxPerLine;
            maxRows = (dataCount - ) / maxPerLine;
            currentColumns = dataIndex % maxPerLine;

            int startRows = ; //開始的行數
            int offsetRows = ; // 偏移的行數
            int maxShowRows = (int)(panel.height / height); //裁剪區域能顯示的最大行數
            if (currentRows + maxShowRows /  < maxShowRows)//
            {
                startRows = ;
                offsetRows = startRows;
            }
            else if (currentRows + maxShowRows/ >= maxRows)
            {
                startRows = maxRows - rows + ;
                offsetRows = maxRows - maxShowRows +;
            }
            else
            {
                startRows = currentRows - ;
                offsetRows = startRows;
            }
            int line = -;

            //計算裁剪以及panel的坐标
            Vector2 offset = new Vector2(, offsetRows * -height);
            float y = panel.transform.localPosition.y + panel.clipOffset.y;
            panel.clipOffset = offset;
            panel.transform.localPosition = new Vector3(panel.transform.localPosition.x, y - offset.y, );


            for (int i = ; i < showCountItem; i++)
            {
                Transform item = childers[i];
                if (i % columns == ) //下一行了
                {
                    line += ;
                }
                int row = (startRows + line);
                if (row > maxRows)
                {
                    break;//超标了 直接退出
                }
                posY = row * -height;
                posX = i % columns * width;

                Vector3 newPos = new Vector3(posX, posY);
                item.localPosition = newPos;
                int realIndex = getRealIndexByPos(newPos);
                updateItem(item, i, realIndex);
            }
        }

        onRenderCompelte();
    }
           

寫在最後:

本篇教程是結合ngui 的uigrid排序元件,以及uipanel,UIScrollView滑動元件來做的,uigrid的pivot 必須為 UIWidget.Pivot.TopLeft

NGUI 無限滑動,支援定位顯示指定資料

如果是其他的錨點需要根據這篇教程的思路自己去實作

注意點:grid.arrangement 與UIScrollView.movement必須相反, uigrid的pivot 必須為 UIWidget.Pivot.TopLeft