对RecycleclerView 无限循环列表进行优化 扩展 基础封装

This commit is contained in:
陈思海 2025-11-20 15:40:38 +08:00
parent 850dfb7af6
commit 94f9112465
30 changed files with 304 additions and 83 deletions

View File

@ -3,7 +3,7 @@ using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.UI.RecyclerView;
using AlicizaX.UI;
using UnityEngine.UI;
using Object = UnityEngine.Object;
@ -156,6 +156,7 @@ public class RecyclerViewEditor : Editor
{
_selectedLayoutIndex = newIndex; // 立即更新索引
UpdateLayoutManager(newIndex);
serializedObject.ApplyModifiedProperties();
}
}
@ -243,6 +244,7 @@ public class RecyclerViewEditor : Editor
if (newIndex != _selectedScrollerIndex)
{
UpdateScroller(newIndex);
serializedObject.ApplyModifiedProperties();
}
}
@ -295,7 +297,123 @@ public class RecyclerViewEditor : Editor
void DrawTemplatesSection()
{
EditorGUILayout.Space(5);
EditorGUILayout.PropertyField(templates, new GUIContent("Item Templates"), true);
EditorGUILayout.LabelField("Item Templates", EditorStyles.boldLabel);
DrawTemplatesList();
DrawDragAndDropArea();
}
void DrawTemplatesList()
{
if (templates == null || !templates.isArray) return;
serializedObject.Update();
for (int i = 0; i < templates.arraySize; i++)
{
DrawTemplateItem(i);
}
}
void DrawTemplateItem(int index)
{
if (index < 0 || index >= templates.arraySize) return;
SerializedProperty item = templates.GetArrayElementAtIndex(index);
EditorGUILayout.BeginHorizontal();
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(item, GUIContent.none, true);
EditorGUI.EndDisabledGroup();
if (GUILayout.Button("×", GUILayout.Width(20), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
{
RemoveTemplateItem(index);
return;
}
}
EditorGUILayout.EndHorizontal();
}
void RemoveTemplateItem(int index)
{
if (templates == null || index < 0 || index >= templates.arraySize) return;
templates.DeleteArrayElementAtIndex(index);
serializedObject.ApplyModifiedProperties();
GUIUtility.ExitGUI();
}
void DrawDragAndDropArea()
{
Rect dropArea = GUILayoutUtility.GetRect(0f, 50f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "Drag Template to here", EditorStyles.helpBox);
// 处理拖拽事件
HandleDragAndDrop(dropArea);
}
void HandleDragAndDrop(Rect dropArea)
{
Event evt = Event.current;
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!dropArea.Contains(evt.mousePosition))
return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
AddTemplatesFromDrag();
evt.Use();
}
break;
}
}
void AddTemplatesFromDrag()
{
foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences)
{
if (draggedObject is GameObject gameObject && gameObject.GetComponent<ViewHolder>() != null)
{
AddTemplate(gameObject);
}
else
{
Debug.LogWarning("模板必须为GameObject资源 同时挂载继承ViewHolder脚本!");
}
}
}
void AddTemplate(GameObject templatePrefab)
{
if (templates == null) return;
for (int i = 0; i < templates.arraySize; i++)
{
SerializedProperty existingItem = templates.GetArrayElementAtIndex(i);
var refrence = (ViewHolder)existingItem.objectReferenceValue;
if (refrence.GetType() != templatePrefab.GetType())
{
Debug.LogWarning("列表元素重复项,禁止再次添加!");
return;
}
}
templates.arraySize++;
SerializedProperty newItem = templates.GetArrayElementAtIndex(templates.arraySize - 1);
newItem.objectReferenceValue = templatePrefab;
serializedObject.ApplyModifiedProperties();
}
#endregion

View File

@ -102,16 +102,17 @@ MonoBehaviour:
templates: []
_scrollerTypeName: AlicizaX.UI.RecyclerView.Scroller
_scroller: {fileID: 144409482669617178}
_layoutManagerTypeName: AlicizaX.UI.RecyclerView.LinearLayoutManager
_showScrollBar: 0
_scrollbar: {fileID: 0}
_layoutManagerTypeName: AlicizaX.UI.LinearLayoutManager
_layoutManager:
rid: 6528754475160043629
rid: 7492395943315111994
references:
version: 2
RefIds:
- rid: 6528754475160043629
type: {class: LinearLayoutManager, ns: AlicizaX.UI.RecyclerView, asm: AlicizaX.UI.Extension}
data:
testValue: 0
- rid: 7492395943315111994
type: {class: LinearLayoutManager, ns: AlicizaX.UI, asm: AlicizaX.UI.Extension}
data:
--- !u!114 &144409482669617178
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class Adapter<T> : IAdapter
public class Adapter<T> : IAdapter where T : ISimpleViewData
{
protected RecyclerView recyclerView;
@ -11,13 +11,11 @@ namespace AlicizaX.UI.RecyclerView
protected Action<T> onItemClick;
protected int choiceIndex = -1;
public int ChoiceIndex
{
get => choiceIndex;
set
{
SetChoiceIndex(value);
}
set { SetChoiceIndex(value); }
}
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>(), null)
@ -71,7 +69,7 @@ namespace AlicizaX.UI.RecyclerView
recyclerView.Refresh();
}
public virtual void SetList(List<T> list)
protected internal virtual void SetList(List<T> list)
{
this.list = list;
recyclerView.Reset();

View File

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class GroupAdapter : Adapter<GroupData>
public class GroupAdapter<TData> : Adapter<TData> where TData : IGroupViewData, new()
{
private readonly List<GroupData> showList = new();
private readonly List<TData> showList = new();
private string groupViewName;
public GroupAdapter(RecyclerView recyclerView, string groupViewName) : base(recyclerView)
@ -13,14 +13,19 @@ namespace AlicizaX.UI.RecyclerView
this.groupViewName = groupViewName;
}
public GroupAdapter(RecyclerView recyclerView, List<GroupData> list) : base(recyclerView, list)
public GroupAdapter(RecyclerView recyclerView, List<TData> list) : base(recyclerView, list)
{
}
public GroupAdapter(RecyclerView recyclerView, List<GroupData> list, Action<GroupData> onItemClick) : base(recyclerView, list, onItemClick)
public GroupAdapter(RecyclerView recyclerView, List<TData> list, Action<TData> onItemClick) : base(recyclerView, list, onItemClick)
{
}
public GroupAdapter(RecyclerView recyclerView) : base(recyclerView)
{
}
public override int GetItemCount()
{
return showList.Count;
@ -28,21 +33,21 @@ namespace AlicizaX.UI.RecyclerView
public override string GetViewName(int index)
{
return showList[index].viewName;
return showList[index].TemplateName;
}
public override void OnBindViewHolder(ViewHolder viewHolder, int index)
{
if (index < 0 || index >= GetItemCount()) return;
GroupData data = showList[index];
TData data = showList[index];
viewHolder.BindViewData(data);
viewHolder.BindItemClick(data, t =>
{
if (data.viewName == groupViewName)
if (data.TemplateName == groupViewName)
{
data.bExpand = !data.bExpand;
data.Expanded = !data.Expanded;
NotifyDataChanged();
}
else
@ -57,15 +62,15 @@ namespace AlicizaX.UI.RecyclerView
{
foreach (var data in list)
{
CreateGroup(data.type);
CreateGroup(data.Type);
}
var groupList = showList.FindAll(data => data.viewName == groupViewName);
var groupList = showList.FindAll(data => data.TemplateName == groupViewName);
for (int i = 0; i < groupList.Count; i++)
{
int index = showList.IndexOf(groupList[i]);
Collapse(index);
if (groupList[i].bExpand)
if (groupList[i].Expanded)
{
Expand(index);
}
@ -73,7 +78,7 @@ namespace AlicizaX.UI.RecyclerView
foreach (var group in groupList)
{
if (list.FindAll(data => data.type == group.type).Count == 0)
if (list.FindAll(data => data.Type == group.Type).Count == 0)
{
showList.Remove(group);
}
@ -82,7 +87,7 @@ namespace AlicizaX.UI.RecyclerView
base.NotifyDataChanged();
}
public override void SetList(List<GroupData> list)
protected internal override void SetList(List<TData> list)
{
showList.Clear();
base.SetList(list);
@ -90,39 +95,28 @@ namespace AlicizaX.UI.RecyclerView
private void CreateGroup(int type)
{
var groupData = showList.Find(data => data.type == type && data.viewName == groupViewName);
var groupData = showList.Find(data => data.Type == type && data.TemplateName == groupViewName);
if (groupData == null)
{
groupData = new GroupData(type, groupViewName, type.ToString());
groupData = new TData
{
TemplateName = groupViewName,
Type = type
};
showList.Add(groupData);
}
}
public void Expand(int index)
{
var expandList = list.FindAll(data => data.type == showList[index].type);
var expandList = list.FindAll(data => data.Type == showList[index].Type);
showList.InsertRange(index + 1, expandList);
}
public void Collapse(int index)
{
var collapseList = showList.FindAll(data => data.type == showList[index].type && data.viewName != groupViewName);
var collapseList = showList.FindAll(data => data.Type == showList[index].Type && data.TemplateName != groupViewName);
showList.RemoveRange(index + 1, collapseList.Count);
}
}
public class GroupData
{
public bool bExpand;
public int type;
public string viewName;
public string name;
public GroupData(int type, string viewName, string name)
{
this.type = type;
this.viewName = viewName;
this.name = name;
}
}
}

View File

@ -1,4 +1,4 @@
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public interface IAdapter
{

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class LoopAdapter<T> : Adapter<T>
public class LoopAdapter<T> : Adapter<T> where T : ISimpleViewData
{
public LoopAdapter(RecyclerView recyclerView) : base(recyclerView)
{

View File

@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class MixedAdapter : Adapter<IMixedData>
public class MixedAdapter<TData> : Adapter<TData> where TData : IMixedViewData
{
public MixedAdapter(RecyclerView recyclerView) : base(recyclerView)
{
}
public MixedAdapter(RecyclerView recyclerView, List<IMixedData> list) : base(recyclerView, list)
public MixedAdapter(RecyclerView recyclerView, List<TData> list) : base(recyclerView, list)
{
}
public MixedAdapter(RecyclerView recyclerView, List<IMixedData> list, Action<IMixedData> onItemClick) : base(recyclerView, list, onItemClick)
public MixedAdapter(RecyclerView recyclerView, List<TData> list, Action<TData> onItemClick) : base(recyclerView, list, onItemClick)
{
}
@ -22,10 +22,4 @@ namespace AlicizaX.UI.RecyclerView
return list[index].TemplateName;
}
}
public interface IMixedData
{
string TemplateName { get; set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 05736c35a54b467a966b416c1461ba61
timeCreated: 1763617414

View File

@ -0,0 +1,17 @@
namespace AlicizaX.UI
{
public interface ISimpleViewData
{
}
public interface IMixedViewData : ISimpleViewData
{
string TemplateName { get; set; }
}
public interface IGroupViewData : IMixedViewData
{
bool Expanded { get; set; }
int Type { get; set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a059b7119e284e37a88a569f76b40579
timeCreated: 1763617420

View File

@ -1,7 +1,7 @@
using System;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
[Serializable]
public class AlignableLinearLayoutManager : LinearLayoutManager

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class CircleLayoutManager : LayoutManager
{

View File

@ -1,7 +1,7 @@
using System;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
[Serializable]
public class GridLayoutManager : LayoutManager

View File

@ -1,6 +1,6 @@
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public interface ILayoutManager
{

View File

@ -1,7 +1,7 @@
using System;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
[Serializable]
public abstract class LayoutManager : ILayoutManager

View File

@ -1,6 +1,6 @@
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class LinearLayoutManager : LayoutManager
{

View File

@ -1,6 +1,6 @@
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class MixedLayoutManager : LayoutManager
{

View File

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class PageLayoutManager : LinearLayoutManager
{

View File

@ -3,7 +3,7 @@ using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class RecyclerView : MonoBehaviour
{
@ -91,7 +91,6 @@ namespace AlicizaX.UI.RecyclerView
public ViewHolder[] Templates
{
get => templates;
set => templates = value;
}
@ -132,13 +131,10 @@ namespace AlicizaX.UI.RecyclerView
}
}
public Scrollbar Scrollbar => _scrollbar;
private IAdapter _adapter;
public LayoutManager LayoutManager => _layoutManager;
public Action<int> OnIndexChanged;
public Action OnScrollValueChanged;
public Action OnMoveingChanged;
@ -272,7 +268,6 @@ namespace AlicizaX.UI.RecyclerView
_adapter = adapter;
ViewProvider.Adapter = _adapter;
ViewProvider.LayoutManager = _layoutManager;
ViewProvider.LayoutManager = _layoutManager;
_layoutManager.RecyclerView = this;
_layoutManager.Adapter = _adapter;
_layoutManager.ViewProvider = viewProvider;

View File

@ -2,7 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
[Serializable]
public class CircleScroller : Scroller

View File

@ -1,6 +1,6 @@
using UnityEngine.Events;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public interface IScroller
{

View File

@ -3,7 +3,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class ScrollbarEx : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler
{

View File

@ -3,7 +3,7 @@ using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
[Serializable]
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 369d2829a7a2462ba93212b3d8afe0fa
timeCreated: 1763615925

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
namespace AlicizaX.UI
{
public abstract class UGListBase<TData, TAdapter> where TAdapter : Adapter<TData> where TData : ISimpleViewData
{
protected readonly RecyclerView _recyclerView;
protected readonly TAdapter _adapter;
public RecyclerView RecyclerView => _recyclerView;
public UGListBase(RecyclerView recyclerView, TAdapter adapter, Action<TData> onItemClick = null)
{
_recyclerView = recyclerView;
_adapter = adapter;
if (_recyclerView != null)
{
_recyclerView.SetAdapter(_adapter);
}
if (onItemClick != null)
{
_adapter.SetOnItemClick(onItemClick);
}
}
public TAdapter Adapter => _adapter;
private List<TData> _datas;
public List<TData> Data
{
get => _datas;
set
{
_datas = value;
_adapter.SetList(_datas);
}
}
}
public class UGList<TData> : UGListBase<TData, Adapter<TData>> where TData : ISimpleViewData
{
public UGList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new Adapter<TData>(recyclerView), onItemClick)
{
}
}
public class UGGroupList<TData> : UGListBase<TData, GroupAdapter<TData>> where TData : class, IGroupViewData, new()
{
public UGGroupList(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null)
: base(recyclerView, new GroupAdapter<TData>(recyclerView, groupViewName), onItemClick)
{
}
}
public class UGLoopList<TData> : UGListBase<TData, LoopAdapter<TData>> where TData : ISimpleViewData, new()
{
public UGLoopList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new LoopAdapter<TData>(recyclerView), onItemClick)
{
}
}
public class UGMixedList<TData> : UGListBase<TData, MixedAdapter<TData>> where TData : IMixedViewData
{
public UGMixedList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new MixedAdapter<TData>(recyclerView), onItemClick)
{
}
}
public static class UGList
{
public static UGList<TData> Create<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData
=> new UGList<TData>(recyclerView, onItemClick);
public static UGGroupList<TData> CreateGroup<TData>(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null) where TData : class, IGroupViewData, new()
=> new UGGroupList<TData>(recyclerView, groupViewName, onItemClick);
public static UGLoopList<TData> CreateLoop<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData, new()
=> new UGLoopList<TData>(recyclerView, onItemClick);
public static UGMixedList<TData> CreateMixed<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : IMixedViewData
=> new UGMixedList<TData>(recyclerView, onItemClick);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19892a10438348b3b7e7cac3e2201b9e
timeCreated: 1763615934

View File

@ -3,7 +3,7 @@ using AlicizaX.UI.Extension;
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public abstract class ViewHolder : MonoBehaviour
{

View File

@ -4,7 +4,7 @@ using System.Linq;
using SimpleObjectPool;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public class MixedViewProvider : ViewProvider
{

View File

@ -1,7 +1,7 @@
using System;
using SimpleObjectPool;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
public sealed class SimpleViewProvider : ViewProvider
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using AlicizaX.UI.Runtime;
using UnityEngine;
namespace AlicizaX.UI.RecyclerView
namespace AlicizaX.UI
{
/// <summary>
/// 提供和管理 ViewHolder