using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using UnityEngine.EventSystems;
namespace AlicizaX.UI
{
///
/// 定义 ItemRender 的基础绑定与解绑协议。
///
internal interface IItemRender
{
///
/// 将指定数据绑定到当前渲染实例。
///
/// 待绑定的数据对象。
/// 当前数据索引。
void Bind(object data, int index);
///
/// 更新当前渲染实例的选中状态。
///
/// 是否处于选中状态。
void UpdateSelection(bool selected);
///
/// 清理当前渲染实例上的绑定状态。
///
void Unbind();
}
///
/// 定义带强类型数据绑定能力的 ItemRender 协议。
///
/// 列表数据类型。
internal interface ITypedItemRender : IItemRender
{
///
/// 使用强类型数据执行绑定。
///
/// 待绑定的数据对象。
/// 当前数据索引。
void BindData(TData data, int index);
}
///
/// 提供 ItemRender 的公共基类,封装框架内部的绑定生命周期入口。
///
public abstract class ItemRenderBase : IItemRender
{
///
/// 将渲染实例附加到指定的视图持有者。
///
/// 目标视图持有者。
/// 所属的 RecyclerView。
/// 当前使用的适配器。
/// 选中项变更回调。
internal abstract void Attach(ViewHolder viewHolder, RecyclerView recyclerView, IAdapter adapter, Action selectionHandler);
///
/// 将渲染实例从当前视图持有者上分离。
///
internal abstract void Detach();
///
/// 以对象形式绑定数据。
///
/// 待绑定的数据对象。
/// 当前数据索引。
internal abstract void BindObject(object data, int index);
///
/// 更新内部记录的选中状态。
///
/// 是否处于选中状态。
internal abstract void UpdateSelectionInternal(bool selected);
///
/// 清理当前绑定产生的临时状态。
///
internal abstract void UnbindInternal();
///
/// 由框架内部调用,将对象数据绑定到当前渲染实例。
///
/// 待绑定的数据对象。
/// 当前数据索引。
void IItemRender.Bind(object data, int index)
{
BindObject(data, index);
}
///
/// 由框架内部调用,更新当前渲染实例的选中状态。
///
/// 是否处于选中状态。
void IItemRender.UpdateSelection(bool selected)
{
UpdateSelectionInternal(selected);
}
///
/// 由框架内部调用,清理当前渲染实例的绑定状态。
///
void IItemRender.Unbind()
{
UnbindInternal();
}
}
///
/// 提供带强类型数据与视图持有者的列表项渲染基类。
///
/// 列表数据类型。
/// 视图持有者类型。
public abstract class ItemRender : ItemRenderBase, IItemInteractionHost, ITypedItemRender
where THolder : ViewHolder
{
///
/// 当前持有者上的交互代理组件。
///
private ItemInteractionProxy interactionProxy;
///
/// 当前项被选中时的回调委托。
///
private Action selectionHandler;
///
/// 上一次绑定到交互代理的交互标记。
///
private ItemInteractionFlags cachedInteractionFlags;
///
/// 标记交互代理是否已经完成当前配置绑定。
///
private bool interactionBindingActive;
///
/// 获取当前附加的强类型视图持有者。
///
private THolder Holder { get; set; }
protected THolder baseui
{
get => Holder;
}
///
/// 获取当前所属的 RecyclerView。
///
protected RecyclerView RecyclerView { get; private set; }
///
/// 获取当前所属的适配器。
///
protected IAdapter Adapter { get; private set; }
///
/// 获取当前绑定的数据对象。
///
protected TData CurrentData { get; private set; }
///
/// 获取当前绑定的数据索引。
///
protected int CurrentIndex { get; private set; } = -1;
///
/// 获取当前绑定的布局索引。
///
protected int CurrentLayoutIndex { get; private set; } = -1;
///
/// 获取当前项是否处于选中状态。
///
protected bool IsSelected { get; private set; }
///
/// 获取当前绑定版本号,用于校验异步回调是否仍然有效。
///
protected uint CurrentBindingVersion { get; private set; }
///
/// 获取当前渲染项支持的交互能力。
///
public virtual ItemInteractionFlags InteractionFlags => ItemInteractionFlags.None;
///
/// 由框架交互代理读取当前渲染项的交互能力。
///
ItemInteractionFlags IItemInteractionHost.InteractionFlags => InteractionFlags;
///
/// 获取键盘或手柄导航时采用的移动选项。
///
protected virtual RecyclerNavigationOptions NavigationOptions => RecyclerNavigationOptions.Circular;
///
/// 以对象形式绑定数据并执行强类型校验。
///
/// 待绑定的数据对象。
/// 当前数据索引。
internal override void BindObject(object data, int index)
{
if (data is not TData itemData)
{
throw new InvalidCastException(
$"ItemRender '{GetType().Name}' expected data '{typeof(TData).Name}', but got '{data?.GetType().Name ?? "null"}'.");
}
BindCore(itemData, index);
}
///
/// 由框架内部调用,使用强类型数据执行绑定。
///
/// 待绑定的数据对象。
/// 当前数据索引。
void ITypedItemRender.BindData(TData data, int index)
{
BindCore(data, index);
}
///
/// 更新内部选中状态并触发选中状态回调。
///
/// 是否处于选中状态。
internal override void UpdateSelectionInternal(bool selected)
{
EnsureHolder();
IsSelected = selected;
OnSelectionChanged(selected);
}
///
/// 清理当前绑定数据关联的状态,并重置内部缓存。
///
internal override void UnbindInternal()
{
if (Holder != null)
{
if (IsSelected)
{
IsSelected = false;
OnSelectionChanged(false);
}
OnClear();
if (interactionProxy != null)
{
interactionProxy.Clear();
interactionBindingActive = false;
cachedInteractionFlags = ItemInteractionFlags.None;
}
Holder.DataIndex = -1;
}
CurrentData = default;
CurrentIndex = -1;
CurrentLayoutIndex = -1;
CurrentBindingVersion = 0;
IsSelected = false;
}
///
/// 判断指定绑定版本是否仍与当前持有者保持一致。
///
/// 待校验的绑定版本号。
/// 版本号仍然有效时返回 ;否则返回 。
protected bool IsBindingCurrent(uint bindingVersion)
{
return Holder != null &&
CurrentBindingVersion != 0 &&
CurrentBindingVersion == bindingVersion &&
Holder.BindingVersion == bindingVersion;
}
///
/// 执行一次完整的数据绑定流程。
///
/// 待绑定的强类型数据。
/// 当前数据索引。
private void BindCore(TData itemData, int index)
{
EnsureHolder();
CurrentData = itemData;
CurrentIndex = index;
CurrentLayoutIndex = Holder.Index;
Holder.DataIndex = index;
CurrentBindingVersion = Holder.BindingVersion;
BindInteractionProxyIfNeeded();
OnBind(itemData, index);
}
///
/// 每次当前持有者绑定到新的数据项时调用。
/// 仅用于执行数据驱动的界面刷新,例如文本、图片与状态更新。
/// 不要在此注册持有者级别的事件监听。
///
/// 当前绑定的数据对象。
/// 当前数据索引。
protected abstract void OnBind(TData data, int index);
///
/// 当当前渲染实例附加到持有者实例时调用。
/// 这是持有者级生命周期,通常对同一组 render 与 holder 仅触发一次。
/// 适合执行一次性的持有者初始化,例如注册按钮监听或挂接可复用交互组件。
///
protected virtual void OnHolderAttached()
{
interactionProxy = Holder.GetComponent();
if (interactionProxy == null)
{
interactionProxy = Holder.gameObject.AddComponent();
}
}
///
/// 当当前渲染实例即将从持有者实例分离时调用。
/// 这是持有者级清理生命周期,通常对同一组 render 与 holder 仅触发一次。
/// 适合执行一次性的持有者清理,例如注销按钮监听或释放附加阶段缓存的引用。
///
protected virtual void OnHolderDetached()
{
}
///
/// 当当前项的选中状态发生变化时调用。
/// 仅应在此更新选中态相关的界面表现。
///
/// 当前是否处于选中状态。
protected virtual void OnSelectionChanged(bool selected)
{
}
///
/// 每次当前数据绑定被清理时调用。
/// 这是绑定级清理生命周期,在复用过程中可能被多次触发。
/// 适合在此重置由当前绑定数据产生的临时界面状态。
///
protected virtual void OnClear()
{
}
///
/// 通知外部选中当前数据项。
///
private void SelectCurrentItem()
{
if (CurrentIndex >= 0)
{
selectionHandler?.Invoke(CurrentIndex);
}
}
///
/// 将当前渲染实例附加到指定持有者,并初始化上下文引用。
///
/// 目标视图持有者。
/// 所属的 RecyclerView。
/// 当前使用的适配器。
/// 选中项变更回调。
internal override void Attach(ViewHolder viewHolder, RecyclerView recyclerView, IAdapter adapter, Action selectionHandler)
{
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}'.");
}
Holder = holder;
RecyclerView = recyclerView;
Adapter = adapter;
this.selectionHandler = selectionHandler;
interactionBindingActive = false;
cachedInteractionFlags = ItemInteractionFlags.None;
OnHolderAttached();
}
///
/// 将当前渲染实例从持有者上分离,并释放上下文引用。
///
internal override void Detach()
{
if (Holder == null)
{
return;
}
OnHolderDetached();
interactionProxy?.Clear();
interactionProxy = null;
selectionHandler = null;
Holder = null;
RecyclerView = null;
Adapter = null;
CurrentData = default;
CurrentIndex = -1;
CurrentLayoutIndex = -1;
CurrentBindingVersion = 0;
IsSelected = false;
interactionBindingActive = false;
cachedInteractionFlags = ItemInteractionFlags.None;
}
///
/// 确保当前渲染实例已经绑定有效的视图持有者。
///
private void EnsureHolder()
{
if (Holder == null)
{
throw new InvalidOperationException(
$"RecyclerView item render '{GetType().FullName}' has not been initialized with a holder.");
}
}
///
/// 按指定方向尝试移动焦点。
///
/// 焦点移动方向。
/// 成功移动焦点时返回 ;否则返回 。
private bool MoveFocus(MoveDirection direction)
{
return RecyclerView != null && RecyclerView.NavigationController.TryMove(Holder, direction, NavigationOptions);
}
///
/// 在需要时将当前渲染实例绑定到交互代理。
///
private void BindInteractionProxyIfNeeded()
{
if (interactionProxy == null)
{
return;
}
ItemInteractionFlags interactionFlags = InteractionFlags;
if (interactionBindingActive && cachedInteractionFlags == interactionFlags)
{
return;
}
interactionProxy.Bind(this);
cachedInteractionFlags = interactionFlags;
interactionBindingActive = true;
}
///
/// 由交互代理转发点击事件。
///
/// 点击事件数据。
void IItemInteractionHost.HandlePointerClick(PointerEventData eventData)
{
SelectCurrentItem();
OnPointerClick(eventData);
}
///
/// 由交互代理转发指针进入事件。
///
/// 指针事件数据。
void IItemInteractionHost.HandlePointerEnter(PointerEventData eventData)
{
OnPointerEnter(eventData);
}
///
/// 由交互代理转发指针离开事件。
///
/// 指针事件数据。
void IItemInteractionHost.HandlePointerExit(PointerEventData eventData)
{
OnPointerExit(eventData);
}
///
/// 由交互代理转发选中事件。
///
/// 选中事件数据。
void IItemInteractionHost.HandleSelect(BaseEventData eventData)
{
OnItemSelected(eventData);
}
///
/// 由交互代理转发取消选中事件。
///
/// 取消选中事件数据。
void IItemInteractionHost.HandleDeselect(BaseEventData eventData)
{
OnItemDeselected(eventData);
}
///
/// 由交互代理转发导航移动事件。
///
/// 导航事件数据。
void IItemInteractionHost.HandleMove(AxisEventData eventData)
{
if (!OnMove(eventData))
{
MoveFocus(eventData.moveDir);
}
}
///
/// 由交互代理转发开始拖拽事件。
///
/// 拖拽事件数据。
void IItemInteractionHost.HandleBeginDrag(PointerEventData eventData)
{
OnBeginDrag(eventData);
}
///
/// 由交互代理转发拖拽事件。
///
/// 拖拽事件数据。
void IItemInteractionHost.HandleDrag(PointerEventData eventData)
{
OnDrag(eventData);
}
///
/// 由交互代理转发结束拖拽事件。
///
/// 拖拽事件数据。
void IItemInteractionHost.HandleEndDrag(PointerEventData eventData)
{
OnEndDrag(eventData);
}
///
/// 由交互代理转发提交事件。
///
/// 提交事件数据。
void IItemInteractionHost.HandleSubmit(BaseEventData eventData)
{
SelectCurrentItem();
OnSubmit(eventData);
}
///
/// 由交互代理转发取消事件。
///
/// 取消事件数据。
void IItemInteractionHost.HandleCancel(BaseEventData eventData)
{
OnCancel(eventData);
}
///
/// 当当前项收到点击事件时调用。
///
/// 点击事件数据。
protected virtual void OnPointerClick(PointerEventData eventData)
{
}
///
/// 当指针进入当前项时调用。
///
/// 指针事件数据。
protected virtual void OnPointerEnter(PointerEventData eventData)
{
}
///
/// 当指针离开当前项时调用。
///
/// 指针事件数据。
protected virtual void OnPointerExit(PointerEventData eventData)
{
}
///
/// 当当前项被 EventSystem 选中时调用。
///
/// 选中事件数据。
protected virtual void OnItemSelected(BaseEventData eventData)
{
}
///
/// 当当前项被 EventSystem 取消选中时调用。
///
/// 取消选中事件数据。
protected virtual void OnItemDeselected(BaseEventData eventData)
{
}
///
/// 当当前项收到导航移动事件时调用。
///
/// 导航事件数据。
/// 已自行处理导航事件时返回 ;否则返回 。
protected virtual bool OnMove(AxisEventData eventData)
{
return false;
}
///
/// 当当前项开始被拖拽时调用。
///
/// 拖拽事件数据。
protected virtual void OnBeginDrag(PointerEventData eventData)
{
}
///
/// 当当前项发生拖拽时调用。
///
/// 拖拽事件数据。
protected virtual void OnDrag(PointerEventData eventData)
{
}
///
/// 当当前项结束拖拽时调用。
///
/// 拖拽事件数据。
protected virtual void OnEndDrag(PointerEventData eventData)
{
}
///
/// 当当前项收到提交操作时调用。
///
/// 提交事件数据。
protected virtual void OnSubmit(BaseEventData eventData)
{
}
///
/// 当当前项收到取消操作时调用。
///
/// 取消事件数据。
protected virtual void OnCancel(BaseEventData eventData)
{
}
}
///
/// 负责解析、缓存并创建 ItemRender 定义。
///
internal static class ItemRenderResolver
{
///
/// 描述单个 ItemRender 的类型信息与创建方式。
///
internal sealed class ItemRenderDefinition
{
///
/// 初始化一份 ItemRender 定义。
///
/// 渲染器运行时类型。
/// 对应的持有者类型。
/// 渲染器实例创建委托。
public ItemRenderDefinition(Type itemRenderType, Type holderType, Func createInstance)
{
ItemRenderType = itemRenderType;
HolderType = holderType;
this.createInstance = createInstance;
}
///
/// 获取渲染器运行时类型。
///
public Type ItemRenderType { get; }
///
/// 获取渲染器要求的持有者类型。
///
public Type HolderType { get; }
///
/// 用于创建渲染器实例的缓存委托。
///
private readonly Func createInstance;
///
/// 创建并初始化一个可用的 ItemRender 实例。
///
/// 目标视图持有者。
/// 所属的 RecyclerView。
/// 当前使用的适配器。
/// 选中项变更回调。
/// 已初始化完成的渲染器实例。
public IItemRender Create(ViewHolder viewHolder, RecyclerView recyclerView, IAdapter adapter, Action selectionHandler)
{
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}'.");
}
if (createInstance() is not ItemRenderBase itemRender)
{
throw new InvalidOperationException(
$"RecyclerView item render '{ItemRenderType.FullName}' could not be created.");
}
itemRender.Attach(viewHolder, recyclerView, adapter, selectionHandler);
return itemRender;
}
}
///
/// ItemRender 定义缓存表,键为渲染器类型。
///
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,
Type.EmptyTypes,
null);
if (constructor == null)
{
throw new InvalidOperationException(
$"RecyclerView item render '{itemRenderType.FullName}' must have a parameterless constructor.");
}
return new ItemRenderDefinition(itemRenderType, holderType, CreateFactory(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;
}
///
/// 基于无参构造函数创建渲染器实例工厂。
///
/// 渲染器的无参构造函数。
/// 用于创建渲染器实例的委托。
private static Func CreateFactory(ConstructorInfo constructor)
{
NewExpression newExpression = Expression.New(constructor);
UnaryExpression convertExpression = Expression.Convert(newExpression, typeof(IItemRender));
return Expression.Lambda>(convertExpression).Compile();
}
}
}