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(); } } }