204 lines
6.1 KiB
C#
204 lines
6.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|