判断Item在Scrollview列表的上下左右

📅 2026/8/13 13:54:08
判断Item在Scrollview列表的上下左右
判断Item在Scrollview列表的上下左右直接挂就行using System; using System.Collections.Generic; using UnityEngine; /// summary /// 在滚动列表中查找可激活Item相对视口的位置上方 / 下方 / 视口内 / 无 /// 并计算跳转按钮应显示在左侧还是右侧。 /// /// 使用方式 /// 1. 挂载到任意 GameObject 上或通过 AddComponent 动态挂载。 /// 2. 设置 ScrollContainer滚动视口的 RectTransform和 ShowXPos。 /// 3. 每帧或滚动时调用 Locate()根据返回值刷新按钮显示。 /// /summary public class ScrollActiveItemLocator : MonoBehaviour { // --------------------------------------------------------------- // 枚举 // --------------------------------------------------------------- public enum State { None, MiddleActive, // 可激活 Item 在视口内 UpActive, // 可激活 Item 在视口上方需要向上滚动 DownActive, // 可激活 Item 在视口下方需要向下滚动 } // --------------------------------------------------------------- // 数据结构调用方填充后传入 // --------------------------------------------------------------- /// summary当前视口中已渲染出来的 Item 信息/summary public struct VisibleItem { /// summary该 Item 在全量数据列表中的索引/summary public int Index; /// summary用于判断是否在视口内的 RectTransform/summary public RectTransform Rect; /// summary该 Item 当前是否处于可激活状态/summary public bool IsCanActive; } /// summary全量数据列表中每条数据的状态无需 RectTransform/summary public struct DataItem { public bool IsCanActive; } // --------------------------------------------------------------- // 公开配置 // --------------------------------------------------------------- [Tooltip(滚动视口的 RectTransform用于判断 Item 是否在可见区域内)] public RectTransform ScrollContainer; [Tooltip(跳转按钮的 X 轴偏移量正值右侧负值左侧)] public float ShowXPos 150f; // --------------------------------------------------------------- // 核心方法 // --------------------------------------------------------------- /// summary /// 根据当前可见 Item 列表和全量数据找出最近的可激活 Item 的位置。 /// /summary /// param namevisibleItemsLoop 列表当前渲染出的所有 Item含视口外的缓存行/param /// param nameallDataItems完整数据列表用于在视口外扫描/param /// param nameactiveIndex找到的可激活 Item 在 allDataItems 中的索引/param /// param namebuttonPosX跳转按钮应使用的 X 坐标根据列数奇偶判断左右/param /// returns当前定位状态/returns public State Locate( IListVisibleItem visibleItems, IListDataItem allDataItems, out int activeIndex, out float buttonPosX) { int minVisibleIndex int.MaxValue; int maxVisibleIndex int.MinValue; // 先在视口内找 foreach (var item in visibleItems) { if (!IsInScrollRect(item.Rect)) continue; if (item.IsCanActive) { activeIndex item.Index; buttonPosX GetButtonPosX(item.Index); return State.MiddleActive; } if (item.Index minVisibleIndex) minVisibleIndex item.Index; if (item.Index maxVisibleIndex) maxVisibleIndex item.Index; } // 视口上方扫描从最小可见索引往前 if (minVisibleIndex 0) { for (int i minVisibleIndex; i 0; i--) { if (allDataItems[i].IsCanActive) { activeIndex i; buttonPosX GetButtonPosX(i); return State.UpActive; } } } // 视口下方扫描从最大可见索引往后 if (maxVisibleIndex 0) { for (int i maxVisibleIndex; i allDataItems.Count; i) { if (allDataItems[i].IsCanActive) { activeIndex i; buttonPosX GetButtonPosX(i); return State.DownActive; } } } activeIndex 0; buttonPosX 0f; return State.None; } // --------------------------------------------------------------- // 辅助方法 // --------------------------------------------------------------- /// summary /// 判断 itemRect 是否至少有一个角点在 ScrollContainer 的世界空间矩形内。 /// /summary public bool IsInScrollRect(RectTransform itemRect) { if (ScrollContainer null || itemRect null) return false; Vector3[] containerCorners new Vector3[4]; Vector3[] itemCorners new Vector3[4]; ScrollContainer.GetWorldCorners(containerCorners); itemRect.GetWorldCorners(itemCorners); // GetWorldCorners 返回顺序左下(0)、左上(1)、右上(2)、右下(3) Rect containerRect new Rect( containerCorners[0], containerCorners[2] - containerCorners[0]); for (int i 0; i 4; i) { if (containerRect.Contains(itemCorners[i])) return true; } return false; } /// summary /// 根据 Item 索引的奇偶性决定按钮显示在左侧还是右侧。 /// 偶数索引 → 左列 → 负 X奇数索引 → 右列 → 正 X。 /// /summary private float GetButtonPosX(int index) { return index % 2 0 ? -ShowXPos : ShowXPos; } }