当前位置: 首页 > news >正文

Unity UGUI 无限循环列表组件

Unity UGUI 无限循环列表组件

概述

这是一个高性能的Unity UGUI无限循环列表组件,支持任意数量的数据项,只使用少量UI对象实现无限滚动效果。

特性

  • 无限循环滚动:支持数据的无限循环显示
  • 高性能优化:只创建5个UI对象处理任意数量数据
  • 智能动画系统:中间元素平滑移动,首尾交换瞬间切换
  • 多种跳转方式:支持直接跳转、逐步跳转、平滑跳转
  • 拖拽交互:支持手势拖拽切换
  • 自适应时间:根据跳转距离自动调整动画时长
  • Mask遮挡:只显示指定数量的项目

核心原理

UI对象管理

  • 创建5个UI对象,只显示中间3个
  • 通过首尾交换实现无限循环效果
  • 使用Mask组件遮挡多余的UI元素

动画策略

  • 中间可见元素:3个可见UI元素有平滑移动动画
  • 首尾交换元素:瞬间出现在新位置,无移动动画
  • 智能跳转:根据距离选择最佳动画方式

文件结构

InfiniteScroll/
├── InfiniteLoopList.cs     # 主控制器
└── LoopItem.cs            # 列表项组件

安装和设置

1. 创建UI结构

Canvas
└── InfiniteLoopList (空GameObject)└── Container (添加Image和Mask组件)└── [动态生成的Item们]

2. Container设置

  • 添加 Image 组件(可设为透明)
  • 添加 Mask 组件
  • 设置RectTransform大小来控制可见区域

3. 创建Item预制体

  • 创建UI元素作为Item基础
  • 添加 Image(背景)、Text(文本)、Button(可选)
  • 添加 LoopItem 脚本

4. 配置主脚本

  • 将Container拖到 container 字段
  • 将Item预制体拖到 itemPrefab 字段
  • 设置相关参数

API文档

InfiniteLoopList 主要方法

基本移动
// 移动到下一个数据项
public void MoveToNext()// 移动到上一个数据项  
public void MoveToPrevious()
跳转功能
// 直接跳转(无动画)
public void JumpToIndex(int index)// 跳转并选择是否使用动画
public void JumpToIndex(int index, bool withAnimation)// 平滑跳转(自动计算时长)
public void SmoothJumpToIndex(int targetIndex, float duration = -1f)
获取状态
// 获取当前中心项的数据索引
public int GetCurrentCenterIndex()// 获取当前中心项的数据
public string GetCurrentCenterData()

LoopItem 主要方法

// 设置数据
public void SetData(string data, int index)// 设置可见性
public void SetVisible(bool visible)// 获取数据
public string GetData()
public int GetDataIndex()

配置参数

InfiniteLoopList 参数

参数类型默认值说明
itemPrefabGameObjectnull列表项预制体
containerTransformnull容器对象
visibleCountint3可见项目数量
totalUICountint5UI对象池大小
itemWidthfloat200f每项宽度
spacingfloat20f项目间距
moveSpeedfloat500f移动速度
moveCurveAnimationCurveEaseInOut动画曲线

使用示例

基本使用

// 获取组件
InfiniteLoopList list = GetComponent<InfiniteLoopList>();// 移动操作
list.MoveToNext();        // 下一个
list.MoveToPrevious();    // 上一个// 跳转操作
list.JumpToIndex(5);              // 直接跳转到索引5
list.JumpToIndex(10, true);       // 带动画跳转到索引10
list.SmoothJumpToIndex(15, 2f);   // 2秒平滑跳转到索引15

自定义数据

// 在InfiniteLoopList.cs的InitializeData方法中修改
void InitializeData()
{dataList.Clear();// 添加自定义数据for (int i = 0; i < yourDataCount; i++){dataList.Add(yourData[i]);}
}

跳转策略

智能跳转算法

// 距离判断
if (距离 ≤ 5)
{使用逐步移动动画;  // 每步完整动画
}
else
{使用平滑跳转动画;  // 整体缓动动画
}

时间计算

  • 逐步跳转:每步固定时间 + 0.05秒间隔
  • 平滑跳转distance * 0.08f,限制在0.3-1.5秒之间
  • 最短路径:自动计算环形结构中的最短距离

性能优化

对象池管理

  • 只创建5个UI对象处理任意数量数据
  • 通过数据索引映射实现无限数据支持
  • UI对象重用,避免频繁创建销毁

动画优化

  • 首尾交换无动画,减少不必要的视觉干扰
  • 中间元素动画流畅,提供良好的用户体验
  • 智能时间计算,避免动画时间过长

内存优化

  • 数据与UI分离,支持大量数据
  • 按需更新UI内容
  • 最小化GC分配

测试功能

OnGUI测试面板

组件提供了完整的测试界面,包括:

  • 基本移动测试:上一个/下一个按钮
  • 近距离跳转:测试逐步移动动画
  • 远距离跳转:测试平滑移动动画
  • 直接跳转:测试无动画跳转
  • 自动测试:自动测试各种距离的跳转效果

测试方法

// 自动测试不同距离的跳转
StartCoroutine(TestVariousDistances());

扩展建议

自定义列表项

  1. 继承 LoopItem
  2. 重写 SetData 方法
  3. 添加自定义UI元素和逻辑

添加更多动画效果

  1. 修改 moveCurve 参数
  2. 在动画协程中添加缩放、旋转等效果
  3. 支持不同方向的滚动(垂直滚动)

数据绑定

  1. 实现数据源接口
  2. 支持动态添加/删除数据
  3. 添加数据变化通知

注意事项

  1. Mask组件:确保Container有Mask组件用于遮挡
  2. 预制体设置:Item预制体必须有Text组件
  3. 性能考虑:数据量很大时考虑异步加载
  4. 内存管理:及时清理不需要的数据引用
  5. 动画冲突:避免在动画进行时调用跳转方法

常见问题

Q: 为什么显示位置不正确?

A: 检查Container的RectTransform设置,确保锚点和位置正确。

Q: 动画不流畅怎么办?

A: 调整 moveSpeed 参数或修改 moveCurve 动画曲线。

Q: 如何支持垂直滚动?

A: 修改位置计算逻辑,将X坐标改为Y坐标。

Q: 数据更新后如何刷新?

A: 调用 UpdateAllItems() 方法刷新显示。


这个组件为Unity UGUI提供了高性能的无限循环列表解决方案,适用于各种需要循环显示大量数据的场景。

完整代码

InfiniteLoopList.cs

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System;public class InfiniteLoopList : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{[Header("配置")]public GameObject itemPrefab;           // 列表项预制体public Transform container;             // 容器(有Mask组件)public int visibleCount = 3;            // 可见数量public int totalUICount = 5;            // 总UI数量public float itemWidth = 200f;          // 每项宽度public float spacing = 20f;             // 间距[Header("动画设置")]public float moveSpeed = 500f;          // 移动速度public AnimationCurve moveCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);// 数据private List<string> dataList = new List<string>();private List<LoopItem> uiItems = new List<LoopItem>();// 位置管理private List<float> positions = new List<float>();private int centerIndex = 0;           // 中心显示的数据索引// 拖拽相关private Vector2 dragStartPos;private float dragStartTime;private bool isDragging = false;private bool isAnimating = false;// 计算相关private float itemDistance;            // 项目间距离private RectTransform containerRect;// OnGUI相关private bool showDetailPanel = false;void Start(){InitializeData();SetupList();}/// <summary>/// 初始化数据/// </summary>void InitializeData(){// 创建测试数据for (int i = 0; i < 20; i++){dataList.Add($"Item {i}");}}/// <summary>/// 设置列表/// </summary>void SetupList(){containerRect = container.GetComponent<RectTransform>();itemDistance = itemWidth + spacing;// 计算各个位置CalculatePositions();// 创建UI项CreateUIItems();// 初始化显示UpdateAllItems();}/// <summary>/// 计算所有位置/// </summary>void CalculatePositions(){positions.Clear();// 计算中心位置float centerPos = 0f;// 计算所有位置(以中心为基准)for (int i = 0; i < totalUICount; i++){int offset = i - totalUICount / 2;float pos = centerPos + offset * itemDistance;positions.Add(pos);}}/// <summary>/// 创建UI项/// </summary>void CreateUIItems(){for (int i = 0; i < totalUICount; i++){GameObject itemObj = Instantiate(itemPrefab, container);LoopItem item = itemObj.GetComponent<LoopItem>();if (item == null){item = itemObj.AddComponent<LoopItem>();}// 设置初始位置RectTransform itemRect = itemObj.GetComponent<RectTransform>();itemRect.anchoredPosition = new Vector2(positions[i], 0);itemRect.sizeDelta = new Vector2(itemWidth, itemRect.sizeDelta.y);uiItems.Add(item);}}/// <summary>/// 更新所有项目/// </summary>void UpdateAllItems(){int centerUIIndex = totalUICount / 2;for (int i = 0; i < uiItems.Count; i++){// 计算这个UI项应该显示的数据索引int dataOffset = i - centerUIIndex;int dataIndex = (centerIndex + dataOffset) % dataList.Count;if (dataIndex < 0) dataIndex += dataList.Count;// 更新数据uiItems[i].SetData(dataList[dataIndex], dataIndex);// 检查是否在可见范围内bool isVisible = Mathf.Abs(i - centerUIIndex) <= visibleCount / 2;uiItems[i].SetVisible(isVisible);}}/// <summary>/// 移动到下一个(向右移动,显示下一个数据)/// </summary>public void MoveToNext(){if (isAnimating) return;centerIndex = (centerIndex + 1) % dataList.Count;StartCoroutine(AnimateMoveToNext());}/// <summary>/// 移动到上一个(向左移动,显示上一个数据)/// </summary>public void MoveToPrevious(){if (isAnimating) return;centerIndex = (centerIndex - 1 + dataList.Count) % dataList.Count;StartCoroutine(AnimateMoveToPrevious());}/// <summary>/// 向右移动动画(显示下一个数据)/// UI向左移动,最左边UI瞬间跳到最右边/// </summary>System.Collections.IEnumerator AnimateMoveToNext(){isAnimating = true;// 最左边的UI项(将要移动到最右边)LoopItem leftmostItem = uiItems[0];RectTransform leftmostRect = leftmostItem.GetComponent<RectTransform>();// 瞬间将最左边的UI移动到最右边的隐藏位置float hiddenRightPos = positions[positions.Count - 1] + itemDistance;leftmostRect.anchoredPosition = new Vector2(hiddenRightPos, 0);// 重新排列UI项列表(最左边移到最右边)uiItems.RemoveAt(0);uiItems.Add(leftmostItem);// 准备动画数据List<Vector2> startPositions = new List<Vector2>();List<Vector2> targetPositions = new List<Vector2>();for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();startPositions.Add(itemRect.anchoredPosition);targetPositions.Add(new Vector2(positions[i], 0));}// 执行动画float duration = itemDistance / moveSpeed;float elapsed = 0f;while (elapsed < duration){float t = elapsed / duration;float curveT = moveCurve.Evaluate(t);for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();Vector2 pos = Vector2.Lerp(startPositions[i], targetPositions[i], curveT);itemRect.anchoredPosition = pos;}elapsed += Time.deltaTime;yield return null;}// 确保最终位置正确for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();itemRect.anchoredPosition = targetPositions[i];}// 更新数据显示UpdateAllItems();isAnimating = false;}/// <summary>/// 向左移动动画(显示上一个数据)/// UI向右移动,最右边UI瞬间跳到最左边/// </summary>System.Collections.IEnumerator AnimateMoveToPrevious(){isAnimating = true;// 最右边的UI项(将要移动到最左边)LoopItem rightmostItem = uiItems[uiItems.Count - 1];RectTransform rightmostRect = rightmostItem.GetComponent<RectTransform>();// 瞬间将最右边的UI移动到最左边的隐藏位置float hiddenLeftPos = positions[0] - itemDistance;rightmostRect.anchoredPosition = new Vector2(hiddenLeftPos, 0);// 重新排列UI项列表(最右边移到最左边)uiItems.RemoveAt(uiItems.Count - 1);uiItems.Insert(0, rightmostItem);// 准备动画数据List<Vector2> startPositions = new List<Vector2>();List<Vector2> targetPositions = new List<Vector2>();for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();startPositions.Add(itemRect.anchoredPosition);targetPositions.Add(new Vector2(positions[i], 0));}// 执行动画float duration = itemDistance / moveSpeed;float elapsed = 0f;while (elapsed < duration){float t = elapsed / duration;float curveT = moveCurve.Evaluate(t);for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();Vector2 pos = Vector2.Lerp(startPositions[i], targetPositions[i], curveT);itemRect.anchoredPosition = pos;}elapsed += Time.deltaTime;yield return null;}// 确保最终位置正确for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();itemRect.anchoredPosition = targetPositions[i];}// 更新数据显示UpdateAllItems();isAnimating = false;}/// <summary>/// 跳转到指定索引(无动画)/// </summary>public void JumpToIndex(int index){JumpToIndex(index, false);}/// <summary>/// 跳转到指定索引/// </summary>/// <param name="index">目标索引</param>/// <param name="withAnimation">是否使用动画</param>public void JumpToIndex(int index, bool withAnimation){if (index < 0 || index >= dataList.Count || isAnimating) return;if (!withAnimation){// 直接跳转,无动画centerIndex = index;UpdateAllItems();}else{// 带动画的跳转StartCoroutine(AnimatedJumpToIndex(index));}}/// <summary>/// 带动画的跳转到指定索引/// </summary>System.Collections.IEnumerator AnimatedJumpToIndex(int targetIndex){if (isAnimating || targetIndex == centerIndex) yield break;isAnimating = true;int startIndex = centerIndex;int distance = CalculateShortestDistance(startIndex, targetIndex);// 根据距离决定跳转方式int maxStepsForAnimation = 5; // 超过5步就用平滑跳转if (Mathf.Abs(distance) <= maxStepsForAnimation){// 距离较短,使用逐步移动yield return StartCoroutine(StepByStepJump(distance));}else{// 距离较长,使用平滑跳转float duration = Mathf.Clamp(Mathf.Abs(distance) * 0.1f, 0.5f, 2f); // 限制在0.5-2秒之间yield return StartCoroutine(SmoothJumpCoroutine(targetIndex, duration));}isAnimating = false;}/// <summary>/// 计算最短距离(考虑环形结构)/// </summary>int CalculateShortestDistance(int from, int to){int forward = (to - from + dataList.Count) % dataList.Count;int backward = (from - to + dataList.Count) % dataList.Count;if (forward <= backward){return forward;}else{return -backward;}}/// <summary>/// 逐步跳转(用于短距离)/// </summary>System.Collections.IEnumerator StepByStepJump(int distance){bool moveForward = distance > 0;int steps = Mathf.Abs(distance);for (int i = 0; i < steps; i++){if (moveForward){centerIndex = (centerIndex + 1) % dataList.Count;yield return StartCoroutine(AnimateMoveToNext());}else{centerIndex = (centerIndex - 1 + dataList.Count) % dataList.Count;yield return StartCoroutine(AnimateMoveToPrevious());}// 步骤间的短暂延迟if (i < steps - 1) // 最后一步不需要延迟{yield return new WaitForSeconds(0.05f);}}}/// <summary>/// 平滑跳转到指定索引(优化版)/// </summary>/// <param name="targetIndex">目标索引</param>/// <param name="duration">动画持续时间</param>public void SmoothJumpToIndex(int targetIndex, float duration = -1f){if (targetIndex < 0 || targetIndex >= dataList.Count || isAnimating) return;// 如果没有指定时间,根据距离自动计算if (duration < 0){int distance = Mathf.Abs(CalculateShortestDistance(centerIndex, targetIndex));duration = Mathf.Clamp(distance * 0.08f, 0.3f, 1.5f); // 自动计算合适的时间}StartCoroutine(SmoothJumpCoroutine(targetIndex, duration));}/// <summary>/// 优化的平滑跳转协程/// </summary>System.Collections.IEnumerator SmoothJumpCoroutine(int targetIndex, float duration){if (isAnimating || targetIndex == centerIndex) yield break;isAnimating = true;int startIndex = centerIndex;int totalDistance = CalculateShortestDistance(startIndex, targetIndex);bool moveForward = totalDistance > 0;int steps = Mathf.Abs(totalDistance);float elapsed = 0f;int lastProcessedStep = 0;while (elapsed < duration && lastProcessedStep < steps){float t = elapsed / duration;float smoothT = moveCurve.Evaluate(t);// 计算当前应该完成的步数int currentStep = Mathf.RoundToInt(smoothT * steps);// 如果需要移动到下一步if (currentStep > lastProcessedStep){int stepsToMove = currentStep - lastProcessedStep;for (int i = 0; i < stepsToMove; i++){if (moveForward){centerIndex = (centerIndex + 1) % dataList.Count;}else{centerIndex = (centerIndex - 1 + dataList.Count) % dataList.Count;}}UpdateAllItems();lastProcessedStep = currentStep;}elapsed += Time.deltaTime;yield return null;}// 确保最终到达目标位置centerIndex = targetIndex;UpdateAllItems();isAnimating = false;}#region 拖拽处理public void OnBeginDrag(PointerEventData eventData){if (isAnimating) return;isDragging = true;dragStartPos = eventData.position;dragStartTime = Time.time;}public void OnDrag(PointerEventData eventData){if (!isDragging || isAnimating) return;Vector2 dragDelta = eventData.position - dragStartPos;float dragDistance = dragDelta.x;// 移动所有项目for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();Vector2 newPos = new Vector2(positions[i] + dragDistance, 0);itemRect.anchoredPosition = newPos;}}public void OnEndDrag(PointerEventData eventData){if (!isDragging || isAnimating) return;isDragging = false;Vector2 dragDelta = eventData.position - dragStartPos;float dragDistance = dragDelta.x;float dragTime = Time.time - dragStartTime;float dragVelocity = dragDistance / dragTime;// 判断滑动方向和距离bool shouldMove = Mathf.Abs(dragDistance) > itemDistance * 0.3f || Mathf.Abs(dragVelocity) > 500f;if (shouldMove){if (dragDistance > 0){// 向右拖拽,显示上一个数据MoveToPrevious();}else{// 向左拖拽,显示下一个数据MoveToNext();}}else{// 回弹到原位置StartCoroutine(AnimateToOriginalPositions());}}/// <summary>/// 回弹到原位置/// </summary>System.Collections.IEnumerator AnimateToOriginalPositions(){isAnimating = true;List<Vector2> startPositions = new List<Vector2>();List<Vector2> targetPositions = new List<Vector2>();for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();startPositions.Add(itemRect.anchoredPosition);targetPositions.Add(new Vector2(positions[i], 0));}float duration = 0.3f;float elapsed = 0f;while (elapsed < duration){float t = elapsed / duration;float curveT = moveCurve.Evaluate(t);for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();Vector2 pos = Vector2.Lerp(startPositions[i], targetPositions[i], curveT);itemRect.anchoredPosition = pos;}elapsed += Time.deltaTime;yield return null;}// 确保最终位置正确for (int i = 0; i < uiItems.Count; i++){RectTransform itemRect = uiItems[i].GetComponent<RectTransform>();itemRect.anchoredPosition = targetPositions[i];}isAnimating = false;}#endregion/// <summary>/// 获取当前中心项的数据索引/// </summary>public int GetCurrentCenterIndex(){return centerIndex;}/// <summary>/// 获取当前中心项的数据/// </summary>public string GetCurrentCenterData(){return dataList[centerIndex];}/// <summary>/// OnGUI测试界面/// </summary>void OnGUI(){if (!Application.isPlaying) return;// 主测试面板GUILayout.BeginArea(new Rect(10, 10, 300, 400));GUILayout.BeginVertical("box");// 标题和状态GUILayout.Label("无限循环列表测试", GUI.skin.box);GUILayout.Label($"当前: 索引{centerIndex} | {dataList[centerIndex]}");if (isAnimating) GUILayout.Label("⏳ 动画中...", GUI.skin.box);GUILayout.Space(5);// 基本移动GUILayout.BeginHorizontal();if (GUILayout.Button("← 上一个", GUILayout.Height(30)))MoveToPrevious();if (GUILayout.Button("下一个 →", GUILayout.Height(30)))MoveToNext();GUILayout.EndHorizontal();GUILayout.Space(10);// 跳转测试GUILayout.Label("跳转测试:");// 近距离跳转(逐步动画)GUILayout.Label("近距离跳转(逐步):");GUILayout.BeginHorizontal();if (GUILayout.Button("±1")) JumpToIndex((centerIndex + 1) % dataList.Count, true);if (GUILayout.Button("±2")) JumpToIndex((centerIndex + 2) % dataList.Count, true);if (GUILayout.Button("±3")) JumpToIndex((centerIndex + 3) % dataList.Count, true);GUILayout.EndHorizontal();// 远距离跳转(平滑动画)GUILayout.Label("远距离跳转(平滑):");GUILayout.BeginHorizontal();if (GUILayout.Button("±8")) JumpToIndex((centerIndex + 8) % dataList.Count, true);if (GUILayout.Button("±10")) JumpToIndex((centerIndex + 10) % dataList.Count, true);if (GUILayout.Button("对面")) JumpToIndex((centerIndex + 10) % dataList.Count, true);GUILayout.EndHorizontal();// 直接跳转(无动画)GUILayout.Label("直接跳转(无动画):");GUILayout.BeginHorizontal();if (GUILayout.Button("0")) JumpToIndex(0, false);if (GUILayout.Button("5")) JumpToIndex(5, false);if (GUILayout.Button("10")) JumpToIndex(10, false);if (GUILayout.Button("15")) JumpToIndex(15, false);if (GUILayout.Button("19")) JumpToIndex(19, false);GUILayout.EndHorizontal();GUILayout.Space(10);// 测试说明GUILayout.Label("说明:", GUI.skin.box);GUILayout.Label("• ≤5步: 逐步移动动画");GUILayout.Label("• >5步: 平滑跳转动画");GUILayout.Label("• 时间根据距离自动调整");GUILayout.Label("• 最长不超过2秒");GUILayout.Space(5);// 自动测试if (GUILayout.Button("自动测试各种距离", GUILayout.Height(25))){StartCoroutine(TestVariousDistances());}GUILayout.EndVertical();GUILayout.EndArea();// 详细信息面板(可选显示)if (showDetailPanel){ShowDetailPanel();}// 切换详细面板的按钮if (GUI.Button(new Rect(320, 10, 80, 25), showDetailPanel ? "隐藏详情" : "显示详情")){showDetailPanel = !showDetailPanel;}}/// <summary>/// 显示详细信息面板/// </summary>void ShowDetailPanel(){GUILayout.BeginArea(new Rect(410, 10, 300, 400));GUILayout.BeginVertical("box");GUILayout.Label("详细状态信息", GUI.skin.box);// UI状态详情GUILayout.Label("UI对象状态:");for (int i = 0; i < uiItems.Count && i < 5; i++){if (uiItems[i] != null){int dataIndex = uiItems[i].GetDataIndex();RectTransform rect = uiItems[i].GetComponent<RectTransform>();float xPos = rect.anchoredPosition.x;string visibility = "隐藏";if (i == 1) visibility = "左";else if (i == 2) visibility = "中";else if (i == 3) visibility = "右";GUILayout.Label($"UI[{i}]: 数据{dataIndex} | X:{xPos:F0} | {visibility}");}}GUILayout.Space(10);// 操作说明GUILayout.Label("操作说明:", GUI.skin.box);GUILayout.Label("• 拖拽: 左拖显示下一个,右拖显示上一个");GUILayout.Label("• 动画: 中间UI有动画,首尾交换无动画");GUILayout.Label("• 跳转: 可选择有无动画效果");GUILayout.EndVertical();GUILayout.EndArea();}/// <summary>/// 测试各种距离的跳转/// </summary>System.Collections.IEnumerator TestVariousDistances(){// 测试短距离(逐步)JumpToIndex(0, false);yield return new WaitForSeconds(0.5f);JumpToIndex(2, true); // 2步yield return new WaitForSeconds(2f);JumpToIndex(7, true); // 5步yield return new WaitForSeconds(3f);// 测试长距离(平滑)JumpToIndex(17, true); // 10步,会用平滑跳转yield return new WaitForSeconds(2f);JumpToIndex(3, true); // 跨越首尾,会选择最短路径yield return new WaitForSeconds(2f);// 回到起点JumpToIndex(0, true);}
}

LoopItem.cs

using UnityEngine;
using UnityEngine.UI;/// <summary>
/// 无限循环列表的单个项目组件
/// </summary>
public class LoopItem : MonoBehaviour
{[Header("UI组件")]public Text itemText;              // 显示文本的组件public Image backgroundImage;      // 背景图像组件public Button itemButton;          // 按钮组件(可选)// 数据相关private int dataIndex;            // 当前显示的数据索引private string itemData;          // 当前显示的数据内容private CanvasGroup canvasGroup;  // 用于控制透明度void Awake(){InitializeComponents();SetupButton();}/// <summary>/// 初始化组件引用/// </summary>void InitializeComponents(){// 自动获取组件(如果没有手动指定)if (itemText == null)itemText = GetComponentInChildren<Text>();if (backgroundImage == null)backgroundImage = GetComponent<Image>();if (itemButton == null)itemButton = GetComponent<Button>();// 获取或添加CanvasGroup用于控制透明度canvasGroup = GetComponent<CanvasGroup>();if (canvasGroup == null){canvasGroup = gameObject.AddComponent<CanvasGroup>();}}/// <summary>/// 设置按钮事件/// </summary>void SetupButton(){if (itemButton != null){itemButton.onClick.RemoveAllListeners();itemButton.onClick.AddListener(OnItemClick);}}/// <summary>/// 设置数据内容/// </summary>/// <param name="data">要显示的数据</param>/// <param name="index">数据索引</param>public void SetData(string data, int index){itemData = data;dataIndex = index;// 更新文本显示if (itemText != null){itemText.text = data;}// 设置样式SetItemStyle(index);// 更新按钮交互状态UpdateInteractable();}/// <summary>/// 根据索引设置项目样式/// </summary>/// <param name="index">数据索引</param>void SetItemStyle(int index){if (backgroundImage != null){// 根据索引设置不同颜色,便于区分Color baseColor = GetColorByIndex(index);backgroundImage.color = new Color(baseColor.r, baseColor.g, baseColor.b, 0.8f);}// 可以根据需要添加更多样式设置SetTextStyle(index);}/// <summary>/// 根据索引获取颜色/// </summary>/// <param name="index">数据索引</param>/// <returns>对应的颜色</returns>Color GetColorByIndex(int index){Color[] colors = {Color.red,      // 0Color.green,    // 1Color.blue,     // 2Color.yellow,   // 3Color.cyan,     // 4Color.magenta,  // 5new Color(1f, 0.5f, 0f),    // 橙色 6new Color(0.5f, 0f, 1f),    // 紫色 7new Color(0f, 1f, 0.5f),    // 青绿色 8new Color(1f, 0f, 0.5f)     // 粉红色 9};return colors[index % colors.Length];}/// <summary>/// 设置文本样式/// </summary>/// <param name="index">数据索引</param>void SetTextStyle(int index){if (itemText != null){// 可以根据索引设置不同的文本样式itemText.color = Color.white;itemText.fontSize = 16;// 示例:每5个一组,设置不同的字体大小if (index % 5 == 0){itemText.fontSize = 18;itemText.fontStyle = FontStyle.Bold;}else{itemText.fontSize = 16;itemText.fontStyle = FontStyle.Normal;}}}/// <summary>/// 设置可见性(用于显示/隐藏非中心项目)/// </summary>/// <param name="visible">是否可见</param>public void SetVisible(bool visible){if (canvasGroup != null){// 可见项目完全不透明,隐藏项目半透明canvasGroup.alpha = visible ? 1f : 0.3f;// 可选:完全禁用隐藏项目的交互canvasGroup.interactable = visible;canvasGroup.blocksRaycasts = visible;}else{// 备用方案:直接控制GameObject激活状态gameObject.SetActive(visible);}}/// <summary>/// 设置高亮状态(用于标识中心项目)/// </summary>/// <param name="highlighted">是否高亮</param>public void SetHighlighted(bool highlighted){if (backgroundImage != null){if (highlighted){// 高亮时边框或阴影效果backgroundImage.color = Color.white;// 可以添加缩放效果transform.localScale = Vector3.one * 1.1f;}else{// 恢复正常颜色Color normalColor = GetColorByIndex(dataIndex);backgroundImage.color = new Color(normalColor.r, normalColor.g, normalColor.b, 0.8f);// 恢复正常大小transform.localScale = Vector3.one;}}}/// <summary>/// 更新交互状态/// </summary>void UpdateInteractable(){if (itemButton != null){// 确保按钮在有数据时可交互itemButton.interactable = !string.IsNullOrEmpty(itemData);}}/// <summary>/// 项目点击事件处理/// </summary>void OnItemClick(){Debug.Log($"点击了项目: {itemData} (索引: {dataIndex})");// 尝试获取父级的无限循环列表组件InfiniteLoopList parentList = GetComponentInParent<InfiniteLoopList>();if (parentList != null){// 如果点击的不是中心项,跳转到该项if (dataIndex != parentList.GetCurrentCenterIndex()){parentList.JumpToIndex(dataIndex, true); // 带动画跳转}else{// 如果点击的是中心项,可以执行其他逻辑Debug.Log($"中心项被点击: {itemData}");OnCenterItemClick();}}// 发送自定义事件(可选)SendItemClickEvent();}/// <summary>/// 中心项目被点击时的处理/// </summary>void OnCenterItemClick(){// 可以在这里添加中心项目特有的点击逻辑// 例如:播放特殊动画、打开详情界面等// 示例:简单的缩放动画StartCoroutine(PlayClickAnimation());}/// <summary>/// 播放点击动画/// </summary>System.Collections.IEnumerator PlayClickAnimation(){Vector3 originalScale = transform.localScale;Vector3 targetScale = originalScale * 1.2f;// 放大float duration = 0.1f;float elapsed = 0f;while (elapsed < duration){float t = elapsed / duration;transform.localScale = Vector3.Lerp(originalScale, targetScale, t);elapsed += Time.deltaTime;yield return null;}// 缩小回原始大小elapsed = 0f;while (elapsed < duration){float t = elapsed / duration;transform.localScale = Vector3.Lerp(targetScale, originalScale, t);elapsed += Time.deltaTime;yield return null;}transform.localScale = originalScale;}/// <summary>/// 发送项目点击事件/// </summary>void SendItemClickEvent(){// 可以使用事件系统或其他方式通知其他组件// 例如:EventManager.TriggerEvent("ItemClicked", dataIndex);// 或者使用Unity的事件系统var eventData = new ItemClickEventData{itemData = this.itemData,dataIndex = this.dataIndex,clickedItem = this};// 向上传递事件SendMessageUpwards("OnLoopItemClicked", eventData, SendMessageOptions.DontRequireReceiver);}/// <summary>/// 获取当前显示的数据/// </summary>/// <returns>当前数据内容</returns>public string GetData(){return itemData;}/// <summary>/// 获取当前数据索引/// </summary>/// <returns>当前数据索引</returns>public int GetDataIndex(){return dataIndex;}/// <summary>/// 检查是否有有效数据/// </summary>/// <returns>是否有有效数据</returns>public bool HasValidData(){return !string.IsNullOrEmpty(itemData);}/// <summary>/// 重置项目状态/// </summary>public void ResetItem(){itemData = string.Empty;dataIndex = -1;if (itemText != null){itemText.text = string.Empty;}if (backgroundImage != null){backgroundImage.color = Color.white;}transform.localScale = Vector3.one;SetVisible(false);}/// <summary>/// 应用自定义数据(支持泛型扩展)/// </summary>/// <typeparam name="T">数据类型</typeparam>/// <param name="data">数据对象</param>/// <param name="index">索引</param>public void SetCustomData<T>(T data, int index){dataIndex = index;// 将泛型数据转换为字符串显示if (data != null){itemData = data.ToString();}else{itemData = "Null";}if (itemText != null){itemText.text = itemData;}SetItemStyle(index);UpdateInteractable();}#region Unity生命周期void OnDestroy(){// 清理事件监听if (itemButton != null){itemButton.onClick.RemoveAllListeners();}}void OnValidate(){// 在编辑器中验证组件设置if (Application.isPlaying) return;if (itemText == null)itemText = GetComponentInChildren<Text>();if (backgroundImage == null)backgroundImage = GetComponent<Image>();if (itemButton == null)itemButton = GetComponent<Button>();}#endregion
}/// <summary>
/// 项目点击事件数据
/// </summary>
[System.Serializable]
public class ItemClickEventData
{public string itemData;      // 项目数据public int dataIndex;        // 数据索引public LoopItem clickedItem; // 被点击的项目组件
}
http://www.dtcms.com/a/282510.html

相关文章:

  • 倒立摆系统控制器设计报告
  • PyCharm(入门篇)
  • OpenSearch SQL 查询完整指南
  • Spring Boot 缓存 与 Redis
  • Java-74 深入浅出 RPC Dubbo Admin可视化管理 安装使用 源码编译、Docker启动
  • 【Android】TextView的使用
  • 【Fedora 42】Linux内核升级后,鼠标滚轮失灵,libinput的锅?
  • 颠覆NLP十年范式!OpenCSG中文数据集助推CMU无分词器模型登顶SOTA
  • Jetpack Compose 中 Kotlin 协程的使用
  • 重学SpringMVC一SpringMVC概述、快速开发程序、请求与响应、Restful请求风格介绍
  • 【iOS】源码阅读(六)——方法交换
  • Flutter基础(前端教程①①-底部导航栏)
  • 中医舌诊学习软件,图文视频详解
  • Flutter Web 的发展历程:Dart、Flutter 与 WasmGC
  • 2025华为ODB卷-箱子之字形摆放100分-三语言题解
  • 文字图标设计-色彩魔方:动态变色技术实现场景自适应 大学毕业论文——仙盟创梦IDE
  • 【Unity】Mono相关理论知识学习
  • 深入核心:理解Spring Boot的三大基石:起步依赖、自动配置与内嵌容器
  • Kafka——生产者压缩算法
  • IsaacLab学习记录(一)
  • opencv 值类型 引用类型
  • Hadoop架构演进:从1.0到2.0的深度对比与优化解析
  • ARCGIS PRO DSK 颜色选择控件(ColorPickerControl)的调用
  • Lumerical Charge ------ 运行 PN 结仿真
  • 74、搜索二维矩阵
  • Python+Tkinter制作音频格式转换器
  • PDF 转 Word 支持加密的PDF文件转换 批量转换 编辑排版自由
  • lua(xlua)基础知识点记录
  • 非控制器(如 Service、工具类)中便捷地获取当前 HTTP 请求的上下文信息
  • SQL,在join中,on和where的区别