com.alicizax.unity.ui.exten.../Runtime/RecyclerView/Adapter/Adapter.cs

579 lines
16 KiB
C#
Raw Normal View History

2025-03-12 20:59:12 +08:00
using System;
using System.Collections.Generic;
namespace AlicizaX.UI
2025-03-12 20:59:12 +08:00
{
internal interface IItemRenderCacheOwner
2025-03-12 20:59:12 +08:00
{
void ReleaseAllItemRenders();
}
2025-03-12 20:59:12 +08:00
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;
2025-03-12 20:59:12 +08:00
protected List<T> list;
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;
2025-03-12 20:59:12 +08:00
public int ChoiceIndex
{
get => choiceIndex;
set => SetChoiceIndex(value);
2025-03-12 20:59:12 +08:00
}
2026-04-01 14:43:37 +08:00
internal Action<int> OnChoiceIndexChanged;
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>())
2025-03-12 20:59:12 +08:00
{
}
public Adapter(RecyclerView recyclerView, List<T> list)
2025-03-12 20:59:12 +08:00
{
this.recyclerView = recyclerView;
this.list = list ?? new List<T>();
2025-03-12 20:59:12 +08:00
}
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 (viewHolder == null) return;
if (!TryGetBindData(index, out var data)) return;
2025-03-12 20:59:12 +08:00
string viewName = GetViewName(index);
viewHolder.AdvanceBindingVersion();
viewHolder.DataIndex = index;
if (TryGetOrCreateItemRender(viewHolder, viewName, out var itemRender))
{
if (itemRender is ITypedItemRender<T> typedItemRender)
{
typedItemRender.BindData(data, index);
}
2026-04-03 15:21:13 +08:00
bool selected = index == choiceIndex;
itemRender.SyncSelection(selected);
return;
}
2025-03-12 20:59:12 +08:00
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))
2025-03-12 20:59:12 +08:00
{
itemRender.Unbind();
}
2025-03-12 20:59:12 +08:00
}
public virtual void NotifyDataChanged()
{
CoerceChoiceIndex();
2025-03-12 20:59:12 +08:00
recyclerView.RequestLayout();
recyclerView.Refresh();
}
public virtual void SetList(List<T> list)
2025-03-12 20:59:12 +08:00
{
this.list = list ?? new List<T>();
2025-03-12 20:59:12 +08:00
recyclerView.Reset();
NotifyDataChanged();
}
public virtual void NotifyItemChanged(int index, bool relayout = false)
{
if (index < 0 || index >= GetRealCount())
{
return;
}
CoerceChoiceIndex();
if (relayout)
{
recyclerView.RequestLayout();
recyclerView.Refresh();
return;
}
recyclerView.RebindVisibleDataIndex(index);
}
public virtual void NotifyItemRangeChanged(int index, int count, bool relayout = false)
{
if (count <= 0 || index < 0 || index >= GetRealCount())
{
return;
}
CoerceChoiceIndex();
if (relayout)
{
recyclerView.RequestLayout();
recyclerView.Refresh();
return;
}
recyclerView.RebindVisibleDataRange(index, count);
}
public virtual void NotifyItemInserted(int index)
{
CoerceChoiceIndex();
recyclerView.RequestLayout();
recyclerView.Refresh();
}
public virtual void NotifyItemRangeInserted(int index, int count)
{
if (count <= 0)
{
return;
}
CoerceChoiceIndex();
recyclerView.RequestLayout();
recyclerView.Refresh();
}
public virtual void NotifyItemRemoved(int index)
{
CoerceChoiceIndex();
recyclerView.RequestLayout();
recyclerView.Refresh();
}
public virtual void NotifyItemRangeRemoved(int index, int count)
{
if (count <= 0)
{
return;
}
CoerceChoiceIndex();
recyclerView.RequestLayout();
recyclerView.Refresh();
}
public void RegisterItemRender<TItemRender>(string viewName = "") where TItemRender : ItemRenderBase
{
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;
}
2025-03-12 20:59:12 +08:00
public T GetData(int index)
{
if (index < 0 || index >= GetItemCount()) return default;
return list[index];
}
public void Add(T item)
{
if (list == null)
{
list = new List<T>();
}
2025-03-12 20:59:12 +08:00
list.Add(item);
NotifyItemInserted(list.Count - 1);
2025-03-12 20:59:12 +08:00
}
public void AddRange(IEnumerable<T> collection)
{
if (collection == null)
{
return;
}
int startIndex = list.Count;
2025-03-12 20:59:12 +08:00
list.AddRange(collection);
if (collection is ICollection<T> itemCollection)
{
NotifyItemRangeInserted(startIndex, itemCollection.Count);
return;
}
2025-03-12 20:59:12 +08:00
NotifyDataChanged();
}
public void Insert(int index, T item)
{
list.Insert(index, item);
NotifyItemInserted(index);
2025-03-12 20:59:12 +08:00
}
public void InsertRange(int index, IEnumerable<T> collection)
{
if (collection == null)
{
return;
}
2025-03-12 20:59:12 +08:00
list.InsertRange(index, collection);
if (collection is ICollection<T> itemCollection)
{
NotifyItemRangeInserted(index, itemCollection.Count);
return;
}
2025-03-12 20:59:12 +08:00
NotifyDataChanged();
}
public void Remove(T item)
{
int index = list.IndexOf(item);
RemoveAt(index);
}
public void RemoveAt(int index)
{
if (index < 0 || index >= GetItemCount()) return;
list.RemoveAt(index);
NotifyItemRemoved(index);
2025-03-12 20:59:12 +08:00
}
public void RemoveRange(int index, int count)
{
list.RemoveRange(index, count);
NotifyItemRangeRemoved(index, count);
2025-03-12 20:59:12 +08:00
}
public void RemoveAll(Predicate<T> match)
{
list.RemoveAll(match);
NotifyDataChanged();
}
public void Clear()
{
if (list == null || list.Count == 0)
{
return;
}
int count = list.Count;
2025-03-12 20:59:12 +08:00
list.Clear();
NotifyItemRangeRemoved(0, count);
2025-03-12 20:59:12 +08:00
}
public void Reverse(int index, int count)
{
list.Reverse(index, count);
NotifyDataChanged();
}
public void Reverse()
{
list.Reverse();
NotifyDataChanged();
}
public void Sort(Comparison<T> comparison)
{
list.Sort(comparison);
NotifyDataChanged();
}
protected void SetChoiceIndex(int index)
2025-03-12 20:59:12 +08:00
{
int itemCount = GetRealCount();
if (itemCount <= 0)
{
index = -1;
}
else if (index >= itemCount)
{
index = itemCount - 1;
}
else if (index < -1)
{
index = -1;
}
if (index == choiceIndex) return;
2026-04-03 15:21:13 +08:00
int previousChoice = choiceIndex;
if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var oldHolder))
{
UpdateSelectionState(oldHolder, false);
}
choiceIndex = index;
if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var newHolder))
{
UpdateSelectionState(newHolder, true);
}
2026-04-01 14:43:37 +08:00
OnChoiceIndexChanged?.Invoke(choiceIndex);
2025-03-12 20:59:12 +08:00
}
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;
}
private bool TryGetViewHolder(int index, out ViewHolder viewHolder)
{
viewHolder = recyclerView.ViewProvider.GetViewHolderByDataIndex(index);
return viewHolder != null;
}
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 static void ReleaseItemRender(ItemRenderEntry entry)
{
if (entry?.ItemRender == null)
{
return;
}
entry.ItemRender.Unbind();
if (entry.ItemRender is ItemRenderBase itemRender)
{
itemRender.Detach();
}
}
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;
}
ReleaseItemRender(entry);
viewHolder.Destroyed -= OnViewHolderDestroyed;
itemRenders.Remove(viewHolder);
}
2025-03-12 20:59:12 +08:00
if (!TryGetItemRenderDefinition(viewName, out var definition))
2025-03-12 20:59:12 +08:00
{
itemRender = null;
return false;
}
itemRender = definition.Create(viewHolder, recyclerView, this, SetChoiceIndex);
itemRenders[viewHolder] = new ItemRenderEntry(viewName, itemRender);
viewHolder.Destroyed += OnViewHolderDestroyed;
return true;
}
void IItemRenderCacheOwner.ReleaseAllItemRenders()
{
ReleaseAllItemRenders();
}
private void ReleaseAllItemRenders()
{
foreach (var pair in itemRenders)
{
ReleaseItemRender(pair.Value);
if (pair.Key != null)
2025-03-12 20:59:12 +08:00
{
pair.Key.Destroyed -= OnViewHolderDestroyed;
2025-03-12 20:59:12 +08:00
}
}
itemRenders.Clear();
}
2025-03-12 20:59:12 +08:00
private void ReleaseCachedItemRenders(string viewName)
{
if (itemRenders.Count == 0)
2025-03-12 20:59:12 +08:00
{
return;
}
List<ViewHolder> viewHoldersToRemove = null;
foreach (var pair in itemRenders)
{
if (!string.Equals(pair.Value.ViewName, viewName, StringComparison.Ordinal))
2025-03-12 20:59:12 +08:00
{
continue;
2025-03-12 20:59:12 +08:00
}
ReleaseItemRender(pair.Value);
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]);
2025-03-12 20:59:12 +08:00
}
}
private void OnViewHolderDestroyed(ViewHolder viewHolder)
2025-03-12 20:59:12 +08:00
{
if (viewHolder == null)
{
return;
}
viewHolder.Destroyed -= OnViewHolderDestroyed;
if (itemRenders.TryGetValue(viewHolder, out var entry))
{
ReleaseItemRender(entry);
itemRenders.Remove(viewHolder);
}
}
private void CoerceChoiceIndex()
{
int itemCount = GetRealCount();
if (itemCount <= 0)
{
SetChoiceIndex(-1);
return;
}
if (choiceIndex >= itemCount)
{
SetChoiceIndex(itemCount - 1);
}
2025-03-12 20:59:12 +08:00
}
}
}