378 lines
9.5 KiB
C#
378 lines
9.5 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.UI;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 通用滑动列表组件
|
|||
|
|
/// 支持动态添加、删除和更新列表项,带有滚动功能
|
|||
|
|
/// </summary>
|
|||
|
|
public class ScrollableListView : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
[Header("滚动设置")]
|
|||
|
|
public ScrollRect ScrollRect; // 滚动组件
|
|||
|
|
public RectTransform Content; // 内容容器
|
|||
|
|
public GameObject ItemPrefab; // 列表项预制件
|
|||
|
|
|
|||
|
|
[Header("布局设置")]
|
|||
|
|
public float ItemHeight = 80f; // 列表项高度
|
|||
|
|
public float ItemSpacing = 5f; // 列表项间距
|
|||
|
|
public bool AutoResize = true; // 自动调整内容大小
|
|||
|
|
|
|||
|
|
[Header("样式设置")]
|
|||
|
|
public Color AlternateRowColor = new Color(0.9f, 0.9f, 0.9f, 0.3f); // 交替行颜色
|
|||
|
|
public bool UseAlternateRowColors = true; // 是否使用交替行颜色
|
|||
|
|
|
|||
|
|
private List<ScrollableListItem> _items = new List<ScrollableListItem>();
|
|||
|
|
private VerticalLayoutGroup _layoutGroup;
|
|||
|
|
|
|||
|
|
void Awake()
|
|||
|
|
{
|
|||
|
|
InitializeComponents();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitializeComponents()
|
|||
|
|
{
|
|||
|
|
// 如果没有设置ScrollRect,尝试获取
|
|||
|
|
if (ScrollRect == null)
|
|||
|
|
{
|
|||
|
|
ScrollRect = GetComponent<ScrollRect>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有设置Content,尝试从ScrollRect获取
|
|||
|
|
if (Content == null && ScrollRect != null)
|
|||
|
|
{
|
|||
|
|
Content = ScrollRect.content;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保Content有VerticalLayoutGroup
|
|||
|
|
if (Content != null)
|
|||
|
|
{
|
|||
|
|
_layoutGroup = Content.GetComponent<VerticalLayoutGroup>();
|
|||
|
|
if (_layoutGroup == null)
|
|||
|
|
{
|
|||
|
|
_layoutGroup = Content.gameObject.AddComponent<VerticalLayoutGroup>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 配置VerticalLayoutGroup
|
|||
|
|
_layoutGroup.childControlHeight = false;
|
|||
|
|
_layoutGroup.childControlWidth = true;
|
|||
|
|
_layoutGroup.childForceExpandHeight = false;
|
|||
|
|
_layoutGroup.childForceExpandWidth = false;
|
|||
|
|
_layoutGroup.spacing = ItemSpacing;
|
|||
|
|
_layoutGroup.padding = new RectOffset(5, 5, 5, 5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保ItemPrefab不活跃
|
|||
|
|
if (ItemPrefab != null)
|
|||
|
|
{
|
|||
|
|
ItemPrefab.SetActive(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 添加列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public ScrollableListItem AddItem(object data = null)
|
|||
|
|
{
|
|||
|
|
if (ItemPrefab == null || Content == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError("ItemPrefab or Content is null!");
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建新项
|
|||
|
|
GameObject itemGO = Instantiate(ItemPrefab, Content);
|
|||
|
|
itemGO.SetActive(true);
|
|||
|
|
|
|||
|
|
// 设置高度
|
|||
|
|
RectTransform itemRect = itemGO.GetComponent<RectTransform>();
|
|||
|
|
if (itemRect != null)
|
|||
|
|
{
|
|||
|
|
itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, ItemHeight);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取或添加ScrollableListItem组件
|
|||
|
|
ScrollableListItem item = itemGO.GetComponent<ScrollableListItem>();
|
|||
|
|
if (item == null)
|
|||
|
|
{
|
|||
|
|
item = itemGO.AddComponent<ScrollableListItem>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置数据和索引
|
|||
|
|
item.Initialize(data, _items.Count, this);
|
|||
|
|
_items.Add(item);
|
|||
|
|
|
|||
|
|
// 设置交替行颜色
|
|||
|
|
if (UseAlternateRowColors && _items.Count % 2 == 0)
|
|||
|
|
{
|
|||
|
|
var bgImage = itemGO.GetComponent<Image>();
|
|||
|
|
if (bgImage == null)
|
|||
|
|
{
|
|||
|
|
bgImage = itemGO.AddComponent<Image>();
|
|||
|
|
}
|
|||
|
|
bgImage.color = AlternateRowColor;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新内容大小
|
|||
|
|
if (AutoResize)
|
|||
|
|
{
|
|||
|
|
UpdateContentSize();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return item;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 删除指定列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public void RemoveItem(ScrollableListItem item)
|
|||
|
|
{
|
|||
|
|
if (_items.Remove(item))
|
|||
|
|
{
|
|||
|
|
if (item.gameObject != null)
|
|||
|
|
{
|
|||
|
|
Destroy(item.gameObject);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新剩余项的索引
|
|||
|
|
UpdateItemIndices();
|
|||
|
|
|
|||
|
|
if (AutoResize)
|
|||
|
|
{
|
|||
|
|
UpdateContentSize();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 删除指定索引的列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public void RemoveItemAt(int index)
|
|||
|
|
{
|
|||
|
|
if (index >= 0 && index < _items.Count)
|
|||
|
|
{
|
|||
|
|
RemoveItem(_items[index]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 清空所有列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public void ClearItems()
|
|||
|
|
{
|
|||
|
|
foreach (var item in _items)
|
|||
|
|
{
|
|||
|
|
if (item != null && item.gameObject != null)
|
|||
|
|
{
|
|||
|
|
Destroy(item.gameObject);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_items.Clear();
|
|||
|
|
|
|||
|
|
if (AutoResize)
|
|||
|
|
{
|
|||
|
|
UpdateContentSize();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取列表项数量
|
|||
|
|
/// </summary>
|
|||
|
|
public int GetItemCount()
|
|||
|
|
{
|
|||
|
|
return _items.Count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取指定索引的列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public ScrollableListItem GetItem(int index)
|
|||
|
|
{
|
|||
|
|
if (index >= 0 && index < _items.Count)
|
|||
|
|
{
|
|||
|
|
return _items[index];
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取所有列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public List<ScrollableListItem> GetAllItems()
|
|||
|
|
{
|
|||
|
|
return new List<ScrollableListItem>(_items);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 滚动到指定列表项
|
|||
|
|
/// </summary>
|
|||
|
|
public void ScrollToItem(ScrollableListItem item)
|
|||
|
|
{
|
|||
|
|
if (ScrollRect == null || Content == null || item == null) return;
|
|||
|
|
|
|||
|
|
int index = _items.IndexOf(item);
|
|||
|
|
if (index >= 0)
|
|||
|
|
{
|
|||
|
|
ScrollToIndex(index);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 滚动到指定索引
|
|||
|
|
/// </summary>
|
|||
|
|
public void ScrollToIndex(int index)
|
|||
|
|
{
|
|||
|
|
if (ScrollRect == null || Content == null || index < 0 || index >= _items.Count) return;
|
|||
|
|
|
|||
|
|
float itemY = index * (ItemHeight + ItemSpacing);
|
|||
|
|
float contentHeight = Content.rect.height;
|
|||
|
|
float viewportHeight = ScrollRect.viewport.rect.height;
|
|||
|
|
|
|||
|
|
if (contentHeight > viewportHeight)
|
|||
|
|
{
|
|||
|
|
float normalizedPosition = 1f - (itemY / (contentHeight - viewportHeight));
|
|||
|
|
normalizedPosition = Mathf.Clamp01(normalizedPosition);
|
|||
|
|
ScrollRect.verticalNormalizedPosition = normalizedPosition;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 滚动到顶部
|
|||
|
|
/// </summary>
|
|||
|
|
public void ScrollToTop()
|
|||
|
|
{
|
|||
|
|
if (ScrollRect != null)
|
|||
|
|
{
|
|||
|
|
ScrollRect.verticalNormalizedPosition = 1f;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 滚动到底部
|
|||
|
|
/// </summary>
|
|||
|
|
public void ScrollToBottom()
|
|||
|
|
{
|
|||
|
|
if (ScrollRect != null)
|
|||
|
|
{
|
|||
|
|
ScrollRect.verticalNormalizedPosition = 0f;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 更新内容大小
|
|||
|
|
/// </summary>
|
|||
|
|
private void UpdateContentSize()
|
|||
|
|
{
|
|||
|
|
if (Content == null) return;
|
|||
|
|
|
|||
|
|
float totalHeight = _items.Count * ItemHeight + (_items.Count - 1) * ItemSpacing + _layoutGroup.padding.top + _layoutGroup.padding.bottom;
|
|||
|
|
Content.sizeDelta = new Vector2(Content.sizeDelta.x, totalHeight);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 更新所有项的索引
|
|||
|
|
/// </summary>
|
|||
|
|
private void UpdateItemIndices()
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < _items.Count; i++)
|
|||
|
|
{
|
|||
|
|
if (_items[i] != null)
|
|||
|
|
{
|
|||
|
|
_items[i].SetIndex(i);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 刷新列表显示
|
|||
|
|
/// </summary>
|
|||
|
|
public void RefreshList()
|
|||
|
|
{
|
|||
|
|
// 移除已被销毁的项
|
|||
|
|
_items.RemoveAll(item => item == null || item.gameObject == null);
|
|||
|
|
|
|||
|
|
// 更新索引
|
|||
|
|
UpdateItemIndices();
|
|||
|
|
|
|||
|
|
// 更新内容大小
|
|||
|
|
if (AutoResize)
|
|||
|
|
{
|
|||
|
|
UpdateContentSize();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 滚动列表项基类
|
|||
|
|
/// </summary>
|
|||
|
|
public class ScrollableListItem : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
public object Data { get; private set; }
|
|||
|
|
public int Index { get; private set; }
|
|||
|
|
public ScrollableListView ListView { get; private set; }
|
|||
|
|
|
|||
|
|
public event Action<ScrollableListItem> OnItemClicked;
|
|||
|
|
public event Action<ScrollableListItem> OnItemSelected;
|
|||
|
|
|
|||
|
|
private Button _button;
|
|||
|
|
private Toggle _toggle;
|
|||
|
|
|
|||
|
|
void Awake()
|
|||
|
|
{
|
|||
|
|
// 获取按钮组件
|
|||
|
|
_button = GetComponent<Button>();
|
|||
|
|
if (_button != null)
|
|||
|
|
{
|
|||
|
|
_button.onClick.AddListener(OnButtonClicked);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取Toggle组件
|
|||
|
|
_toggle = GetComponent<Toggle>();
|
|||
|
|
if (_toggle != null)
|
|||
|
|
{
|
|||
|
|
_toggle.onValueChanged.AddListener(OnToggleChanged);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Initialize(object data, int index, ScrollableListView listView)
|
|||
|
|
{
|
|||
|
|
Data = data;
|
|||
|
|
Index = index;
|
|||
|
|
ListView = listView;
|
|||
|
|
|
|||
|
|
// 子类可以重写此方法来更新UI
|
|||
|
|
UpdateUI();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void SetIndex(int index)
|
|||
|
|
{
|
|||
|
|
Index = index;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected virtual void UpdateUI()
|
|||
|
|
{
|
|||
|
|
// 子类重写此方法来更新UI显示
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnButtonClicked()
|
|||
|
|
{
|
|||
|
|
OnItemClicked?.Invoke(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnToggleChanged(bool isOn)
|
|||
|
|
{
|
|||
|
|
if (isOn)
|
|||
|
|
{
|
|||
|
|
OnItemSelected?.Invoke(this);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 删除此项
|
|||
|
|
/// </summary>
|
|||
|
|
public void RemoveFromList()
|
|||
|
|
{
|
|||
|
|
ListView?.RemoveItem(this);
|
|||
|
|
}
|
|||
|
|
}
|