2025-03-12 20:59:12 +08:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
2026-03-31 15:18:50 +08:00
|
|
|
using UnityEngine.EventSystems;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
2025-11-20 15:40:38 +08:00
|
|
|
namespace AlicizaX.UI
|
2025-03-12 20:59:12 +08:00
|
|
|
{
|
2025-12-26 14:22:46 +08:00
|
|
|
public abstract class ViewProvider
|
2025-03-12 20:59:12 +08:00
|
|
|
{
|
|
|
|
|
private readonly List<ViewHolder> viewHolders = new();
|
2026-03-31 15:18:50 +08:00
|
|
|
private readonly Dictionary<int, ViewHolder> viewHoldersByIndex = new();
|
|
|
|
|
private readonly Dictionary<int, List<ViewHolder>> viewHoldersByDataIndex = new();
|
|
|
|
|
private readonly Dictionary<int, int> viewHolderPositions = new();
|
2025-03-12 20:59:12 +08:00
|
|
|
|
|
|
|
|
public IAdapter Adapter { get; set; }
|
2026-03-11 14:18:07 +08:00
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
public LayoutManager LayoutManager { get; set; }
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
public IReadOnlyList<ViewHolder> ViewHolders => viewHolders;
|
|
|
|
|
|
|
|
|
|
public abstract string PoolStats { get; }
|
2025-03-12 20:59:12 +08:00
|
|
|
|
|
|
|
|
protected RecyclerView recyclerView;
|
|
|
|
|
protected ViewHolder[] templates;
|
|
|
|
|
|
|
|
|
|
public ViewProvider(RecyclerView recyclerView, ViewHolder[] templates)
|
|
|
|
|
{
|
|
|
|
|
this.recyclerView = recyclerView;
|
|
|
|
|
this.templates = templates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract ViewHolder GetTemplate(string viewName);
|
|
|
|
|
|
|
|
|
|
public abstract ViewHolder[] GetTemplates();
|
|
|
|
|
|
|
|
|
|
public abstract ViewHolder Allocate(string viewName);
|
|
|
|
|
|
|
|
|
|
public abstract void Free(string viewName, ViewHolder viewHolder);
|
|
|
|
|
|
|
|
|
|
public abstract void Reset();
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
public abstract void PreparePool();
|
|
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
public void CreateViewHolder(int index)
|
|
|
|
|
{
|
|
|
|
|
for (int i = index; i < index + LayoutManager.Unit; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i > Adapter.GetItemCount() - 1) break;
|
|
|
|
|
|
|
|
|
|
string viewName = Adapter.GetViewName(i);
|
|
|
|
|
var viewHolder = Allocate(viewName);
|
|
|
|
|
viewHolder.Name = viewName;
|
|
|
|
|
viewHolder.Index = i;
|
2026-03-31 15:18:50 +08:00
|
|
|
viewHolder.DataIndex = i;
|
|
|
|
|
viewHolder.RecyclerView = recyclerView;
|
2025-03-12 20:59:12 +08:00
|
|
|
viewHolders.Add(viewHolder);
|
2026-03-31 15:18:50 +08:00
|
|
|
RegisterViewHolder(viewHolder);
|
2025-12-26 14:22:46 +08:00
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
LayoutManager.Layout(viewHolder, i);
|
|
|
|
|
Adapter.OnBindViewHolder(viewHolder, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RemoveViewHolder(int index)
|
|
|
|
|
{
|
|
|
|
|
for (int i = index; i < index + LayoutManager.Unit; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i > Adapter.GetItemCount() - 1) break;
|
|
|
|
|
|
|
|
|
|
int viewHolderIndex = GetViewHolderIndex(i);
|
|
|
|
|
|
|
|
|
|
if (viewHolderIndex < 0 || viewHolderIndex >= viewHolders.Count) return;
|
|
|
|
|
|
|
|
|
|
var viewHolder = viewHolders[viewHolderIndex];
|
2026-03-31 15:18:50 +08:00
|
|
|
string viewName = viewHolder.Name;
|
2025-03-12 20:59:12 +08:00
|
|
|
viewHolders.RemoveAt(viewHolderIndex);
|
2026-03-31 15:18:50 +08:00
|
|
|
UnregisterViewHolder(viewHolder);
|
|
|
|
|
RebuildViewHolderPositions(viewHolderIndex);
|
2026-03-27 18:38:29 +08:00
|
|
|
Adapter?.OnRecycleViewHolder(viewHolder);
|
2025-05-30 13:43:08 +08:00
|
|
|
viewHolder.OnRecycled();
|
2026-03-31 15:18:50 +08:00
|
|
|
ClearSelectedState(viewHolder);
|
|
|
|
|
Free(viewName, viewHolder);
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ViewHolder GetViewHolder(int index)
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
return viewHoldersByIndex.TryGetValue(index, out ViewHolder viewHolder)
|
|
|
|
|
? viewHolder
|
|
|
|
|
: null;
|
|
|
|
|
}
|
2025-04-01 15:21:02 +08:00
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
public ViewHolder GetViewHolderByDataIndex(int dataIndex)
|
|
|
|
|
{
|
|
|
|
|
return viewHoldersByDataIndex.TryGetValue(dataIndex, out List<ViewHolder> holders) &&
|
|
|
|
|
holders is { Count: > 0 }
|
|
|
|
|
? holders[0]
|
|
|
|
|
: null;
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
public bool TryGetViewHoldersByDataIndex(int dataIndex, out IReadOnlyList<ViewHolder> holders)
|
2025-03-12 20:59:12 +08:00
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
if (viewHoldersByDataIndex.TryGetValue(dataIndex, out List<ViewHolder> list) && list.Count > 0)
|
2025-03-12 20:59:12 +08:00
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
holders = list;
|
|
|
|
|
return true;
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
2025-04-01 15:21:02 +08:00
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
holders = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int GetViewHolderIndex(int index)
|
|
|
|
|
{
|
|
|
|
|
return viewHolderPositions.TryGetValue(index, out int viewHolderIndex)
|
|
|
|
|
? viewHolderIndex
|
|
|
|
|
: -1;
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
foreach (var viewHolder in viewHolders)
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
string viewName = viewHolder.Name;
|
2026-03-27 18:38:29 +08:00
|
|
|
Adapter?.OnRecycleViewHolder(viewHolder);
|
2026-03-31 15:18:50 +08:00
|
|
|
UnregisterViewHolder(viewHolder);
|
2026-03-27 18:38:29 +08:00
|
|
|
viewHolder.OnRecycled();
|
2026-03-31 15:18:50 +08:00
|
|
|
ClearSelectedState(viewHolder);
|
|
|
|
|
Free(viewName, viewHolder);
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
2025-04-01 15:21:02 +08:00
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
viewHolders.Clear();
|
2026-03-31 15:18:50 +08:00
|
|
|
viewHoldersByIndex.Clear();
|
|
|
|
|
viewHoldersByDataIndex.Clear();
|
|
|
|
|
viewHolderPositions.Clear();
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Vector2 CalculateViewSize(int index)
|
|
|
|
|
{
|
|
|
|
|
Vector2 size = GetTemplate(Adapter.GetViewName(index)).SizeDelta;
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int GetItemCount()
|
|
|
|
|
{
|
|
|
|
|
return Adapter == null ? 0 : Adapter.GetItemCount();
|
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
protected int GetRecommendedWarmCount()
|
|
|
|
|
{
|
|
|
|
|
if (Adapter == null || LayoutManager == null)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int itemCount = Adapter.GetItemCount();
|
|
|
|
|
if (itemCount <= 0)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int start = Mathf.Max(0, LayoutManager.GetStartIndex());
|
|
|
|
|
int end = Mathf.Max(start, LayoutManager.GetEndIndex());
|
|
|
|
|
int visibleCount = end - start + 1;
|
|
|
|
|
int bufferCount = Mathf.Max(1, LayoutManager.Unit);
|
|
|
|
|
return Mathf.Min(itemCount, visibleCount + bufferCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RegisterViewHolder(ViewHolder viewHolder)
|
|
|
|
|
{
|
|
|
|
|
if (viewHolder == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewHoldersByIndex[viewHolder.Index] = viewHolder;
|
|
|
|
|
viewHolderPositions[viewHolder.Index] = viewHolders.Count - 1;
|
|
|
|
|
|
|
|
|
|
if (!viewHoldersByDataIndex.TryGetValue(viewHolder.DataIndex, out List<ViewHolder> holders))
|
|
|
|
|
{
|
2026-04-15 14:51:57 +08:00
|
|
|
holders = new List<ViewHolder>(1);
|
2026-03-31 15:18:50 +08:00
|
|
|
viewHoldersByDataIndex[viewHolder.DataIndex] = holders;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
holders.Add(viewHolder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UnregisterViewHolder(ViewHolder viewHolder)
|
|
|
|
|
{
|
|
|
|
|
if (viewHolder == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewHoldersByIndex.Remove(viewHolder.Index);
|
|
|
|
|
viewHolderPositions.Remove(viewHolder.Index);
|
|
|
|
|
|
|
|
|
|
if (!viewHoldersByDataIndex.TryGetValue(viewHolder.DataIndex, out List<ViewHolder> holders))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:51:57 +08:00
|
|
|
// 用末尾元素覆盖目标项再移除末位,避免 List.Remove 的线性搜索+内存搬移。
|
|
|
|
|
int idx = holders.LastIndexOf(viewHolder);
|
|
|
|
|
if (idx >= 0)
|
|
|
|
|
{
|
|
|
|
|
holders[idx] = holders[holders.Count - 1];
|
|
|
|
|
holders.RemoveAt(holders.Count - 1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
if (holders.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
viewHoldersByDataIndex.Remove(viewHolder.DataIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RebuildViewHolderPositions(int startIndex)
|
|
|
|
|
{
|
|
|
|
|
for (int i = startIndex; i < viewHolders.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
ViewHolder holder = viewHolders[i];
|
|
|
|
|
if (holder != null)
|
|
|
|
|
{
|
|
|
|
|
viewHolderPositions[holder.Index] = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ClearSelectedState(ViewHolder viewHolder)
|
|
|
|
|
{
|
|
|
|
|
if (viewHolder == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventSystem eventSystem = EventSystem.current;
|
|
|
|
|
if (eventSystem == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameObject selected = eventSystem.currentSelectedGameObject;
|
|
|
|
|
if (selected != null && selected.transform.IsChildOf(viewHolder.transform))
|
|
|
|
|
{
|
|
|
|
|
eventSystem.SetSelectedGameObject(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-12 20:59:12 +08:00
|
|
|
}
|
|
|
|
|
}
|