591 lines
18 KiB
C#
591 lines
18 KiB
C#
using UnityEditor;
|
||
using UnityEngine;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using AlicizaX.UI;
|
||
using UnityEngine.UI;
|
||
using Object = UnityEngine.Object;
|
||
|
||
[CustomEditor(typeof(RecyclerView))]
|
||
public class RecyclerViewEditor : Editor
|
||
{
|
||
// Layout Manager
|
||
private SerializedProperty _layoutManagerTypeName;
|
||
private SerializedProperty _layoutManager;
|
||
private List<string> _layoutTypeNames = new List<string>();
|
||
private int _selectedLayoutIndex;
|
||
|
||
// Scroller
|
||
private SerializedProperty scroll;
|
||
private SerializedProperty _scroller;
|
||
private SerializedProperty _scrollerTypeName;
|
||
private List<Type> _scrollerTypes = new List<Type>();
|
||
private List<string> _scrollerTypeNames;
|
||
private int _selectedScrollerIndex;
|
||
|
||
// Base Properties
|
||
private SerializedProperty direction;
|
||
private SerializedProperty alignment;
|
||
private SerializedProperty content;
|
||
private SerializedProperty spacing;
|
||
private SerializedProperty padding;
|
||
private SerializedProperty snap;
|
||
private SerializedProperty scrollSpeed;
|
||
private SerializedProperty wheelSpeed;
|
||
private SerializedProperty templates;
|
||
|
||
private SerializedProperty _showScrollBar;
|
||
private SerializedProperty _scrollbar;
|
||
|
||
private const string NoneOptionName = "None";
|
||
|
||
private void OnEnable()
|
||
{
|
||
// Layout Manager
|
||
_layoutManagerTypeName = serializedObject.FindProperty("_layoutManagerTypeName");
|
||
_layoutManager = serializedObject.FindProperty("_layoutManager");
|
||
RefreshLayoutTypes();
|
||
|
||
// Scroller
|
||
scroll = serializedObject.FindProperty("scroll");
|
||
_scroller = serializedObject.FindProperty("_scroller");
|
||
_scrollerTypeName = serializedObject.FindProperty("_scrollerTypeName");
|
||
RefreshScrollerTypes();
|
||
SyncExistingScroller();
|
||
|
||
// Base Properties
|
||
direction = serializedObject.FindProperty("direction");
|
||
alignment = serializedObject.FindProperty("alignment");
|
||
content = serializedObject.FindProperty("content");
|
||
spacing = serializedObject.FindProperty("spacing");
|
||
padding = serializedObject.FindProperty("padding");
|
||
snap = serializedObject.FindProperty("snap");
|
||
scrollSpeed = serializedObject.FindProperty("scrollSpeed");
|
||
wheelSpeed = serializedObject.FindProperty("wheelSpeed");
|
||
templates = serializedObject.FindProperty("templates");
|
||
_showScrollBar = serializedObject.FindProperty("_showScrollBar");
|
||
_scrollbar = serializedObject.FindProperty("_scrollbar");
|
||
}
|
||
|
||
#region Layout Manager
|
||
|
||
void RefreshLayoutTypes()
|
||
{
|
||
_layoutTypeNames.Clear();
|
||
_layoutTypeNames.Add(NoneOptionName);
|
||
|
||
// 获取所有实现ILayoutManager的非Mono类型
|
||
var types = AlicizaX.Utility.Assembly.GetRuntimeTypes(typeof(ILayoutManager));
|
||
foreach (var type in types)
|
||
{
|
||
if (!typeof(MonoBehaviour).IsAssignableFrom(type))
|
||
{
|
||
_layoutTypeNames.Add(type.FullName);
|
||
}
|
||
}
|
||
|
||
_selectedLayoutIndex = Mathf.Clamp(
|
||
_layoutTypeNames.IndexOf(_layoutManagerTypeName.stringValue),
|
||
0,
|
||
_layoutTypeNames.Count - 1
|
||
);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Scroller
|
||
|
||
void RefreshScrollerTypes()
|
||
{
|
||
_scrollerTypes = TypeCache.GetTypesDerivedFrom<IScroller>()
|
||
.Where(t => t.IsSubclassOf(typeof(MonoBehaviour)))
|
||
.ToList();
|
||
|
||
_scrollerTypeNames = _scrollerTypes
|
||
.Select(t => t.FullName)
|
||
.Prepend(NoneOptionName)
|
||
.ToList();
|
||
}
|
||
|
||
void SyncExistingScroller()
|
||
{
|
||
var rv = target as RecyclerView;
|
||
if (rv == null) return;
|
||
|
||
var existing = rv.GetComponent<IScroller>();
|
||
if (existing != null)
|
||
{
|
||
_scrollerTypeName.stringValue = existing.GetType().FullName;
|
||
_selectedScrollerIndex = _scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue);
|
||
}
|
||
else
|
||
{
|
||
_selectedScrollerIndex = 0;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
public override void OnInspectorGUI()
|
||
{
|
||
serializedObject.Update();
|
||
bool isPlaying = Application.isPlaying;
|
||
|
||
DrawLayoutManagerSection(isPlaying);
|
||
DrawBaseSettings(isPlaying);
|
||
DrawScrollerSettings(isPlaying);
|
||
DrawTemplatesSection();
|
||
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
|
||
#region Drawing Methods
|
||
|
||
void DrawLayoutManagerSection(bool isPlaying)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
{
|
||
EditorGUILayout.LabelField("Layout Manager", EditorStyles.boldLabel);
|
||
|
||
using (new EditorGUI.DisabledScope(isPlaying))
|
||
{
|
||
// 强制允许选择空值
|
||
int newIndex = EditorGUILayout.Popup("Layout Type", _selectedLayoutIndex, _layoutTypeNames.ToArray());
|
||
if (newIndex != _selectedLayoutIndex)
|
||
{
|
||
_selectedLayoutIndex = newIndex; // 立即更新索引
|
||
UpdateLayoutManager(newIndex);
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
|
||
// 显示布局属性或警告
|
||
if (_layoutManager.managedReferenceValue != null)
|
||
{
|
||
EditorGUILayout.Space(3);
|
||
DrawManagedProperties(_layoutManager);
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.HelpBox("Need Choose LayoutManager", MessageType.Error);
|
||
}
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
void DrawBaseSettings(bool isPlaying)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
{
|
||
EditorGUILayout.LabelField("Base Settings", EditorStyles.boldLabel);
|
||
|
||
using (new EditorGUI.DisabledScope(isPlaying))
|
||
{
|
||
EditorGUILayout.PropertyField(direction);
|
||
EditorGUILayout.PropertyField(alignment);
|
||
EditorGUILayout.PropertyField(content);
|
||
}
|
||
|
||
EditorGUILayout.Space(5);
|
||
EditorGUILayout.LabelField("Spacing", EditorStyles.boldLabel);
|
||
EditorGUILayout.PropertyField(spacing);
|
||
EditorGUILayout.PropertyField(padding);
|
||
|
||
EditorGUILayout.Space(5);
|
||
EditorGUILayout.LabelField("Scrolling", EditorStyles.boldLabel);
|
||
|
||
using (new EditorGUI.DisabledScope(isPlaying))
|
||
{
|
||
bool prevScrollValue = scroll.boolValue;
|
||
EditorGUILayout.PropertyField(scroll);
|
||
|
||
if (scroll.boolValue != prevScrollValue)
|
||
{
|
||
HandleScrollToggle();
|
||
if (!scroll.boolValue)
|
||
{
|
||
ClearScrollBar();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (scroll.boolValue)
|
||
{
|
||
using (new EditorGUI.DisabledScope(isPlaying))
|
||
{
|
||
bool prevShowScrollBarValue = _showScrollBar.boolValue;
|
||
EditorGUILayout.PropertyField(_showScrollBar);
|
||
|
||
if (_showScrollBar.boolValue != prevShowScrollBarValue)
|
||
{
|
||
HandleScrollBarToggle();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
EditorGUILayout.PropertyField(snap);
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
void DrawScrollerSettings(bool isPlaying)
|
||
{
|
||
if (!scroll.boolValue) return;
|
||
|
||
EditorGUILayout.BeginVertical("box");
|
||
{
|
||
EditorGUILayout.LabelField("Scroller Settings", EditorStyles.boldLabel);
|
||
|
||
using (new EditorGUI.DisabledScope(isPlaying))
|
||
{
|
||
int newIndex = EditorGUILayout.Popup("Scroller Type", _selectedScrollerIndex, _scrollerTypeNames.ToArray());
|
||
if (newIndex != _selectedScrollerIndex)
|
||
{
|
||
UpdateScroller(newIndex);
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
|
||
var rv = target as RecyclerView;
|
||
if (rv != null)
|
||
{
|
||
var scrollerComponent = rv.GetComponent<IScroller>();
|
||
if (scrollerComponent != null)
|
||
{
|
||
DrawComponentProperties(scrollerComponent as MonoBehaviour, "Scroller Properties");
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.HelpBox("Must choose to use a Scroller.", MessageType.Error);
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.Space(3);
|
||
EditorGUILayout.PropertyField(scrollSpeed);
|
||
EditorGUILayout.PropertyField(wheelSpeed);
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
void DrawComponentProperties(MonoBehaviour component, string header = null)
|
||
{
|
||
if (component == null) return;
|
||
|
||
EditorGUILayout.Space(3);
|
||
if (!string.IsNullOrEmpty(header))
|
||
{
|
||
EditorGUILayout.LabelField(header, EditorStyles.boldLabel);
|
||
}
|
||
|
||
SerializedObject so = new SerializedObject(component);
|
||
so.Update();
|
||
|
||
SerializedProperty prop = so.GetIterator();
|
||
bool enterChildren = true;
|
||
while (prop.NextVisible(enterChildren))
|
||
{
|
||
enterChildren = false;
|
||
if (prop.name == "m_Script") continue;
|
||
EditorGUILayout.PropertyField(prop, true);
|
||
}
|
||
|
||
so.ApplyModifiedProperties();
|
||
}
|
||
|
||
void DrawTemplatesSection()
|
||
{
|
||
EditorGUILayout.Space(5);
|
||
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
|
||
|
||
#region Update Handlers
|
||
|
||
void UpdateLayoutManager(int selectedIndex)
|
||
{
|
||
try
|
||
{
|
||
// 强制清空逻辑
|
||
if (selectedIndex == 0)
|
||
{
|
||
_layoutManager.managedReferenceValue = null;
|
||
_layoutManagerTypeName.stringValue = "";
|
||
_selectedLayoutIndex = 0; // 确保索引同步
|
||
return;
|
||
}
|
||
|
||
// 有效性检查
|
||
if (selectedIndex < 0 || selectedIndex >= _layoutTypeNames.Count)
|
||
{
|
||
Debug.LogError($"Invalid layout index: {selectedIndex}");
|
||
_selectedLayoutIndex = 0;
|
||
return;
|
||
}
|
||
|
||
string typeName = _layoutTypeNames[selectedIndex];
|
||
Type type = AlicizaX.Utility.Assembly.GetType(typeName);
|
||
|
||
if (type != null && typeof(ILayoutManager).IsAssignableFrom(type))
|
||
{
|
||
_layoutManager.managedReferenceValue = Activator.CreateInstance(type);
|
||
_layoutManagerTypeName.stringValue = typeName;
|
||
_selectedLayoutIndex = selectedIndex;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Invalid layout type: {typeName}");
|
||
_selectedLayoutIndex = 0;
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"Layout Manager Error: {e.Message}");
|
||
_layoutManager.managedReferenceValue = null;
|
||
_layoutManagerTypeName.stringValue = "";
|
||
_selectedLayoutIndex = 0;
|
||
}
|
||
}
|
||
|
||
void UpdateScroller(int selectedIndex)
|
||
{
|
||
try
|
||
{
|
||
var rv = target as RecyclerView;
|
||
if (rv == null) return;
|
||
|
||
Undo.RecordObjects(new UnityEngine.Object[] { rv, this }, "Update Scroller");
|
||
|
||
// 移除旧组件
|
||
var oldScroller = rv.GetComponent<IScroller>();
|
||
if (oldScroller != null)
|
||
{
|
||
Undo.DestroyObjectImmediate(oldScroller as MonoBehaviour);
|
||
}
|
||
|
||
if (selectedIndex == 0)
|
||
{
|
||
// 清除序列化引用
|
||
_scroller.objectReferenceValue = null;
|
||
_scrollerTypeName.stringValue = "";
|
||
return;
|
||
}
|
||
|
||
// 添加新组件
|
||
Type selectedType = _scrollerTypes[selectedIndex - 1];
|
||
var newScroller = Undo.AddComponent(rv.gameObject, selectedType) as IScroller;
|
||
|
||
// 同步到序列化属性
|
||
_scroller.objectReferenceValue = newScroller as MonoBehaviour;
|
||
_scrollerTypeName.stringValue = selectedType.FullName;
|
||
_selectedScrollerIndex = selectedIndex;
|
||
|
||
// 立即应用修改
|
||
serializedObject.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(rv); // 标记对象需要保存
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"Scroller Error: {e}");
|
||
_selectedScrollerIndex = 0;
|
||
_scrollerTypeName.stringValue = "";
|
||
_scroller.objectReferenceValue = null;
|
||
}
|
||
}
|
||
|
||
void HandleScrollToggle()
|
||
{
|
||
var rv = target as RecyclerView;
|
||
if (rv == null) return;
|
||
|
||
if (!scroll.boolValue)
|
||
{
|
||
// Remove scroller component
|
||
var scrollerComponent = rv.GetComponent<IScroller>();
|
||
if (scrollerComponent != null)
|
||
{
|
||
Undo.DestroyObjectImmediate(scrollerComponent as MonoBehaviour);
|
||
}
|
||
|
||
_scrollerTypeName.stringValue = "";
|
||
_selectedScrollerIndex = 0;
|
||
}
|
||
}
|
||
|
||
void HandleScrollBarToggle()
|
||
{
|
||
var rv = target as RecyclerView;
|
||
if (rv == null) return;
|
||
if (_showScrollBar.boolValue)
|
||
{
|
||
Direction direction = (Direction)this.direction.enumValueIndex;
|
||
if (direction == Direction.Vertical)
|
||
{
|
||
const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/vertical.prefab";
|
||
InstantiateScrollBar(path, rv.transform);
|
||
}
|
||
else if (direction == Direction.Horizontal)
|
||
{
|
||
const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/horizontal.prefab";
|
||
InstantiateScrollBar(path, rv.transform);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ClearScrollBar();
|
||
}
|
||
}
|
||
|
||
void ClearScrollBar()
|
||
{
|
||
_showScrollBar.boolValue = false;
|
||
if (_scrollbar.objectReferenceValue != null)
|
||
{
|
||
Scrollbar scrollbar = _scrollbar.objectReferenceValue as Scrollbar;
|
||
_scrollbar.objectReferenceValue = null;
|
||
Object.DestroyImmediate(scrollbar.gameObject);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
void DrawManagedProperties(SerializedProperty property)
|
||
{
|
||
SerializedProperty iterator = property.Copy();
|
||
bool enterChildren = true;
|
||
|
||
while (iterator.NextVisible(enterChildren))
|
||
{
|
||
enterChildren = false;
|
||
if (iterator.name == "m_Script") continue;
|
||
EditorGUILayout.PropertyField(iterator, true);
|
||
}
|
||
}
|
||
|
||
void InstantiateScrollBar(string path, Transform parent)
|
||
{
|
||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||
GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
|
||
PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.UserAction);
|
||
_scrollbar.objectReferenceValue = instance.GetComponent<Scrollbar>();
|
||
}
|
||
}
|