TableView版ScrollRect

本文详细介绍了在Unity中实现UI TableView的方法,通过自定义ScrollRect类实现动态加载和复用单元格,支持垂直和水平布局,适用于游戏和应用界面开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.EventSystems;

class TableViewGroup
{
   public Dictionary<int, RectTransform> visibilityCellQueue = new Dictionary<int, RectTransform>();
   public List<RectTransform> invisibilityCellQueue = new List<RectTransform>();

   ~TableViewGroup()
   {
      visibilityCellQueue.Clear();
      visibilityCellQueue = null;
      invisibilityCellQueue.Clear();
      invisibilityCellQueue = null;
   }

    public void QueueInvisible()
    {
        foreach(int i in visibilityCellQueue.Keys) {
            RectTransform trans = visibilityCellQueue[i];
            invisibilityCellQueue.Add(trans);
            trans.gameObject.SetActive(false);
        }
        visibilityCellQueue.Clear();
    }
}

public class TableView : ScrollRect
{
   public const string DEFAULT_IDENTIFIER = "default.identifier";

   public delegate int NumberOfCells(TableView tbView);
   public delegate float SizeOfIndex(TableView tbView, int index);
   public delegate RectTransform TransformOfIndex(TableView tbView, int index);
   public delegate string IdentifierOfIndex(TableView tbView, int index);
   /// <summary>
   /// 返回行数
   /// </summary>
   public NumberOfCells delegateNumberOfCells = null;
   /// <summary>
   /// 说明:返回第i行cell的高度(垂直),或宽度(水平)
   /// 参数
   /// t: TableView
   /// i: int 表示第几行
   /// </summary>
   public SizeOfIndex delegateSizeOfIndex = null;
   /// <summary>
   /// 说明:返回第i行cell的RectTransform
   /// 参数
   /// t: TableView
   /// i: int 表示第几行
   /// </summary>
   public TransformOfIndex delegateTransformOfIndex = null;
   /// <summary>
   /// 说明:返回第i行cell的标识符
   /// 参数
   /// t: TableView
   /// i: int 表示第几行
   /// </summary>
   public IdentifierOfIndex delegateIdentifierOfIndex = null;

   int extraBuffer = 2;

   private Rect[] rects;
   private Dictionary<string, TableViewGroup> groupDict = new Dictionary<string, TableViewGroup>();
   
   private int numberOfCells = 0;
   private int minVisibleIndex = 0;
   private int maxVisibleIndex = 0;

   protected override void OnDestroy()
   {
      delegateNumberOfCells = null;
      delegateSizeOfIndex = null;
      delegateTransformOfIndex = null;
      delegateIdentifierOfIndex = null;

      base.OnDestroy();
   }

   protected override void Start()
   {
        base.Start();
      onValueChanged.AddListener(OnMove);
   }

   bool IsIdentifierExist(string identifier)
   {
      return groupDict.ContainsKey(identifier + "_v");
   }

   void OnMove(Vector2 value)
   {
        if (numberOfCells == 0) return;

      UpdateMinMaxVisibleIndex();
   }

   /// <summary>
   /// 滑动循环使用ITEM
   /// </summary>
   private void UpdateMinMaxVisibleIndex()
   {
      Vector2 minMaxIndexes = GetMinMaxVisibleIndex();
      
      int minIndex = (int)minMaxIndexes.x;
      int maxIndex = (int)minMaxIndexes.y;

      if(minVisibleIndex == minIndex || maxVisibleIndex == maxIndex) return;
      
      if (minVisibleIndex - minIndex < 0)
      {
         for (int i = minVisibleIndex; i < minIndex; i++)
         {
            RemoveCellAtIndex(i);
         }
         for (int i = maxVisibleIndex; i <= maxIndex; i++)
         {
            AddCellAtIndex(i);
         }
      }
      else
      {
         for (int i = minVisibleIndex; i >= minIndex; i--)
         {
            AddCellAtIndex(i);
         }
         for (int i = maxVisibleIndex; i > maxIndex; i--)
         {
            RemoveCellAtIndex(i);
         }
      }

      minVisibleIndex = minIndex;
      maxVisibleIndex = maxIndex;
   }

   private void AddCellAtIndex(int index)
   {
      if (index >= numberOfCells || index < 0) return;
      string identifier = GetIdentifierForIndex(index);

      if (groupDict[identifier].visibilityCellQueue.ContainsKey(index) == false)
      {
         Rect rect = rects[index];
         RectTransform trans = delegateTransformOfIndex(this, index);
         //trans.anchorMin = new Vector2(0.5f, 0.5f);
         //trans.anchorMax = new Vector2(0.5f, 0.5f);
         trans.SetParent(content);
         //trans.anchoredPosition = new Vector2(rect.x, rect.y);

         trans.localRotation = new Quaternion(0, 0, 0, 0);
         trans.localScale = Vector3.one;

         if (vertical)
         {
            trans.anchoredPosition = new Vector2(rect.x, rect.y - rect.height / 2f);
            trans.anchorMin = new Vector2(0f, 1f);
            trans.anchorMax = new Vector2(1f, 1f);
            trans.pivot = new Vector2(0.5f, 0.5f);
            Vector2 v = trans.offsetMin;
            v.x = 0;
            trans.offsetMin = v;
            v = trans.offsetMax;
            v.x = 0;
            trans.offsetMax = v;
         }
         else
         {
            trans.anchoredPosition = new Vector2(rect.x + rect.width / 2f, rect.y);
            trans.anchorMin = new Vector2(0f, 0.5f);
            trans.anchorMax = new Vector2(0f, 0.5f);
            trans.pivot = new Vector2(0.5f, 0.5f);
         }

         groupDict[identifier].visibilityCellQueue.Add(index, trans);
      }
   }

   private void RemoveCellAtIndex(int index)
   {
      string identifier = GetIdentifierForIndex(index);
      if (groupDict[identifier].visibilityCellQueue.ContainsKey(index) == true)
      {
         RectTransform trans = groupDict[identifier].visibilityCellQueue[index];
         groupDict[identifier].invisibilityCellQueue.Add(trans);
         groupDict[identifier].visibilityCellQueue.Remove(index);
         trans.gameObject.SetActive(false);
      }
   }

   private int IndexAtOffset(float offset)
   {
      int minIndex = 0;
      int maxIndex = numberOfCells - 1;
      maxIndex = maxIndex < 0 ? 0 : maxIndex;
      int index = (maxIndex + minIndex) / 2;

      if (vertical)
      {
         while (minIndex < maxIndex)
         {
            float indexY = rects[index].y;
            float nextY = rects[index + 1].y;

            if (indexY >= offset && nextY < offset) break;
            else if (nextY >= offset) minIndex = index + 1;
            else maxIndex = index;

            index = (maxIndex + minIndex) / 2;
         }
      }
      else
      {
         while (minIndex < maxIndex)
         {
            float indexX = -rects[index].x;
            float nextX = -rects[index + 1].x;

            if (indexX >= offset && nextX < offset) break;
            else if (nextX >= offset) minIndex = index + 1;
            else maxIndex = index;

            index = (maxIndex + minIndex) / 2;
         }
      }
      return index;
   }

   private Vector2 GetMinMaxVisibleIndex()
   {
      RectTransform trans = transform as RectTransform;
      Vector2 offset = content.anchoredPosition;

      float viewWidth = trans.rect.width;
      float viewHeight = trans.rect.height;

      int minIndex = vertical ? IndexAtOffset(-offset.y) : IndexAtOffset(offset.x);
      int maxIndex = vertical ? IndexAtOffset(-offset.y - viewHeight) : IndexAtOffset(offset.x - viewWidth);

      int boundMinIndex = 0;
      int boundMaxIndex = numberOfCells - 1;// Mathf.Max(0, numberOfCells - 1);
      minIndex = minIndex - extraBuffer / 2;
      minIndex = minIndex < boundMinIndex ? boundMinIndex : minIndex;
      maxIndex = maxIndex + extraBuffer / 2;
      maxIndex = maxIndex > boundMaxIndex ? boundMaxIndex : maxIndex;

      return new Vector2(minIndex, maxIndex);
   }

    /// <summary>
    /// 重新加载数据+
    /// </summary>
    /// <param name="startIndex">重新加载后跳转到第startIndex行,如:10,跳转到第10行。另:-1,跳转到第0行,并执行反弹动画;-2,在当前行刷新</param>
    public void ReloadData(int startIndex = 0, bool isLerp = false, bool cleanCell = false)
    {
       
        RectTransform trans = transform as RectTransform;
        
        float viewWidth = trans.rect.width;
        float viewHeight = trans.rect.height;
        
        if (cleanCell) 
        {
            groupDict.Clear();

            while (content.childCount > 0)
            {
                //PoolManager.instance.recycle(content.GetChild(0).gameObject);
            }
        }
        else
        {
            foreach(string identifier in groupDict.Keys)
            {
                groupDict[identifier].QueueInvisible();
            }
        }

        numberOfCells = delegateNumberOfCells(this);

        rects = new Rect[numberOfCells];

        float offsetX = 0f;
        float offsetY = 0f;
        float width = 0;
        float x = 0;
        float height = 0;
        float y = 0;
        Vector2 contentPos = content.anchoredPosition;
        Vector2 contentSize = content.sizeDelta;

        System.Action<int> createIdentifierGroup = null;
        
        if (delegateIdentifierOfIndex == null)
        {
           if(groupDict.ContainsKey(DEFAULT_IDENTIFIER) == false) groupDict[DEFAULT_IDENTIFIER] = new TableViewGroup();
        }
        else
        {
           createIdentifierGroup = (i) =>
           {
              string identifier = delegateIdentifierOfIndex(this, i);
              if (groupDict.ContainsKey(identifier) == false) groupDict[identifier] = new TableViewGroup();
           };
        }

        if (vertical)
        {
           for (int i = 0; i < numberOfCells; i++)
           {
              height = delegateSizeOfIndex(this, i);
              
              rects[i] = new Rect(0, -y, viewWidth, height);
              
              y += height;
              
              if (i < startIndex) offsetY += height;
              
              if (createIdentifierGroup != null) createIdentifierGroup(i);
           }

           contentSize.y = y;
        }
        else
        {
           for (int i = 0; i < numberOfCells; i++)
           {
              width = delegateSizeOfIndex(this, i);
              
              rects[i] = new Rect(x, 0, width, viewHeight);
              
              x += width;
              
              if (i < startIndex) offsetX += width;
              
              if (createIdentifierGroup != null) createIdentifierGroup(i);
           }

           contentSize.x = x;
        }
        
        content.sizeDelta = contentSize;

        if (startIndex >= 0)
        {
           if (vertical) contentPos.y = offsetY;
           else contentPos.x = -offsetX;
           
           if (isLerp)
           {
              ExecuteContentLerp(contentPos);
           }
           else
           {
              content.anchoredPosition = contentPos;
           }
        }
        else if (startIndex == -1)
        {
           normalizedPosition = Vector2.zero;
        }

        Vector2 indexes = GetMinMaxVisibleIndex();
        
        minVisibleIndex = (int)indexes.x;
        maxVisibleIndex = (int)indexes.y;

        if (numberOfCells > 0)
        {
           for (int i = minVisibleIndex; i <= maxVisibleIndex; i++)
           {
              AddCellAtIndex(i);
           }
        }
   }
   
    public void ToBottom()
    {
        RectTransform trans = transform as RectTransform;
        
        ReloadData(-2);

        if (this.content.sizeDelta.y > trans.rect.height)
        {
            content.anchoredPosition = new Vector2(content.anchoredPosition.x, this.content.sizeDelta.y + trans.rect.height);
        }
    }
   
   string GetIdentifierForIndex(int index)
   {
      return delegateIdentifierOfIndex == null ? DEFAULT_IDENTIFIER : delegateIdentifierOfIndex(this, index);
   }

   public RectTransform DequeueReusabelCell(int index)
   {
      string identifier = GetIdentifierForIndex(index);
      List<RectTransform> invisibilityCellQueue = groupDict[identifier].invisibilityCellQueue;
      int count = invisibilityCellQueue.Count;
      if (count > 0)
      {
         RectTransform trans = invisibilityCellQueue[count - 1];
         invisibilityCellQueue.RemoveAt(count - 1);
         trans.gameObject.SetActive(true);
         return trans;
      }

      return null;
   }

   public bool enableDragControl = false;

   public override void OnDrag(PointerEventData eventData)
   {
      if (!enableDragControl || (eventData.pointerCurrentRaycast.gameObject != null && isChild(eventData.pointerCurrentRaycast.gameObject.transform)))
         base.OnDrag(eventData);
   }

   bool isChild(Transform t)
   {
      Transform[] children = this.transform.GetComponentsInChildren<Transform>();
      for (int i = 0; i < children.Length; i++)
      {
         if (t.gameObject == children[i].gameObject)
            return true;
      }

      return false;
   }
   
   private IEnumerator ContentLerpIEnum;
   /// <summary>
   /// 滑动效果
   /// </summary>
   /// <param name="pos"></param>
   private void ExecuteContentLerp(Vector2 pos)
   {
      if (gameObject.activeInHierarchy)
      {
         if (ContentLerpIEnum != null) StopCoroutine(ContentLerpIEnum);
         ContentLerpIEnum = ContentLerp(pos);
         StartCoroutine(ContentLerpIEnum);
      }
   }
   
   private IEnumerator ContentLerp(Vector2 pos)
   {
      while (Vector2.Distance(content.anchoredPosition,pos) > 5)
      {
         content.anchoredPosition = Vector2.Lerp(content.anchoredPosition, pos, 0.7f);
         yield return 1f;
      }

      content.anchoredPosition = pos;
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值