RecyclerView优化
1.分离逻辑视图与渲染视图 2.xxxx若干项懒得写了
This commit is contained in:
parent
91f7641f59
commit
f22a4f1efa
@ -3,113 +3,103 @@ using System.Collections.Generic;
|
||||
|
||||
namespace AlicizaX.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// RecyclerView 的通用适配器,支持数据绑定、选中状态和列表操作
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型,必须实现 ISimpleViewData 接口</typeparam>
|
||||
public class Adapter<T> : IAdapter where T : ISimpleViewData
|
||||
internal interface IItemRenderCacheOwner
|
||||
{
|
||||
protected RecyclerView recyclerView;
|
||||
void ReleaseAllItemRenders();
|
||||
}
|
||||
|
||||
public class Adapter<T> : 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<T> list;
|
||||
protected Action<T> onItemClick;
|
||||
|
||||
protected int choiceIndex = -1;
|
||||
private readonly Dictionary<string, ItemRenderResolver.ItemRenderDefinition> itemRenderDefinitions = new(StringComparer.Ordinal);
|
||||
private readonly Dictionary<ViewHolder, ItemRenderEntry> itemRenders = new();
|
||||
private ItemRenderResolver.ItemRenderDefinition defaultItemRenderDefinition;
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中项的索引
|
||||
/// </summary>
|
||||
public int ChoiceIndex
|
||||
{
|
||||
get => choiceIndex;
|
||||
set { SetChoiceIndex(value); }
|
||||
set => SetChoiceIndex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>(), null)
|
||||
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="list">数据列表</param>
|
||||
public Adapter(RecyclerView recyclerView, List<T> list) : this(recyclerView, list, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="list">数据列表</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public Adapter(RecyclerView recyclerView, List<T> list, Action<T> onItemClick)
|
||||
public Adapter(RecyclerView recyclerView, List<T> list)
|
||||
{
|
||||
this.recyclerView = recyclerView;
|
||||
this.list = list;
|
||||
this.onItemClick = onItemClick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表项总数
|
||||
/// </summary>
|
||||
public virtual int GetItemCount()
|
||||
{
|
||||
return list == null ? 0 : list.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际数据项数量
|
||||
/// </summary>
|
||||
public virtual int GetRealCount()
|
||||
{
|
||||
return GetItemCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定索引位置的视图名称
|
||||
/// </summary>
|
||||
public virtual string GetViewName(int index)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定视图持有者与数据
|
||||
/// </summary>
|
||||
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) ? "<default>" : 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知数据已更改
|
||||
/// </summary>
|
||||
public virtual void NotifyDataChanged()
|
||||
{
|
||||
recyclerView.RequestLayout();
|
||||
recyclerView.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据列表并刷新视图
|
||||
/// </summary>
|
||||
/// <param name="list">新的数据列表</param>
|
||||
public virtual void SetList(List<T> list)
|
||||
{
|
||||
this.list = list;
|
||||
@ -117,11 +107,56 @@ namespace AlicizaX.UI
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定索引的数据
|
||||
/// </summary>
|
||||
/// <param name="index">数据索引</param>
|
||||
/// <returns>数据对象,索引无效时返回 default</returns>
|
||||
public void RegisterItemRender<TItemRender>(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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加单个数据项
|
||||
/// </summary>
|
||||
/// <param name="item">要添加的数据项</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
list.Add(item);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加数据项
|
||||
/// </summary>
|
||||
/// <param name="collection">要添加的数据集合</param>
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
list.AddRange(collection);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定位置插入数据项
|
||||
/// </summary>
|
||||
/// <param name="index">插入位置</param>
|
||||
/// <param name="item">要插入的数据项</param>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
list.Insert(index, item);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定位置批量插入数据项
|
||||
/// </summary>
|
||||
/// <param name="index">插入位置</param>
|
||||
/// <param name="collection">要插入的数据集合</param>
|
||||
public void InsertRange(int index, IEnumerable<T> collection)
|
||||
{
|
||||
list.InsertRange(index, collection);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定的数据项
|
||||
/// </summary>
|
||||
/// <param name="item">要移除的数据项</param>
|
||||
public void Remove(T item)
|
||||
{
|
||||
int index = list.IndexOf(item);
|
||||
RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定索引位置的数据项
|
||||
/// </summary>
|
||||
/// <param name="index">要移除的索引</param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= GetItemCount()) return;
|
||||
@ -193,112 +202,218 @@ namespace AlicizaX.UI
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定范围的数据项
|
||||
/// </summary>
|
||||
/// <param name="index">起始索引</param>
|
||||
/// <param name="count">移除数量</param>
|
||||
public void RemoveRange(int index, int count)
|
||||
{
|
||||
list.RemoveRange(index, count);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除所有符合条件的数据项
|
||||
/// </summary>
|
||||
/// <param name="match">匹配条件</param>
|
||||
public void RemoveAll(Predicate<T> match)
|
||||
{
|
||||
list.RemoveAll(match);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有数据
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转指定范围的数据项顺序
|
||||
/// </summary>
|
||||
/// <param name="index">起始索引</param>
|
||||
/// <param name="count">反转数量</param>
|
||||
public void Reverse(int index, int count)
|
||||
{
|
||||
list.Reverse(index, count);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转所有数据项顺序
|
||||
/// </summary>
|
||||
public void Reverse()
|
||||
{
|
||||
list.Reverse();
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的比较器排序数据
|
||||
/// </summary>
|
||||
/// <param name="comparison">比较器</param>
|
||||
public void Sort(Comparison<T> comparison)
|
||||
{
|
||||
list.Sort(comparison);
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置列表项点击回调
|
||||
/// </summary>
|
||||
/// <param name="onItemClick">点击回调</param>
|
||||
public void SetOnItemClick(Action<T> onItemClick)
|
||||
{
|
||||
this.onItemClick = onItemClick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置选中项索引
|
||||
/// </summary>
|
||||
/// <param name="index">要选中的索引</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定索引的视图持有者
|
||||
/// </summary>
|
||||
/// <param name="index">数据索引</param>
|
||||
/// <param name="viewHolder">输出的视图持有者</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
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) ? "<default>" : 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<ViewHolder> 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<ViewHolder>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ namespace AlicizaX.UI
|
||||
public class GroupAdapter<TData> : Adapter<TData> where TData : IGroupViewData, new()
|
||||
{
|
||||
private readonly List<TData> 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<TData> list, Action<TData> 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,8 @@ namespace AlicizaX.UI
|
||||
/// <param name="index">数据索引</param>
|
||||
void OnBindViewHolder(ViewHolder viewHolder, int index);
|
||||
|
||||
void OnRecycleViewHolder(ViewHolder viewHolder);
|
||||
|
||||
/// <summary>
|
||||
/// 通知数据已更改,触发视图刷新
|
||||
/// </summary>
|
||||
|
||||
203
Runtime/RecyclerView/Adapter/ItemRender.cs
Normal file
203
Runtime/RecyclerView/Adapter/ItemRender.cs
Normal file
@ -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<TData, THolder> : 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<Type, ItemRenderDefinition> 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<TData, THolder>.");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/RecyclerView/Adapter/ItemRender.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/ItemRender.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5baf3050ddf617e4aa583b8ec23f82ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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<T> list, Action<T> onItemClick) : base(recyclerView, list, onItemClick)
|
||||
{
|
||||
}
|
||||
|
||||
public override int GetItemCount()
|
||||
{
|
||||
return int.MaxValue;
|
||||
|
||||
@ -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<TData> list, Action<TData> onItemClick) : base(recyclerView, list, onItemClick)
|
||||
{
|
||||
}
|
||||
|
||||
public override string GetViewName(int index)
|
||||
{
|
||||
return list[index].TemplateName;
|
||||
|
||||
@ -3,30 +3,14 @@ using System.Collections.Generic;
|
||||
|
||||
namespace AlicizaX.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UGList 基类
|
||||
/// 提供简化的列表操作接口,封装 RecyclerView 和 Adapter 的交互
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
/// <typeparam name="TAdapter">适配器类型</typeparam>
|
||||
public abstract class UGListBase<TData, TAdapter> where TAdapter : Adapter<TData> where TData : ISimpleViewData
|
||||
{
|
||||
protected readonly RecyclerView _recyclerView;
|
||||
|
||||
protected readonly TAdapter _adapter;
|
||||
|
||||
/// <summary>
|
||||
/// 获取关联的 RecyclerView 实例
|
||||
/// </summary>
|
||||
public RecyclerView RecyclerView => _recyclerView;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="adapter">适配器实例</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public UGListBase(RecyclerView recyclerView, TAdapter adapter, Action<TData> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取适配器实例
|
||||
/// </summary>
|
||||
public TAdapter Adapter => _adapter;
|
||||
|
||||
public void RegisterItemRender<TItemRender>(string viewName = "") where TItemRender : IItemRender
|
||||
{
|
||||
_adapter.RegisterItemRender<TItemRender>(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<TData> _datas;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置数据列表
|
||||
/// 设置时会自动更新适配器
|
||||
/// </summary>
|
||||
public List<TData> Data
|
||||
{
|
||||
get => _datas;
|
||||
@ -64,108 +56,50 @@ namespace AlicizaX.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用列表类
|
||||
/// 用于显示简单的单一类型数据列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型,必须实现 ISimpleViewData</typeparam>
|
||||
public class UGList<TData> : UGListBase<TData, Adapter<TData>> where TData : ISimpleViewData
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public UGList(RecyclerView recyclerView, Action<TData> onItemClick = null)
|
||||
: base(recyclerView, new Adapter<TData>(recyclerView), onItemClick)
|
||||
public UGList(RecyclerView recyclerView)
|
||||
: base(recyclerView, new Adapter<TData>(recyclerView))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分组列表类
|
||||
/// 用于显示带有分组头的列表数据
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型,必须实现 IGroupViewData</typeparam>
|
||||
public class UGGroupList<TData> : UGListBase<TData, GroupAdapter<TData>> where TData : class, IGroupViewData, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="groupViewName">分组头视图名称</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public UGGroupList(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null)
|
||||
: base(recyclerView, new GroupAdapter<TData>(recyclerView, groupViewName), onItemClick)
|
||||
public UGGroupList(RecyclerView recyclerView, string groupViewName)
|
||||
: base(recyclerView, new GroupAdapter<TData>(recyclerView, groupViewName))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 循环列表类
|
||||
/// 用于实现无限循环滚动的列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型,必须实现 ISimpleViewData</typeparam>
|
||||
public class UGLoopList<TData> : UGListBase<TData, LoopAdapter<TData>> where TData : ISimpleViewData, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public UGLoopList(RecyclerView recyclerView, Action<TData> onItemClick = null)
|
||||
: base(recyclerView, new LoopAdapter<TData>(recyclerView), onItemClick)
|
||||
public UGLoopList(RecyclerView recyclerView)
|
||||
: base(recyclerView, new LoopAdapter<TData>(recyclerView))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 混合列表类
|
||||
/// 用于显示多种不同类型的列表项
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型,必须实现 IMixedViewData</typeparam>
|
||||
public class UGMixedList<TData> : UGListBase<TData, MixedAdapter<TData>> where TData : IMixedViewData
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="recyclerView">RecyclerView 实例</param>
|
||||
/// <param name="onItemClick">列表项点击回调</param>
|
||||
public UGMixedList(RecyclerView recyclerView, Action<TData> onItemClick = null)
|
||||
: base(recyclerView, new MixedAdapter<TData>(recyclerView), onItemClick)
|
||||
public UGMixedList(RecyclerView recyclerView)
|
||||
: base(recyclerView, new MixedAdapter<TData>(recyclerView))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UGList 创建辅助类
|
||||
/// 提供便捷的静态方法来创建各种类型的列表
|
||||
/// </summary>
|
||||
public static class UGListCreateHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建通用列表
|
||||
/// </summary>
|
||||
public static UGList<TData> Create<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData
|
||||
=> new UGList<TData>(recyclerView, onItemClick);
|
||||
public static UGList<TData> Create<TData>(RecyclerView recyclerView) where TData : ISimpleViewData
|
||||
=> new UGList<TData>(recyclerView);
|
||||
|
||||
/// <summary>
|
||||
/// 创建分组列表
|
||||
/// </summary>
|
||||
public static UGGroupList<TData> CreateGroup<TData>(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null) where TData : class, IGroupViewData, new()
|
||||
=> new UGGroupList<TData>(recyclerView, groupViewName, onItemClick);
|
||||
public static UGGroupList<TData> CreateGroup<TData>(RecyclerView recyclerView, string groupViewName) where TData : class, IGroupViewData, new()
|
||||
=> new UGGroupList<TData>(recyclerView, groupViewName);
|
||||
|
||||
/// <summary>
|
||||
/// 创建循环列表
|
||||
/// </summary>
|
||||
public static UGLoopList<TData> CreateLoop<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData, new()
|
||||
=> new UGLoopList<TData>(recyclerView, onItemClick);
|
||||
public static UGLoopList<TData> CreateLoop<TData>(RecyclerView recyclerView) where TData : ISimpleViewData, new()
|
||||
=> new UGLoopList<TData>(recyclerView);
|
||||
|
||||
/// <summary>
|
||||
/// 创建混合列表
|
||||
/// </summary>
|
||||
public static UGMixedList<TData> CreateMixed<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : IMixedViewData
|
||||
=> new UGMixedList<TData>(recyclerView, onItemClick);
|
||||
public static UGMixedList<TData> CreateMixed<TData>(RecyclerView recyclerView) where TData : IMixedViewData
|
||||
=> new UGMixedList<TData>(recyclerView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
22
Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs
Normal file
22
Runtime/RecyclerView/ViewHolder/InteractiveViewHolder.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 491a09d781095104989abb7a91424008
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,20 +1,17 @@
|
||||
using System;
|
||||
using AlicizaX.UI.Extension;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AlicizaX.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 视图持有者基类,用于缓存和复用列表项视图
|
||||
/// </summary>
|
||||
public abstract class ViewHolder : MonoBehaviour
|
||||
{
|
||||
private RectTransform rectTransform;
|
||||
private Action clickAction;
|
||||
private Action pointerEnterAction;
|
||||
private Action pointerExitAction;
|
||||
|
||||
internal event Action<ViewHolder> Destroyed;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 RectTransform 组件
|
||||
/// </summary>
|
||||
public RectTransform RectTransform
|
||||
{
|
||||
get
|
||||
@ -26,83 +23,59 @@ namespace AlicizaX.UI
|
||||
|
||||
return rectTransform;
|
||||
}
|
||||
private set { rectTransform = value; }
|
||||
private set => rectTransform = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视图名称,用于区分不同类型的视图
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前绑定的数据索引
|
||||
/// </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选中状态
|
||||
/// </summary>
|
||||
public bool ChoiseState { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取视图的尺寸
|
||||
/// </summary>
|
||||
public Vector2 SizeDelta => RectTransform.sizeDelta;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 视图首次创建时调用
|
||||
/// </summary>
|
||||
protected internal virtual void OnStart()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视图被回收到对象池时调用
|
||||
/// </summary>
|
||||
protected internal virtual void OnRecycled()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定视图数据(抽象方法,子类必须实现)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="data">要绑定的数据</param>
|
||||
public abstract void BindViewData<T>(T data);
|
||||
|
||||
/// <summary>
|
||||
/// 绑定列表项点击事件
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="data">数据对象</param>
|
||||
/// <param name="action">点击回调</param>
|
||||
protected internal virtual void BindItemClick<T>(T data, Action<T> 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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 绑定选中状态
|
||||
/// </summary>
|
||||
/// <param name="state">是否选中</param>
|
||||
protected internal void BindChoiceState(bool state)
|
||||
public void ClearInteractionCallbacks()
|
||||
{
|
||||
if (ChoiseState != state)
|
||||
{
|
||||
ChoiseState = state;
|
||||
OnBindChoiceState(state);
|
||||
}
|
||||
clickAction = null;
|
||||
pointerEnterAction = null;
|
||||
pointerExitAction = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中状态改变时的回调(可在子类中重写)
|
||||
/// </summary>
|
||||
/// <param name="state">是否选中</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ namespace AlicizaX.UI
|
||||
public override void Reset()
|
||||
{
|
||||
Clear();
|
||||
(Adapter as IItemRenderCacheOwner)?.ReleaseAllItemRenders();
|
||||
objectPool.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ namespace AlicizaX.UI
|
||||
public override void Reset()
|
||||
{
|
||||
Clear();
|
||||
(Adapter as IItemRenderCacheOwner)?.ReleaseAllItemRenders();
|
||||
objectPool.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user