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

266 lines
7.9 KiB
C#
Raw Normal View History

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();
}
2026-03-30 15:40:00 +08:00
internal interface IItemRenderInitializer
{
void Reset(ViewHolder viewHolder);
}
public abstract class ItemRender<TData, THolder> : IItemRender
2026-03-30 15:40:00 +08:00
, IItemRenderInitializer
where THolder : ViewHolder
{
private Action defaultClickAction;
2026-03-30 15:40:00 +08:00
protected THolder Holder { get; private set; }
protected TData CurrentData { get; private set; }
2026-03-30 15:40:00 +08:00
protected int CurrentIndex { get; private set; } = -1;
protected bool IsSelected { get; private set; }
public void Bind(object data, int index, Action defaultClickAction)
{
2026-03-30 15:40:00 +08:00
EnsureHolder();
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)
{
2026-03-30 15:40:00 +08:00
EnsureHolder();
IsSelected = selected;
OnSelectionChanged(selected);
}
public void Unbind()
{
2026-03-30 15:40:00 +08:00
ResetState();
}
protected abstract void Bind(TData data, int index);
2026-03-30 15:40:00 +08:00
protected virtual void OnHolderChanged()
{
}
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();
}
2026-03-30 15:40:00 +08:00
void IItemRenderInitializer.Reset(ViewHolder viewHolder)
{
if (viewHolder == null)
{
throw new ArgumentNullException(nameof(viewHolder));
}
if (viewHolder is not THolder holder)
{
throw new InvalidOperationException(
$"RecyclerView item render '{GetType().FullName}' expects holder '{typeof(THolder).FullName}', but got '{viewHolder.GetType().FullName}'.");
}
ResetState();
Holder = holder;
OnHolderChanged();
}
private void EnsureHolder()
{
if (Holder == null)
{
throw new InvalidOperationException(
$"RecyclerView item render '{GetType().FullName}' has not been initialized with a holder.");
}
}
private void ResetState()
{
if (Holder != null)
{
if (IsSelected)
{
IsSelected = false;
OnSelectionChanged(false);
}
OnClear();
Holder.ClearInteractionCallbacks();
}
CurrentData = default;
CurrentIndex = -1;
IsSelected = false;
defaultClickAction = null;
}
}
internal static class ItemRenderResolver
{
internal sealed class ItemRenderDefinition
{
2026-03-30 15:40:00 +08:00
public ItemRenderDefinition(Type itemRenderType, Type holderType)
{
ItemRenderType = itemRenderType;
HolderType = holderType;
}
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}'.");
}
2026-03-30 15:40:00 +08:00
if (Activator.CreateInstance(ItemRenderType, true) is not IItemRender itemRender)
{
throw new InvalidOperationException(
$"RecyclerView item render '{ItemRenderType.FullName}' could not be created.");
}
if (itemRender is not IItemRenderInitializer initializer)
{
throw new InvalidOperationException(
$"RecyclerView item render '{ItemRenderType.FullName}' must inherit from ItemRender<TData, THolder>.");
}
initializer.Reset(viewHolder);
return itemRender;
}
}
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,
2026-03-30 15:40:00 +08:00
Type.EmptyTypes,
null);
if (constructor == null)
{
throw new InvalidOperationException(
2026-03-30 15:40:00 +08:00
$"RecyclerView item render '{itemRenderType.FullName}' must have a parameterless constructor.");
}
2026-03-30 15:40:00 +08:00
return new ItemRenderDefinition(itemRenderType, holderType);
}
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;
}
}
}