unity 中实现tableview

  • Post author:
  • Post category:其他


烦烦烦

新建一个scrollview 如上图,有一个简单预制体Item拿来测试用

新建一个脚本TableView.cs挂载在ScrollView节点上

TableView.cs内容

    /// <summary>
    /// 子对象 一般是预制体
    /// </summary>
    public GameObject cellItem;

    /// <summary>
    /// 面板总尺寸
    /// </summary>
    private Vector2 totalViewSize;

    /// <summary>
    /// 可见区域尺寸
    /// </summary>
    private Vector2 visibleViewSize;

    /// <summary>
    /// 子项尺寸
    /// </summary>
    private Vector2 cellSize;

    /// <summary>
    /// 子项间隔
    /// </summary>
    private float cellInterval;

    /// <summary>
    /// 可滑动的距离
    /// </summary>
    private float totalScrollDistance;

    /// <summary>
    /// 子项数量
    /// </summary>
    private int totalCellCount;


    /// <summary>
    /// 开始结束下标
    /// </summary>
    private int startIndex;
    private int endIndex;

    /// <summary>
    /// 内容有已滑动距离
    /// </summary>
    private Vector2 contentOffset;

    /// <summary>
    /// 可见子项集合
    /// </summary>
    private Dictionary<int, GameObject> cells;

    /// <summary>
    /// 可重用子项集合
    /// </summary>
    private List<GameObject> reUseCells;

    private ViewDirection currentDir;

    /// <summary>
    /// ScrollRect 组件
    /// </summary>
    private UnityEngine.UI.ScrollRect scrollRect;

    /// <summary>
    /// 遮罩的rectTransform
    /// </summary>
    private RectTransform viewportRectTransform;

    /// <summary>
    /// 内容的rectTransform
    /// </summary>
    private RectTransform contentRectTransform;

    public delegate void OnItemRender(int index, Transform trans);
    /// <summary>
    /// 外部回调用
    /// </summary>
    public OnItemRender onItemRender;
    
    public enum ViewDirection
    {
        Horizontal,
        Vertical
    }

初始话组件

private void InitComponent()
    {
        scrollRect = gameObject.GetComponent<UnityEngine.UI.ScrollRect>();
        viewportRectTransform = scrollRect.viewport;
        contentRectTransform = scrollRect.content;

        scrollRect.onValueChanged.AddListener(OnScrollValueChanged);
    }

初始化一些数据

/// <summary>
    /// 初始化数据
    /// </summary>
    private void InitVars()
    {
        cells = new Dictionary<int, GameObject>();
        reUseCells = new List<GameObject>();
        contentOffset = new Vector2(0, 0);

        totalCellCount = 8; // 总的子项数量
        cellInterval = 20; // 子项的间隔

        visibleViewSize = viewportRectTransform.rect.size;
        cellSize = cellItem.GetComponent<RectTransform>().rect.size;

        if (scrollRect.horizontal)
        {
            currentDir = ViewDirection.Horizontal;
            totalViewSize = new Vector2(cellSize.x * totalCellCount + cellInterval * (totalCellCount - 1), contentRectTransform.sizeDelta.y);

            totalScrollDistance = totalViewSize.x - visibleViewSize.x;
        } else
        {
            currentDir = ViewDirection.Vertical;
            totalViewSize = new Vector2(contentRectTransform.sizeDelta.x, cellSize.y * totalCellCount + cellInterval * (totalCellCount - 1));

            totalScrollDistance = totalViewSize.y - visibleViewSize.y;
        }    

    }

private void InitView()
    {
        int count = 0;
        if (currentDir == ViewDirection.Horizontal)
        {
            // 所有锚点设置成一样的
            contentRectTransform.anchorMin = new Vector2(0, 0.5f);
            contentRectTransform.anchorMax = contentRectTransform.anchorMin;
            contentRectTransform.pivot = contentRectTransform.anchorMin;

            if (visibleViewSize.x > totalViewSize.x)
            {
                count = Mathf.FloorToInt((totalViewSize.x + cellInterval) / (cellSize.y + cellInterval));
            } else
            {
                count = Mathf.CeilToInt((visibleViewSize.x + cellInterval) / (cellSize.y + cellInterval));
            }
        } else
        {
            contentRectTransform.anchorMin = new Vector2(0.5f, 1);
            contentRectTransform.anchorMax = contentRectTransform.anchorMin;
            contentRectTransform.pivot = contentRectTransform.anchorMin;

            if (visibleViewSize.y > totalViewSize.y)
            {
                count = Mathf.FloorToInt((totalViewSize.y + cellInterval) / (cellSize.y + cellInterval));
            }
            else
            {
                // count * cellSize + (count - 1) * cellInterval = count * (cellSize + cellInterval) - cellInterval >= visibleViewSize
                // => count >= (visibleViewSize - cellInterval) / (cellSize + cellInterval)
                count = Mathf.CeilToInt((visibleViewSize.y + cellInterval) / (cellSize.y + cellInterval));
            }
        }
        // 设置内容面板尺寸
        contentRectTransform.sizeDelta = totalViewSize;

        contentRectTransform.anchoredPosition = Vector2.zero;

       for (int i = 0; i < count; ++i)
        {
            OnCellCreateAtIndex(i);
        }
    }
public void SetOnLoadCellCallback(OnItemRender func)
    {
        onItemRender = func;
    }

    /// <summary>
    /// 直接跳到最上面或者最左边
    /// </summary>
    public void JumpToTop()
    {
        scrollRect.StopMovement();
        if (currentDir == ViewDirection.Horizontal)
        {
            contentRectTransform.anchoredPosition = new Vector2(0, contentRectTransform.anchoredPosition.y);
        } else
        {
            contentRectTransform.anchoredPosition = new Vector2(contentRectTransform.anchoredPosition.x, 0);
        }
    }
    public void JumpToBottom()
    {
        if (totalScrollDistance > 0)
        {
            scrollRect.StopMovement();
            if (currentDir == ViewDirection.Horizontal)
            {
                contentRectTransform.anchoredPosition = new Vector2(-totalScrollDistance, contentRectTransform.anchoredPosition.y);
            }
            else
            {
                contentRectTransform.anchoredPosition = new Vector2(contentRectTransform.anchoredPosition.x, totalScrollDistance);
            }
        }
    }
private void OnCellCreateAtIndex(int index)
    {
        GameObject cell = null;
        if (reUseCells.Count > 0)
        {
            // 有可以重用的
            cell = reUseCells[0];
            reUseCells.RemoveAt(0);
        } else
        {
            // 没有的话就从预制体创建
            cell = GameObject.Instantiate(cellItem);
        }
        cell.transform.SetParent(contentRectTransform);
        cell.transform.localScale = Vector3.one;

        RectTransform cellRectTrans = cell.GetComponent<RectTransform>();
        if (currentDir == ViewDirection.Horizontal)
        {
            cellRectTrans.anchorMin = new Vector2(0, 0.5f);
            cellRectTrans.anchorMax = cellRectTrans.anchorMin;
            cellRectTrans.pivot = cellRectTrans.anchorMin;

            float posX = index * cellSize.x + index * cellInterval;
            cellRectTrans.anchoredPosition = new Vector2(posX, 0);
        } else
        {
            cellRectTrans.anchorMin = new Vector2(0.5f, 1);
            cellRectTrans.anchorMax = cellRectTrans.anchorMin;
            cellRectTrans.pivot = cellRectTrans.anchorMin;

            float posY = index * cellSize.y + index * cellInterval;
            cellRectTrans.anchoredPosition = new Vector2(0, -posY);
        }
        cell.name = index.ToString();
        
        
        cell.SetActive(true);
        cells.Add(index, cell);
        // 回调
        onItemRender(index, cell.transform);
    }

    private void OnScrollValueChanged(Vector2 offset)
    {
        // offset 的x,y都是0-1之间的数 分别代表横向滑出的宽度百分比 纵向。。。
        OnCellScrolling(offset);
    }
    private void OnCellScrolling(Vector2 offset)
    {
        if ((currentDir == ViewDirection.Horizontal && totalViewSize.x <= visibleViewSize.x) || 
            (currentDir == ViewDirection.Vertical && totalViewSize.y <= visibleViewSize.y))
        {
            return;
        }

        offset.x = Mathf.Max(Mathf.Min(offset.x, 1), 0);
        offset.y = Mathf.Max(Mathf.Min(offset.y, 1), 0);

        contentOffset.x = totalScrollDistance * offset.x;
        contentOffset.y = totalScrollDistance * (1 - offset.y);

        CalCellIndex();
    }
    private void CalCellIndex()
    {
        float startOffset = 0f;
        float endOffset = 0f;
        if (currentDir == ViewDirection.Horizontal)
        {
            startOffset = contentOffset.x; // 当前可见区域起始x坐标
            endOffset = contentOffset.x + visibleViewSize.x;

            startIndex = Mathf.CeilToInt((startOffset + cellInterval) / (cellSize.x + cellInterval)) - 1;
            startIndex = Mathf.Max(0, startIndex);

            endIndex = Mathf.CeilToInt((endOffset + cellInterval) / (cellSize.x + cellInterval)) - 1;
            endIndex = Mathf.Min(endIndex, totalCellCount - 1);
        } else
        {
            startOffset = contentOffset.y;
            endOffset = contentOffset.y + visibleViewSize.y;

            startIndex = Mathf.CeilToInt((startOffset + cellInterval) / (cellSize.y + cellInterval)) - 1;
            startIndex = Mathf.Max(0, startIndex);

            endIndex = Mathf.CeilToInt((endOffset + cellInterval) / (cellSize.y + cellInterval)) - 1;
            endIndex = Mathf.Min(endIndex, totalCellCount - 1);
        }

        UpdateCells();
    }
    private void UpdateCells()
    {
        List<int> delList = new List<int>();
        foreach(KeyValuePair<int, GameObject> pair in cells)
        {
            if (pair.Key < startIndex || pair.Key > endIndex)
            {
                delList.Add(pair.Key);
                reUseCells.Add(pair.Value);
            }
        }

        foreach(int index in delList)
        {
            cells.Remove(index);
        }

        for (int i = startIndex; i <= endIndex; ++i)
        {
            if (!cells.ContainsKey(i))
            {
                OnCellCreateAtIndex(i);
            }
        }
    }



版权声明:本文为jijun7885417原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。