From f22a4f1efa16de9306f02065016ced084da9ab24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com>
Date: Fri, 27 Mar 2026 18:38:29 +0800
Subject: [PATCH] =?UTF-8?q?RecyclerView=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1.分离逻辑视图与渲染视图
2.xxxx若干项懒得写了
---
Runtime/RecyclerView/Adapter/Adapter.cs | 407 +++++++++++-------
Runtime/RecyclerView/Adapter/GroupAdapter.cs | 56 +--
Runtime/RecyclerView/Adapter/IAdapter.cs | 2 +
Runtime/RecyclerView/Adapter/ItemRender.cs | 203 +++++++++
.../RecyclerView/Adapter/ItemRender.cs.meta | 11 +
Runtime/RecyclerView/Adapter/LoopAdapter.cs | 5 -
Runtime/RecyclerView/Adapter/MixedAdapter.cs | 5 -
Runtime/RecyclerView/UGList.cs | 140 ++----
.../ViewHolder/InteractiveViewHolder.cs | 22 +
.../ViewHolder/InteractiveViewHolder.cs.meta | 11 +
Runtime/RecyclerView/ViewHolder/ViewHolder.cs | 97 ++---
.../ViewProvider/MixedViewProvider.cs | 1 +
.../ViewProvider/SimpleViewProvider.cs | 1 +
.../RecyclerView/ViewProvider/ViewProvider.cs | 3 +
14 files changed, 615 insertions(+), 349 deletions(-)
create mode 100644 Runtime/RecyclerView/Adapter/ItemRender.cs
create mode 100644 Runtime/RecyclerView/Adapter/ItemRender.cs.meta
create mode 100644 Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs
create mode 100644 Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs.meta
diff --git a/Runtime/RecyclerView/Adapter/Adapter.cs b/Runtime/RecyclerView/Adapter/Adapter.cs
index 71aad78..78cef3a 100644
--- a/Runtime/RecyclerView/Adapter/Adapter.cs
+++ b/Runtime/RecyclerView/Adapter/Adapter.cs
@@ -3,125 +3,160 @@ using System.Collections.Generic;
namespace AlicizaX.UI
{
- ///
- /// RecyclerView 的通用适配器,支持数据绑定、选中状态和列表操作
- ///
- /// 数据类型,必须实现 ISimpleViewData 接口
- public class Adapter : IAdapter where T : ISimpleViewData
+ internal interface IItemRenderCacheOwner
{
- protected RecyclerView recyclerView;
+ void ReleaseAllItemRenders();
+ }
+ public class Adapter : IAdapter, IItemRenderCacheOwner where T : ISimpleViewData
+ {
+ private sealed class ItemRenderEntry
+ {
+ public ItemRenderEntry(string viewName, IItemRender itemRender)
+ {
+ ViewName = viewName;
+ ItemRender = itemRender;
+ }
+
+ public string ViewName { get; }
+
+ public IItemRender ItemRender { get; }
+ }
+
+ protected RecyclerView recyclerView;
protected List list;
- protected Action onItemClick;
protected int choiceIndex = -1;
+ private readonly Dictionary itemRenderDefinitions = new(StringComparer.Ordinal);
+ private readonly Dictionary itemRenders = new();
+ private ItemRenderResolver.ItemRenderDefinition defaultItemRenderDefinition;
- ///
- /// 当前选中项的索引
- ///
public int ChoiceIndex
{
get => choiceIndex;
- set { SetChoiceIndex(value); }
+ set => SetChoiceIndex(value);
}
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- public Adapter(RecyclerView recyclerView) : this(recyclerView, new List(), null)
+ public Adapter(RecyclerView recyclerView) : this(recyclerView, new List())
{
}
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 数据列表
- public Adapter(RecyclerView recyclerView, List list) : this(recyclerView, list, null)
- {
- }
-
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 数据列表
- /// 列表项点击回调
- public Adapter(RecyclerView recyclerView, List list, Action onItemClick)
+ public Adapter(RecyclerView recyclerView, List list)
{
this.recyclerView = recyclerView;
this.list = list;
- this.onItemClick = onItemClick;
}
- ///
- /// 获取列表项总数
- ///
public virtual int GetItemCount()
{
return list == null ? 0 : list.Count;
}
- ///
- /// 获取实际数据项数量
- ///
public virtual int GetRealCount()
{
return GetItemCount();
}
- ///
- /// 获取指定索引位置的视图名称
- ///
public virtual string GetViewName(int index)
{
return "";
}
- ///
- /// 绑定视图持有者与数据
- ///
public virtual void OnBindViewHolder(ViewHolder viewHolder, int index)
{
- if (index < 0 || index >= GetItemCount()) return;
+ if (viewHolder == null) return;
+ if (!TryGetBindData(index, out var data)) return;
- T data = list[index];
-
- viewHolder.BindViewData(data);
- viewHolder.BindItemClick(data, t =>
+ string viewName = GetViewName(index);
+ Action defaultClickAction = CreateItemClickAction(index, data);
+ if (TryGetOrCreateItemRender(viewHolder, viewName, out var itemRender))
{
- SetChoiceIndex(index);
- onItemClick?.Invoke(data);
- });
- viewHolder.BindChoiceState(index == choiceIndex);
+ itemRender.Bind(data, index, defaultClickAction);
+ itemRender.UpdateSelection(index == choiceIndex);
+ return;
+ }
+
+ string resolvedViewName = string.IsNullOrEmpty(viewName) ? "" : viewName;
+ throw new InvalidOperationException(
+ $"RecyclerView item render is missing for view '{resolvedViewName}'. Holder='{viewHolder.GetType().Name}', Adapter='{GetType().Name}'.");
+ }
+
+ public virtual void OnRecycleViewHolder(ViewHolder viewHolder)
+ {
+ if (viewHolder == null) return;
+
+ if (TryGetItemRender(viewHolder, out var itemRender))
+ {
+ itemRender.UpdateSelection(false);
+ itemRender.Unbind();
+ }
+
+ viewHolder.ClearInteractionCallbacks();
}
- ///
- /// 通知数据已更改
- ///
public virtual void NotifyDataChanged()
{
recyclerView.RequestLayout();
recyclerView.Refresh();
}
- ///
- /// 设置数据列表并刷新视图
- ///
- /// 新的数据列表
- public virtual void SetList(List list)
+ public virtual void SetList(List list)
{
this.list = list;
recyclerView.Reset();
NotifyDataChanged();
}
- ///
- /// 获取指定索引的数据
- ///
- /// 数据索引
- /// 数据对象,索引无效时返回 default
+ public void RegisterItemRender(string viewName = "") where TItemRender : IItemRender
+ {
+ RegisterItemRender(typeof(TItemRender), viewName);
+ }
+
+ public void RegisterItemRender(Type itemRenderType, string viewName = "")
+ {
+ var definition = ItemRenderResolver.GetOrCreate(itemRenderType);
+
+ if (string.IsNullOrEmpty(viewName))
+ {
+ ReleaseCachedItemRenders(string.Empty);
+ defaultItemRenderDefinition = definition;
+ return;
+ }
+
+ ReleaseCachedItemRenders(viewName);
+ itemRenderDefinitions[viewName] = definition;
+ }
+
+ public bool UnregisterItemRender(string viewName = "")
+ {
+ if (string.IsNullOrEmpty(viewName))
+ {
+ bool hadDefault = defaultItemRenderDefinition != null;
+ defaultItemRenderDefinition = null;
+ if (hadDefault)
+ {
+ ReleaseCachedItemRenders(string.Empty);
+ }
+
+ return hadDefault;
+ }
+
+ bool removed = itemRenderDefinitions.Remove(viewName);
+ if (removed)
+ {
+ ReleaseCachedItemRenders(viewName);
+ }
+
+ return removed;
+ }
+
+ public void ClearItemRenderRegistrations()
+ {
+ ReleaseAllItemRenders();
+ itemRenderDefinitions.Clear();
+ defaultItemRenderDefinition = null;
+ }
+
public T GetData(int index)
{
if (index < 0 || index >= GetItemCount()) return default;
@@ -129,62 +164,36 @@ namespace AlicizaX.UI
return list[index];
}
- ///
- /// 添加单个数据项
- ///
- /// 要添加的数据项
public void Add(T item)
{
list.Add(item);
NotifyDataChanged();
}
- ///
- /// 批量添加数据项
- ///
- /// 要添加的数据集合
public void AddRange(IEnumerable collection)
{
list.AddRange(collection);
NotifyDataChanged();
}
- ///
- /// 在指定位置插入数据项
- ///
- /// 插入位置
- /// 要插入的数据项
public void Insert(int index, T item)
{
list.Insert(index, item);
NotifyDataChanged();
}
- ///
- /// 在指定位置批量插入数据项
- ///
- /// 插入位置
- /// 要插入的数据集合
public void InsertRange(int index, IEnumerable collection)
{
list.InsertRange(index, collection);
NotifyDataChanged();
}
- ///
- /// 移除指定的数据项
- ///
- /// 要移除的数据项
public void Remove(T item)
{
int index = list.IndexOf(item);
RemoveAt(index);
}
- ///
- /// 移除指定索引位置的数据项
- ///
- /// 要移除的索引
public void RemoveAt(int index)
{
if (index < 0 || index >= GetItemCount()) return;
@@ -193,112 +202,218 @@ namespace AlicizaX.UI
NotifyDataChanged();
}
- ///
- /// 移除指定范围的数据项
- ///
- /// 起始索引
- /// 移除数量
public void RemoveRange(int index, int count)
{
list.RemoveRange(index, count);
NotifyDataChanged();
}
- ///
- /// 移除所有符合条件的数据项
- ///
- /// 匹配条件
public void RemoveAll(Predicate match)
{
list.RemoveAll(match);
NotifyDataChanged();
}
- ///
- /// 清空所有数据
- ///
public void Clear()
{
list.Clear();
NotifyDataChanged();
}
- ///
- /// 反转指定范围的数据项顺序
- ///
- /// 起始索引
- /// 反转数量
public void Reverse(int index, int count)
{
list.Reverse(index, count);
NotifyDataChanged();
}
- ///
- /// 反转所有数据项顺序
- ///
public void Reverse()
{
list.Reverse();
NotifyDataChanged();
}
- ///
- /// 使用指定的比较器排序数据
- ///
- /// 比较器
public void Sort(Comparison comparison)
{
list.Sort(comparison);
NotifyDataChanged();
}
- ///
- /// 设置列表项点击回调
- ///
- /// 点击回调
- public void SetOnItemClick(Action onItemClick)
- {
- this.onItemClick = onItemClick;
- }
-
- ///
- /// 设置选中项索引
- ///
- /// 要选中的索引
protected void SetChoiceIndex(int index)
{
if (index == choiceIndex) return;
- if (choiceIndex != -1)
+ if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var oldHolder))
{
- if (TryGetViewHolder(choiceIndex, out var viewHolder))
- {
- viewHolder.BindChoiceState(false);
- }
+ UpdateSelectionState(oldHolder, false);
}
choiceIndex = index;
- if (choiceIndex != -1)
+ if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var newHolder))
{
- if (TryGetViewHolder(choiceIndex, out var viewHolder))
- {
- viewHolder.BindChoiceState(true);
- }
+ UpdateSelectionState(newHolder, true);
}
}
- ///
- /// 尝试获取指定索引的视图持有者
- ///
- /// 数据索引
- /// 输出的视图持有者
- /// 是否成功获取
private bool TryGetViewHolder(int index, out ViewHolder viewHolder)
{
viewHolder = recyclerView.ViewProvider.GetViewHolder(index);
return viewHolder != null;
}
+
+ protected virtual bool TryGetBindData(int index, out T data)
+ {
+ if (list == null || index < 0 || index >= list.Count)
+ {
+ data = default;
+ return false;
+ }
+
+ data = list[index];
+ return true;
+ }
+
+ protected virtual Action CreateItemClickAction(int index, T data)
+ {
+ return () => { SetChoiceIndex(index); };
+ }
+
+ private bool TryGetItemRenderDefinition(string viewName, out ItemRenderResolver.ItemRenderDefinition definition)
+ {
+ if (!string.IsNullOrEmpty(viewName) && itemRenderDefinitions.TryGetValue(viewName, out definition))
+ {
+ return true;
+ }
+
+ definition = defaultItemRenderDefinition;
+ return definition != null;
+ }
+
+ private void UpdateSelectionState(ViewHolder viewHolder, bool selected)
+ {
+ if (viewHolder == null)
+ {
+ return;
+ }
+
+ if (TryGetItemRender(viewHolder, out var itemRender))
+ {
+ itemRender.UpdateSelection(selected);
+ return;
+ }
+
+ string viewName = string.IsNullOrEmpty(viewHolder.Name) ? "" : viewHolder.Name;
+ throw new InvalidOperationException(
+ $"RecyclerView item render is missing for selection update. View='{viewName}', Holder='{viewHolder.GetType().Name}', Adapter='{GetType().Name}'.");
+ }
+
+ private bool TryGetItemRender(ViewHolder viewHolder, out IItemRender itemRender)
+ {
+ if (viewHolder != null &&
+ itemRenders.TryGetValue(viewHolder, out var entry) &&
+ entry.ItemRender != null)
+ {
+ itemRender = entry.ItemRender;
+ return true;
+ }
+
+ itemRender = null;
+ return false;
+ }
+
+ private bool TryGetOrCreateItemRender(ViewHolder viewHolder, string viewName, out IItemRender itemRender)
+ {
+ if (viewHolder == null)
+ {
+ itemRender = null;
+ return false;
+ }
+
+ if (itemRenders.TryGetValue(viewHolder, out var entry))
+ {
+ if (entry.ItemRender != null && string.Equals(entry.ViewName, viewName, StringComparison.Ordinal))
+ {
+ itemRender = entry.ItemRender;
+ return true;
+ }
+
+ entry.ItemRender?.Unbind();
+ viewHolder.Destroyed -= OnViewHolderDestroyed;
+ itemRenders.Remove(viewHolder);
+ }
+
+ if (!TryGetItemRenderDefinition(viewName, out var definition))
+ {
+ itemRender = null;
+ return false;
+ }
+
+ itemRender = definition.Create(viewHolder);
+ itemRenders[viewHolder] = new ItemRenderEntry(viewName, itemRender);
+ viewHolder.Destroyed += OnViewHolderDestroyed;
+ return true;
+ }
+
+ void IItemRenderCacheOwner.ReleaseAllItemRenders()
+ {
+ ReleaseAllItemRenders();
+ }
+
+ private void ReleaseAllItemRenders()
+ {
+ foreach (var pair in itemRenders)
+ {
+ pair.Value.ItemRender?.Unbind();
+ if (pair.Key != null)
+ {
+ pair.Key.Destroyed -= OnViewHolderDestroyed;
+ }
+ }
+
+ itemRenders.Clear();
+ }
+
+ private void ReleaseCachedItemRenders(string viewName)
+ {
+ if (itemRenders.Count == 0)
+ {
+ return;
+ }
+
+ List viewHoldersToRemove = null;
+ foreach (var pair in itemRenders)
+ {
+ if (!string.Equals(pair.Value.ViewName, viewName, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ pair.Value.ItemRender?.Unbind();
+ pair.Key.Destroyed -= OnViewHolderDestroyed;
+ viewHoldersToRemove ??= new List();
+ viewHoldersToRemove.Add(pair.Key);
+ }
+
+ if (viewHoldersToRemove == null)
+ {
+ return;
+ }
+
+ for (int i = 0; i < viewHoldersToRemove.Count; i++)
+ {
+ itemRenders.Remove(viewHoldersToRemove[i]);
+ }
+ }
+
+ private void OnViewHolderDestroyed(ViewHolder viewHolder)
+ {
+ if (viewHolder == null)
+ {
+ return;
+ }
+
+ viewHolder.Destroyed -= OnViewHolderDestroyed;
+ itemRenders.Remove(viewHolder);
+ }
}
}
diff --git a/Runtime/RecyclerView/Adapter/GroupAdapter.cs b/Runtime/RecyclerView/Adapter/GroupAdapter.cs
index 1bd2740..736bd7d 100644
--- a/Runtime/RecyclerView/Adapter/GroupAdapter.cs
+++ b/Runtime/RecyclerView/Adapter/GroupAdapter.cs
@@ -6,7 +6,7 @@ namespace AlicizaX.UI
public class GroupAdapter : Adapter where TData : IGroupViewData, new()
{
private readonly List showList = new();
- private string groupViewName;
+ private readonly string groupViewName;
public GroupAdapter(RecyclerView recyclerView, string groupViewName) : base(recyclerView)
{
@@ -17,15 +17,10 @@ namespace AlicizaX.UI
{
}
- public GroupAdapter(RecyclerView recyclerView, List list, Action onItemClick) : base(recyclerView, list, onItemClick)
- {
- }
-
public GroupAdapter(RecyclerView recyclerView) : base(recyclerView)
{
}
-
public override int GetItemCount()
{
return showList.Count;
@@ -36,28 +31,6 @@ namespace AlicizaX.UI
return showList[index].TemplateName;
}
- public override void OnBindViewHolder(ViewHolder viewHolder, int index)
- {
- if (index < 0 || index >= GetItemCount()) return;
-
- TData data = showList[index];
-
- viewHolder.BindViewData(data);
- viewHolder.BindItemClick(data, t =>
- {
- if (data.TemplateName == groupViewName)
- {
- data.Expanded = !data.Expanded;
- NotifyDataChanged();
- }
- else
- {
- SetChoiceIndex(index);
- onItemClick?.Invoke(data);
- }
- });
- }
-
public override void NotifyDataChanged()
{
foreach (var data in list)
@@ -118,5 +91,32 @@ namespace AlicizaX.UI
var collapseList = showList.FindAll(data => data.Type == showList[index].Type && data.TemplateName != groupViewName);
showList.RemoveRange(index + 1, collapseList.Count);
}
+
+ protected override bool TryGetBindData(int index, out TData data)
+ {
+ if (index < 0 || index >= showList.Count)
+ {
+ data = default;
+ return false;
+ }
+
+ data = showList[index];
+ return true;
+ }
+
+ protected override Action CreateItemClickAction(int index, TData data)
+ {
+ return () =>
+ {
+ if (data.TemplateName == groupViewName)
+ {
+ data.Expanded = !data.Expanded;
+ NotifyDataChanged();
+ return;
+ }
+
+ SetChoiceIndex(index);
+ };
+ }
}
}
diff --git a/Runtime/RecyclerView/Adapter/IAdapter.cs b/Runtime/RecyclerView/Adapter/IAdapter.cs
index dcfcb88..df32630 100644
--- a/Runtime/RecyclerView/Adapter/IAdapter.cs
+++ b/Runtime/RecyclerView/Adapter/IAdapter.cs
@@ -31,6 +31,8 @@ namespace AlicizaX.UI
/// 数据索引
void OnBindViewHolder(ViewHolder viewHolder, int index);
+ void OnRecycleViewHolder(ViewHolder viewHolder);
+
///
/// 通知数据已更改,触发视图刷新
///
diff --git a/Runtime/RecyclerView/Adapter/ItemRender.cs b/Runtime/RecyclerView/Adapter/ItemRender.cs
new file mode 100644
index 0000000..cb51b97
--- /dev/null
+++ b/Runtime/RecyclerView/Adapter/ItemRender.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace AlicizaX.UI
+{
+ public interface IItemRender
+ {
+ void Bind(object data, int index, Action defaultClickAction);
+
+ void UpdateSelection(bool selected);
+
+ void Unbind();
+ }
+
+ public abstract class ItemRender : IItemRender
+ where THolder : ViewHolder
+ {
+ private Action defaultClickAction;
+
+ protected ItemRender(THolder holder)
+ {
+ Holder = holder ?? throw new ArgumentNullException(nameof(holder));
+ CurrentIndex = -1;
+ }
+
+ protected THolder Holder { get; }
+
+ protected TData CurrentData { get; private set; }
+
+ protected int CurrentIndex { get; private set; }
+
+ public void Bind(object data, int index, Action defaultClickAction)
+ {
+ if (data is not TData itemData)
+ {
+ throw new InvalidCastException(
+ $"ItemRender '{GetType().Name}' expected data '{typeof(TData).Name}', but got '{data?.GetType().Name ?? "null"}'.");
+ }
+
+ CurrentData = itemData;
+ CurrentIndex = index;
+ this.defaultClickAction = defaultClickAction;
+ Holder.SetInteractionCallbacks(HandleClick, HandlePointerEnter, HandlePointerExit);
+ Bind(itemData, index);
+ }
+
+ public void UpdateSelection(bool selected)
+ {
+ OnSelectionChanged(selected);
+ }
+
+ public void Unbind()
+ {
+ OnClear();
+ CurrentData = default;
+ CurrentIndex = -1;
+ defaultClickAction = null;
+ Holder.ClearInteractionCallbacks();
+ }
+
+ protected abstract void Bind(TData data, int index);
+
+ protected virtual void OnSelectionChanged(bool selected)
+ {
+ }
+
+ protected virtual void OnClear()
+ {
+ }
+
+ protected virtual void OnClick()
+ {
+ }
+
+ protected virtual void OnPointerEnter()
+ {
+ }
+
+ protected virtual void OnPointerExit()
+ {
+ }
+
+ private void HandleClick()
+ {
+ defaultClickAction?.Invoke();
+ OnClick();
+ }
+
+ private void HandlePointerEnter()
+ {
+ OnPointerEnter();
+ }
+
+ private void HandlePointerExit()
+ {
+ OnPointerExit();
+ }
+ }
+
+ internal static class ItemRenderResolver
+ {
+ internal sealed class ItemRenderDefinition
+ {
+ private readonly ConstructorInfo constructor;
+
+ public ItemRenderDefinition(Type itemRenderType, Type holderType, ConstructorInfo constructor)
+ {
+ ItemRenderType = itemRenderType;
+ HolderType = holderType;
+ this.constructor = constructor;
+ }
+
+ public Type ItemRenderType { get; }
+
+ public Type HolderType { get; }
+
+ public IItemRender Create(ViewHolder viewHolder)
+ {
+ if (viewHolder == null)
+ {
+ throw new ArgumentNullException(nameof(viewHolder));
+ }
+
+ if (!HolderType.IsInstanceOfType(viewHolder))
+ {
+ throw new InvalidOperationException(
+ $"RecyclerView item render '{ItemRenderType.FullName}' expects holder '{HolderType.FullName}', but got '{viewHolder.GetType().FullName}'.");
+ }
+
+ return (IItemRender)constructor.Invoke(new object[] { viewHolder });
+ }
+ }
+
+ private static readonly Dictionary Definitions = new();
+
+ public static ItemRenderDefinition GetOrCreate(Type itemRenderType)
+ {
+ if (itemRenderType == null)
+ {
+ throw new ArgumentNullException(nameof(itemRenderType));
+ }
+
+ if (Definitions.TryGetValue(itemRenderType, out ItemRenderDefinition definition))
+ {
+ return definition;
+ }
+
+ definition = CreateDefinition(itemRenderType);
+ Definitions[itemRenderType] = definition;
+ return definition;
+ }
+
+ private static ItemRenderDefinition CreateDefinition(Type itemRenderType)
+ {
+ if (itemRenderType.IsAbstract ||
+ itemRenderType.IsInterface ||
+ itemRenderType.ContainsGenericParameters ||
+ !typeof(IItemRender).IsAssignableFrom(itemRenderType))
+ {
+ throw new InvalidOperationException(
+ $"RecyclerView item render type '{itemRenderType.FullName}' is invalid.");
+ }
+
+ if (!TryGetHolderType(itemRenderType, out Type holderType))
+ {
+ throw new InvalidOperationException(
+ $"RecyclerView item render '{itemRenderType.FullName}' must inherit from ItemRender.");
+ }
+
+ ConstructorInfo constructor = itemRenderType.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null,
+ new[] { holderType },
+ null);
+
+ if (constructor == null)
+ {
+ throw new InvalidOperationException(
+ $"RecyclerView item render '{itemRenderType.FullName}' must declare a constructor with a single '{holderType.FullName}' parameter.");
+ }
+
+ return new ItemRenderDefinition(itemRenderType, holderType, constructor);
+ }
+
+ private static bool TryGetHolderType(Type itemRenderType, out Type holderType)
+ {
+ for (Type current = itemRenderType; current != null && current != typeof(object); current = current.BaseType)
+ {
+ if (current.IsGenericType &&
+ current.GetGenericTypeDefinition() == typeof(ItemRender<,>))
+ {
+ Type[] arguments = current.GetGenericArguments();
+ holderType = arguments[1];
+ return true;
+ }
+ }
+
+ holderType = null;
+ return false;
+ }
+ }
+}
diff --git a/Runtime/RecyclerView/Adapter/ItemRender.cs.meta b/Runtime/RecyclerView/Adapter/ItemRender.cs.meta
new file mode 100644
index 0000000..e08c420
--- /dev/null
+++ b/Runtime/RecyclerView/Adapter/ItemRender.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5baf3050ddf617e4aa583b8ec23f82ce
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/RecyclerView/Adapter/LoopAdapter.cs b/Runtime/RecyclerView/Adapter/LoopAdapter.cs
index d1f2c15..69182bf 100644
--- a/Runtime/RecyclerView/Adapter/LoopAdapter.cs
+++ b/Runtime/RecyclerView/Adapter/LoopAdapter.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
namespace AlicizaX.UI
@@ -13,10 +12,6 @@ namespace AlicizaX.UI
{
}
- public LoopAdapter(RecyclerView recyclerView, List list, Action onItemClick) : base(recyclerView, list, onItemClick)
- {
- }
-
public override int GetItemCount()
{
return int.MaxValue;
diff --git a/Runtime/RecyclerView/Adapter/MixedAdapter.cs b/Runtime/RecyclerView/Adapter/MixedAdapter.cs
index 437d1f6..d156e48 100644
--- a/Runtime/RecyclerView/Adapter/MixedAdapter.cs
+++ b/Runtime/RecyclerView/Adapter/MixedAdapter.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
namespace AlicizaX.UI
@@ -13,10 +12,6 @@ namespace AlicizaX.UI
{
}
- public MixedAdapter(RecyclerView recyclerView, List list, Action onItemClick) : base(recyclerView, list, onItemClick)
- {
- }
-
public override string GetViewName(int index)
{
return list[index].TemplateName;
diff --git a/Runtime/RecyclerView/UGList.cs b/Runtime/RecyclerView/UGList.cs
index a9da453..2e9a186 100644
--- a/Runtime/RecyclerView/UGList.cs
+++ b/Runtime/RecyclerView/UGList.cs
@@ -3,30 +3,14 @@ using System.Collections.Generic;
namespace AlicizaX.UI
{
- ///
- /// UGList 基类
- /// 提供简化的列表操作接口,封装 RecyclerView 和 Adapter 的交互
- ///
- /// 数据类型
- /// 适配器类型
public abstract class UGListBase where TAdapter : Adapter where TData : ISimpleViewData
{
protected readonly RecyclerView _recyclerView;
-
protected readonly TAdapter _adapter;
- ///
- /// 获取关联的 RecyclerView 实例
- ///
public RecyclerView RecyclerView => _recyclerView;
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 适配器实例
- /// 列表项点击回调
- public UGListBase(RecyclerView recyclerView, TAdapter adapter, Action onItemClick = null)
+ public UGListBase(RecyclerView recyclerView, TAdapter adapter)
{
_recyclerView = recyclerView;
_adapter = adapter;
@@ -35,24 +19,32 @@ namespace AlicizaX.UI
{
_recyclerView.SetAdapter(_adapter);
}
-
- if (onItemClick != null)
- {
- _adapter.SetOnItemClick(onItemClick);
- }
}
- ///
- /// 获取适配器实例
- ///
public TAdapter Adapter => _adapter;
+ public void RegisterItemRender(string viewName = "") where TItemRender : IItemRender
+ {
+ _adapter.RegisterItemRender(viewName);
+ }
+
+ public void RegisterItemRender(Type itemRenderType, string viewName = "")
+ {
+ _adapter.RegisterItemRender(itemRenderType, viewName);
+ }
+
+ public bool UnregisterItemRender(string viewName = "")
+ {
+ return _adapter.UnregisterItemRender(viewName);
+ }
+
+ public void ClearItemRenderRegistrations()
+ {
+ _adapter.ClearItemRenderRegistrations();
+ }
+
private List _datas;
- ///
- /// 获取或设置数据列表
- /// 设置时会自动更新适配器
- ///
public List Data
{
get => _datas;
@@ -64,108 +56,50 @@ namespace AlicizaX.UI
}
}
- ///
- /// 通用列表类
- /// 用于显示简单的单一类型数据列表
- ///
- /// 数据类型,必须实现 ISimpleViewData
public class UGList : UGListBase> where TData : ISimpleViewData
{
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 列表项点击回调
- public UGList(RecyclerView recyclerView, Action onItemClick = null)
- : base(recyclerView, new Adapter(recyclerView), onItemClick)
+ public UGList(RecyclerView recyclerView)
+ : base(recyclerView, new Adapter(recyclerView))
{
}
}
- ///
- /// 分组列表类
- /// 用于显示带有分组头的列表数据
- ///
- /// 数据类型,必须实现 IGroupViewData
public class UGGroupList : UGListBase> where TData : class, IGroupViewData, new()
{
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 分组头视图名称
- /// 列表项点击回调
- public UGGroupList(RecyclerView recyclerView, string groupViewName, Action onItemClick = null)
- : base(recyclerView, new GroupAdapter(recyclerView, groupViewName), onItemClick)
+ public UGGroupList(RecyclerView recyclerView, string groupViewName)
+ : base(recyclerView, new GroupAdapter(recyclerView, groupViewName))
{
}
}
- ///
- /// 循环列表类
- /// 用于实现无限循环滚动的列表
- ///
- /// 数据类型,必须实现 ISimpleViewData
public class UGLoopList : UGListBase> where TData : ISimpleViewData, new()
{
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 列表项点击回调
- public UGLoopList(RecyclerView recyclerView, Action onItemClick = null)
- : base(recyclerView, new LoopAdapter(recyclerView), onItemClick)
+ public UGLoopList(RecyclerView recyclerView)
+ : base(recyclerView, new LoopAdapter(recyclerView))
{
}
}
- ///
- /// 混合列表类
- /// 用于显示多种不同类型的列表项
- ///
- /// 数据类型,必须实现 IMixedViewData
public class UGMixedList : UGListBase> where TData : IMixedViewData
{
- ///
- /// 构造函数
- ///
- /// RecyclerView 实例
- /// 列表项点击回调
- public UGMixedList(RecyclerView recyclerView, Action onItemClick = null)
- : base(recyclerView, new MixedAdapter(recyclerView), onItemClick)
+ public UGMixedList(RecyclerView recyclerView)
+ : base(recyclerView, new MixedAdapter(recyclerView))
{
}
}
- ///
- /// UGList 创建辅助类
- /// 提供便捷的静态方法来创建各种类型的列表
- ///
public static class UGListCreateHelper
{
- ///
- /// 创建通用列表
- ///
- public static UGList Create(RecyclerView recyclerView, Action onItemClick = null) where TData : ISimpleViewData
- => new UGList(recyclerView, onItemClick);
+ public static UGList Create(RecyclerView recyclerView) where TData : ISimpleViewData
+ => new UGList(recyclerView);
- ///
- /// 创建分组列表
- ///
- public static UGGroupList CreateGroup(RecyclerView recyclerView, string groupViewName, Action onItemClick = null) where TData : class, IGroupViewData, new()
- => new UGGroupList(recyclerView, groupViewName, onItemClick);
+ public static UGGroupList CreateGroup(RecyclerView recyclerView, string groupViewName) where TData : class, IGroupViewData, new()
+ => new UGGroupList(recyclerView, groupViewName);
- ///
- /// 创建循环列表
- ///
- public static UGLoopList CreateLoop(RecyclerView recyclerView, Action onItemClick = null) where TData : ISimpleViewData, new()
- => new UGLoopList(recyclerView, onItemClick);
+ public static UGLoopList CreateLoop(RecyclerView recyclerView) where TData : ISimpleViewData, new()
+ => new UGLoopList(recyclerView);
- ///
- /// 创建混合列表
- ///
- public static UGMixedList CreateMixed(RecyclerView recyclerView, Action onItemClick = null) where TData : IMixedViewData
- => new UGMixedList(recyclerView, onItemClick);
+ public static UGMixedList CreateMixed(RecyclerView recyclerView) where TData : IMixedViewData
+ => new UGMixedList(recyclerView);
}
-
}
diff --git a/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs b/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs
new file mode 100644
index 0000000..d6de0d4
--- /dev/null
+++ b/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs
@@ -0,0 +1,22 @@
+using UnityEngine.EventSystems;
+
+namespace AlicizaX.UI
+{
+ public abstract class InteractiveViewHolder : ViewHolder, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
+ {
+ public virtual void OnPointerClick(PointerEventData eventData)
+ {
+ InvokeClickAction();
+ }
+
+ public virtual void OnPointerEnter(PointerEventData eventData)
+ {
+ InvokePointerEnterAction();
+ }
+
+ public virtual void OnPointerExit(PointerEventData eventData)
+ {
+ InvokePointerExitAction();
+ }
+ }
+}
diff --git a/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs.meta b/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs.meta
new file mode 100644
index 0000000..d7b2804
--- /dev/null
+++ b/Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 491a09d781095104989abb7a91424008
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/RecyclerView/ViewHolder/ViewHolder.cs b/Runtime/RecyclerView/ViewHolder/ViewHolder.cs
index a0ee452..7d7c867 100644
--- a/Runtime/RecyclerView/ViewHolder/ViewHolder.cs
+++ b/Runtime/RecyclerView/ViewHolder/ViewHolder.cs
@@ -1,20 +1,17 @@
using System;
-using AlicizaX.UI.Extension;
using UnityEngine;
-using UnityEngine.UI;
namespace AlicizaX.UI
{
- ///
- /// 视图持有者基类,用于缓存和复用列表项视图
- ///
public abstract class ViewHolder : MonoBehaviour
{
private RectTransform rectTransform;
+ private Action clickAction;
+ private Action pointerEnterAction;
+ private Action pointerExitAction;
+
+ internal event Action Destroyed;
- ///
- /// 获取 RectTransform 组件
- ///
public RectTransform RectTransform
{
get
@@ -26,83 +23,59 @@ namespace AlicizaX.UI
return rectTransform;
}
- private set { rectTransform = value; }
+ private set => rectTransform = value;
}
- ///
- /// 视图名称,用于区分不同类型的视图
- ///
public string Name { get; internal set; }
- ///
- /// 当前绑定的数据索引
- ///
public int Index { get; internal set; }
- ///
- /// 选中状态
- ///
- public bool ChoiseState { private set; get; }
-
- ///
- /// 获取视图的尺寸
- ///
public Vector2 SizeDelta => RectTransform.sizeDelta;
-
- ///
- /// 视图首次创建时调用
- ///
protected internal virtual void OnStart()
{
}
- ///
- /// 视图被回收到对象池时调用
- ///
protected internal virtual void OnRecycled()
{
}
- ///
- /// 绑定视图数据(抽象方法,子类必须实现)
- ///
- /// 数据类型
- /// 要绑定的数据
- public abstract void BindViewData(T data);
-
- ///
- /// 绑定列表项点击事件
- ///
- /// 数据类型
- /// 数据对象
- /// 点击回调
- protected internal virtual void BindItemClick(T data, Action action)
+ internal void SetInteractionCallbacks(
+ Action clickAction = null,
+ Action pointerEnterAction = null,
+ Action pointerExitAction = null)
{
- // _button.onClick.RemoveAllListeners();
- // _button.onClick.AddListener(() => action?.Invoke(data));
+ this.clickAction = clickAction;
+ this.pointerEnterAction = pointerEnterAction;
+ this.pointerExitAction = pointerExitAction;
}
-
- ///
- /// 绑定选中状态
- ///
- /// 是否选中
- protected internal void BindChoiceState(bool state)
+ public void ClearInteractionCallbacks()
{
- if (ChoiseState != state)
- {
- ChoiseState = state;
- OnBindChoiceState(state);
- }
+ clickAction = null;
+ pointerEnterAction = null;
+ pointerExitAction = null;
}
- ///
- /// 选中状态改变时的回调(可在子类中重写)
- ///
- /// 是否选中
- protected internal virtual void OnBindChoiceState(bool state)
+ protected void InvokeClickAction()
{
+ clickAction?.Invoke();
+ }
+
+ protected void InvokePointerEnterAction()
+ {
+ pointerEnterAction?.Invoke();
+ }
+
+ protected void InvokePointerExitAction()
+ {
+ pointerExitAction?.Invoke();
+ }
+
+ protected virtual void OnDestroy()
+ {
+ Destroyed?.Invoke(this);
+ Destroyed = null;
}
}
}
diff --git a/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs b/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs
index 705f065..5024d93 100644
--- a/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs
+++ b/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs
@@ -57,6 +57,7 @@ namespace AlicizaX.UI
public override void Reset()
{
Clear();
+ (Adapter as IItemRenderCacheOwner)?.ReleaseAllItemRenders();
objectPool.Dispose();
}
}
diff --git a/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs b/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs
index 7811a29..62505ec 100644
--- a/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs
+++ b/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs
@@ -45,6 +45,7 @@ namespace AlicizaX.UI
public override void Reset()
{
Clear();
+ (Adapter as IItemRenderCacheOwner)?.ReleaseAllItemRenders();
objectPool.Dispose();
}
}
diff --git a/Runtime/RecyclerView/ViewProvider/ViewProvider.cs b/Runtime/RecyclerView/ViewProvider/ViewProvider.cs
index 688ab56..934b425 100644
--- a/Runtime/RecyclerView/ViewProvider/ViewProvider.cs
+++ b/Runtime/RecyclerView/ViewProvider/ViewProvider.cs
@@ -111,6 +111,7 @@ namespace AlicizaX.UI
var viewHolder = viewHolders[viewHolderIndex];
viewHolders.RemoveAt(viewHolderIndex);
+ Adapter?.OnRecycleViewHolder(viewHolder);
viewHolder.OnRecycled();
Free(viewHolder.Name, viewHolder);
}
@@ -160,6 +161,8 @@ namespace AlicizaX.UI
{
foreach (var viewHolder in viewHolders)
{
+ Adapter?.OnRecycleViewHolder(viewHolder);
+ viewHolder.OnRecycled();
Free(viewHolder.Name, viewHolder);
}