Unity编辑器开发 Immediate Mode GUI (IMGUI)

1. 简介:

IMGUI是代码驱动gui系统,由 OnGUI 函数驱动:

cs 复制代码
void OnGUI() {
    if (GUILayout.Button("Press Me")){ 
        Debug.Log("Hello!");
    }
}

IMGUI常用于:

创建 in-game debugging displays and tools;

为组件创建自定义inspector;

创建editor windows and tools;

2. IMGUI基础:

OnGUI()函数在脚本激活时每帧调用;

cs 复制代码
using UnityEngine;

[ExecuteAlways]
public class TestGUI : MonoBehaviour
{
    private void OnGUI()
    {
        GUI.Box(new Rect(10, 10, 100, 90), "Loader Menu");

        if(GUI.Button(new Rect(20, 40, 80, 20), "Button 1"))
        {
            Debug.Log("Button 1");
        }

        if (GUI.Button(new Rect(20, 70, 80, 20), "Button 2"))
        {
            Debug.Log("Button 2");
        }
    }
}
  1. 调用函数来绘制控件:

(GUI/GUILayout).Type (Rect, Content);

定义 Rect 时,坐标系基于屏幕左上角,可使用 Screen.width 和 Screen.height ;

Content类型:

string;

Texture2D;

GUIContent;

cs 复制代码
GUI.Box(new Rect(10, 10, 100, 50), icon);
GUI.Box(new Rect(10, 60, 100, 50), new GUIContent("text", icon));
GUI.Box(new Rect(10, 110, 100, 50), new GUIContent("text", icon, "This is the tooltip"));

GUI.Button(new Rect(10, 160, 100, 20), new GUIContent("Click me", "This is the tooltip"));
GUI.Label(new Rect(10, 180, 100, 20), GUI.tooltip); //显示当前的tooltip内容

3. IMGUI控件:

Label:
cs 复制代码
GUI.Label(new Rect(100, 100, 100, 30), "show text");
Button:
cs 复制代码
if(GUI.Button(new Rect(100, 100, 100, 30), "button"))
{
    Debug.Log("Click button");
}
RepeatButton:

类似 Button,只是点击时一直返回 true;

TextField:
cs 复制代码
str = GUI.TextField(new Rect(100, 100, 100, 30), str);

单行文本输入;

TextArea:
cs 复制代码
str = GUI.TextArea(new Rect(100, 100, 100, 100), str);

多行文本输入;

Toggle:
cs 复制代码
toggleVal = GUI.Toggle(new Rect(100, 100, 100, 30), toggleVal, "Toggle");
Toolbar:

一行单选按钮,toolbarInt 用来记录选择的按钮索引;

cs 复制代码
private int toolbarInt = 0;
private string[] toolbarStrings = { "Toolbar1", "Toolbar2", "Toolbar3" };

void OnGUI()
{
    toolbarInt = GUI.Toolbar(new Rect(100, 100, 300, 30), toolbarInt, toolbarStrings);
}
SelectionGrid:

多行Toolbar;

cs 复制代码
private int selectionIndex = 0;
private string[] selectionStrings = { "Toolbar1", "Toolbar2", "Toolbar3", "Toolbar4" };

void OnGUI()
{
    selectionIndex = GUI.SelectionGrid(new Rect(100, 100, 300, 60), selectionIndex, selectionStrings, 2);
}
HorizontalSlider:

水平滑动条

cs 复制代码
private float sliderValue = 0.0f;

void OnGUI()
{
    sliderValue = GUI.HorizontalSlider(new Rect(100, 100, 100, 30), sliderValue, 0.0f, 10.0f);
}
VerticalSlider:

垂直滑动条;

cs 复制代码
private float sliderValue = 0.0f;

void OnGUI()
{
    sliderValue = GUI.VerticalSlider(new Rect(100, 100, 100, 30), sliderValue, 0.0f, 10.0f);
}
HorizontalScrollbar:

类似 Slider,用于导航 ScrollView 控件,有一个额外参数控制旋钮宽度;

cs 复制代码
private float scrollValue = 0.0f;

void OnGUI()
{
    scrollValue = GUI.HorizontalScrollbar(new Rect(100, 100, 100, 30), scrollValue, 1, 0.0f, 10.0f);
}
VerticalScrollbar:

类似 HorizontalScrollbar;

ScrollView:
cs 复制代码
private Vector2 scrollPosition = Vector2.zero;

void OnGUI()
{
    //第一个参数指定可视区域,第三个参数指定在滚动列表内的大小
    scrollPosition = GUI.BeginScrollView(new Rect(100, 100, 100, 100), scrollPosition, new Rect(0, 0, 400, 400));
    GUI.Label(new Rect(0, 0, 400, 400), "text");
    GUI.EndScrollView();
}
Window:

可拖动的容器;

cs 复制代码
private Rect windowRect = new Rect(100, 100, 100, 100);

void OnGUI()
{
    windowRect = GUI.Window(0, windowRect, WindowFunction, "My Window");
    
}

void WindowFunction(int id)
{
    GUI.Label(new Rect(10, 20, 100, 30), "text");

    GUI.DragWindow();   //让window可拖动
}

GUI.changed:

检测用户是否有操作;

cs 复制代码
selectIndex = GUI.Toolbar(new Rect(100, 100, 200, 30), selectIndex, options);
if(GUI.changed)
{
    Debug.Log("selectIndex = " + selectIndex);
}

4. 使用GUIStyles样式化控件:

GUIStyles 控制控件的外观显示;

GUISkins 包含一系列 GUIStyles;

所有控件可设置一个可选的 GUIStyle;

cs 复制代码
//指定 GUIStyle 的名字 button
GUI.Label(new Rect(100, 100, 100, 30), "text", "button");

在脚本中声明一个序列化的GUIStyle ,查看属性:

cs 复制代码
public GUIStyle style;

void OnGUI()
{
    //指定 GUIStyle 的名字 button
    GUI.Label(new Rect(100, 100, 100, 30), "text", style);
}

设置GUISkin:

cs 复制代码
public GUISkin skin;

void OnGUI()
{
    GUI.skin = skin;
    //绘制
    //...
    //恢复默认skin
    GUI.skin = null;    //
}

改变GUIStyle:

cs 复制代码
GUIStyle style = GUI.skin.GetStyle("label");

style.fontSize = 30;
GUI.Label(new Rect(100, 100, 400, 100), "text1");

style.fontSize = 50;
GUI.Label(new Rect(100, 150, 400, 100), "text2");

5. IMGUI Layout 模式:

固定布局和自动布局:

要使用自动布局,使用 GUILayout 代替GUI;

可以在 OnGUI() 函数中同时使用两种布局方式,但是两种布局相互独立;

GUILayout 不需要指定 Rect;

cs 复制代码
// Fixed Layout
GUI.Button(new Rect(25, 25, 100, 30), "I am a Fixed Layout Button");

// Automatic Layout
GUILayout.Button("I am an Automatic Layout Button");
固定布局 Group:

Group内的控件将基于组的左上角定位;

cs 复制代码
GUI.BeginGroup(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 50, 100, 100));

GUI.Box(new Rect(0, 0, 100, 100), "Group is here");
GUI.Button(new Rect(10, 40, 80, 30), "Click me");

GUI.EndGroup();

可以嵌套使用Group,可获取裁剪效果:

cs 复制代码
// background image that is 256 x 32
public Texture2D bgImage;

// foreground image that is 256 x 32
public Texture2D fgImage;

// a float between 0.0 and 1.0
public float playerEnergy = 1.0f;

//使用默认style绘制box
public GUIStyle style = new GUIStyle();

void OnGUI()
{
    if(bgImage == null || fgImage == null)
    {
        return;
    }

    GUI.BeginGroup(new Rect(300, 300, 256, 32));

    // 绘制背景
    GUI.Box(new Rect(0, 0, 256, 32), bgImage, style);

    // 通过 playerEnergy 控制显示大小,达到裁剪效果
    GUI.BeginGroup(new Rect(0, 0, playerEnergy * 256, 32));
    GUI.Box(new Rect(0, 0, 256, 32), fgImage, style);
    GUI.EndGroup();

    GUI.EndGroup();
}
自动布局 Area:

类似固定布局 Group;

定义屏幕的一块区域来包含 GUILayout 控件,控件基于 Area 的左上角定位;

cs 复制代码
GUILayout.Button("I am not inside an Area");
GUILayout.BeginArea(new Rect(Screen.width / 2, Screen.height / 2, 300, 300));
GUILayout.Button("I am completely inside an Area");
GUILayout.EndArea();

注意Area里控件的宽度将被拉伸到与Area一样;

自动布局 Horizontal and Vertical Group:

使用自动布局时,控件默认从上往下一个接一个;

为了修改这一行为,提供了Horizontal 和 Vertical Group;

Horizontal Group 里的控件被水平摆放;

GUILayout.BeginHorizontal() , GUILayout.EndHorizontal()

Vertical Group 里的控件被垂直摆放;

GUILayout.BeginVertical() , and GUILayout.EndVertical()

cs 复制代码
GUILayout.BeginArea(new Rect(400, 400, 200, 100));

GUILayout.BeginHorizontal();

GUILayout.Button("Button\nfirst");

GUILayout.BeginVertical();
GUILayout.Button("Button2");
GUILayout.Label("Label");
GUILayout.EndVertical();

GUILayout.EndHorizontal();

GUILayout.EndArea();
GUILayoutOptions :

使用 GUILayoutOptions 覆盖一些自动布局参数;

cs 复制代码
GUILayout.BeginArea(new Rect(100, 50, Screen.width - 200, Screen.height - 100));
GUILayout.Button("I am a regular Automatic Layout Button");
GUILayout.Button("My width has been overridden", GUILayout.Width(95));
GUILayout.EndArea();

6. 使用 IMGUI 扩展编辑器:

1. 创建自定义 Editor Window:
  1. 创建脚本继承 EditorWindow:
cs 复制代码
using UnityEditor;
using UnityEngine;

public class MyWindow : EditorWindow
{
    private void OnGUI()
    {
        
    }
}
  1. 显示window:
cs 复制代码
using UnityEditor;
using UnityEngine;

public class MyWindow : EditorWindow
{
    [MenuItem("Window/My Window")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(MyWindow));
    }

    private void OnGUI()
    {
        
    }
}
  1. 实现 gui:

EditorGUIEditorGUILayout 提供了额外的控件;

cs 复制代码
using UnityEditor;
using UnityEngine;

public class MyWindow : EditorWindow
{
    string myString = "Hello World";
    bool groupEnabled;
    bool myBool = true;
    float myFloat = 1.23f;

    [MenuItem("Window/My Window")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(MyWindow));
    }

    private void OnGUI()
    {
        GUILayout.Label("Base Settings", EditorStyles.boldLabel);
        myString = EditorGUILayout.TextField("Text Field", myString);

        groupEnabled = EditorGUILayout.BeginToggleGroup("Optional Settings", groupEnabled);
        myBool = EditorGUILayout.Toggle("Toggle", myBool);
        myFloat = EditorGUILayout.Slider("Slider", myFloat, -3, 3);
        EditorGUILayout.EndToggleGroup();
    }
}
2. 自定义 Inspector:
1. 自定义 GUI of Serializable class:
cs 复制代码
using System;
using UnityEngine;

public enum PersonLevel { A, B, C}

[Serializable]
public class Person
{
    public string name;
    public int age;
    public PersonLevel personLevel;
}

public class PersonInfo : MonoBehaviour
{
    public Person person;
    public Person[] persons;
}

(默认显示)

使用 Property Drawer 来控制其在 Inspector 上的显示:

cs 复制代码
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(Person))]
public class PersonDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Using BeginProperty / EndProperty on the parent property means that
        // prefab override logic works on the entire property.
        EditorGUI.BeginProperty(position, label, property);

        // Draw label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // Calculate rects
        var nameRect = new Rect(position.x, position.y, 30, position.height);
        var ageRect = new Rect(position.x + 35, position.y, 50, position.height);
        var personLevelRect = new Rect(position.x + 90, position.y, position.width - 90, position.height);

        // Draw fields - pass GUIContent.none to each so they are drawn without labels
        EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("name"), GUIContent.none);
        EditorGUI.PropertyField(ageRect, property.FindPropertyRelative("age"), GUIContent.none);
        EditorGUI.PropertyField(personLevelRect, property.FindPropertyRelative("personLevel"), GUIContent.none);

        // Set indent back to what it was
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}
2. 使用 Property Attribute 自定义脚本成员 GUI:

创建 Attribute:

cs 复制代码
using UnityEngine;

public class MyRangeAttribute : PropertyAttribute
{
    public float min;
    public float max;

    public MyRangeAttribute(float min, float max)
    {
        this.min = min;
        this.max = max;
    }
}

对 Attribute 进行自定义绘制:

cs 复制代码
using UnityEditor;
using UnityEngine;

//对MyRangeAttribute
[CustomPropertyDrawer(typeof(MyRangeAttribute))]
public class RangeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // First get the attribute since it contains the range for the slider
        MyRangeAttribute range = (MyRangeAttribute)attribute;

        // Now draw the property as a Slider or an IntSlider based on whether it's a float or integer.
        if (property.propertyType == SerializedPropertyType.Float)
            EditorGUI.Slider(position, property, range.min, range.max, label);
        else if (property.propertyType == SerializedPropertyType.Integer)
            EditorGUI.IntSlider(position, property, (int)range.min, (int)range.max, label);
        else
            EditorGUI.LabelField(position, label.text, "Use MyRange with float or int.");
    }
}

(注意,为了性能考虑,EditorGUILayout 不可在 PropertyDrawer 中使用)

使用 Attribute:

cs 复制代码
using UnityEngine;

public class TestAttribute : MonoBehaviour
{
    [MyRange(1.5f, 35f)]
    public float fValue;
}
3. Component 自定义编辑器:

创建 component:

cs 复制代码
using UnityEngine;

[ExecuteInEditMode]
public class LookAtPoint : MonoBehaviour
{
    public Vector3 lookAtPoint = Vector3.zero;

    void Update()
    {
        transform.LookAt(lookAtPoint);
    }
}

创建组件自定义编辑器:

cs 复制代码
using UnityEditor;

[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
    LookAtPoint targetComp;
    SerializedProperty lookAtPoint;

    void OnEnable()
    {
        lookAtPoint = serializedObject.FindProperty("lookAtPoint");
    }

    public override void OnInspectorGUI()
    {
        targetComp = target as LookAtPoint;

        serializedObject.Update();
        EditorGUILayout.PropertyField(lookAtPoint);
        serializedObject.ApplyModifiedProperties();
    }
}

通过 OnSceneGUI 往 Scene View 中添加 编辑器代码:

cs 复制代码
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
    private void OnSceneGUI()
    {
        LookAtPoint t = (LookAtPoint)target;

        //使用 Handles 类来实现3D场景编辑控制
        EditorGUI.BeginChangeCheck();
        //给定位置,在场景中画中控制轴,拖动的话,返回变化的pos
        Vector3 pos = Handles.PositionHandle(t.lookAtPoint, Quaternion.identity);
        if (EditorGUI.EndChangeCheck())
        {
            Undo.RecordObject(target, "Move point");
            t.lookAtPoint = pos;
            t.Update();
        }
    }
}

如果要在场景中添加 GUI,使用 Handles.BeginGUI() and Handles.EndGUI();

4. TreeView :

是一个 IMGUI 控件,用于显示分层数据,可展开和折叠;

使用 TreeView 创建高度自定义的列表视图和多列表格;

按行渲染,每行代表一个 TreeViewItem, 包含父节点和子节点的信息;

TreeView 有一个隐藏的根 TreeViewItem,是所有其他 item的根;

重要的类和方法:

类 TreeViewState:

包含交互的状态信息,可序列化,以便在编辑器编译和进入Play模式时保存数据;

类 TreeViewItem:

包含每个 TreeView item 的数据,在 TreeView 中需要一个唯一 id;

depth 属性用来视觉缩进;

抽象方法 BuildRoot:

TreeView 类的抽象方法,每次调用 TreeView 的 Reload 方法就会触发;

用于创建 root item;

对于简单树,可创建所有的 TreeViewItem;

对于复杂树,通过覆写 BuildRows 来创建当前需要的行;

虚方法 BuildRows:

默认实现创建整个树;

可覆写只创建展开的行;

初始化 TreeView:

调用 Reload 方法来初始化;

两种方式设置 TreeView:

  1. 在 BuildRoot 中创建整个树;

  2. 在 BuildRoot 中只创建根,然后在 BuildRows 中只创建展开的 items;

Example 1: A simple TreeView:
cs 复制代码
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;

public class SimpleTreeView : TreeView
{
    public SimpleTreeView(TreeViewState state) : base(state)
    {
        Reload();
    }

    protected override TreeViewItem BuildRoot()
    {
        // BuildRoot is called every time Reload is called to ensure that TreeViewItems 
        // are created from data. Here we create a fixed set of items. In a real world example,
        // a data model should be passed into the TreeView and the items created from the model.

        // This section illustrates that IDs should be unique. The root item is required to 
        // have a depth of -1, and the rest of the items increment from that.
        var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };

        var allItems = new List<TreeViewItem>
        {
            new TreeViewItem {id = 1, depth = 0, displayName = "Animals"},
            new TreeViewItem {id = 2, depth = 1, displayName = "Mammals"},
            new TreeViewItem {id = 3, depth = 2, displayName = "Tiger"},
            new TreeViewItem {id = 4, depth = 2, displayName = "Elephant"},
            new TreeViewItem {id = 5, depth = 2, displayName = "Okapi"},
            new TreeViewItem {id = 6, depth = 2, displayName = "Armadillo"},
            new TreeViewItem {id = 7, depth = 1, displayName = "Reptiles"},
            new TreeViewItem {id = 8, depth = 2, displayName = "Crocodile"},
            new TreeViewItem {id = 9, depth = 2, displayName = "Lizard"},
        };

        // Utility method that initializes the TreeViewItem.children and .parent for all items.
        SetupParentsAndChildrenFromDepths(root, allItems);

        // Return root of the tree
        return root;
    }
}

另一种方式添加 Item:

cs 复制代码
protected override TreeViewItem BuildRoot()
{
    // BuildRoot is called every time Reload is called to ensure that TreeViewItems 
    // are created from data. Here we create a fixed set of items. In a real world example,
    // a data model should be passed into the TreeView and the items created from the model.

    // This section illustrates that IDs should be unique. The root item is required to 
    // have a depth of -1, and the rest of the items increment from that.
    var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };

    //不指定depth,通过 TreeViewItem.AddChild 来添加节点
    var animals = new TreeViewItem { id = 1, displayName = "Animals" };
    root.AddChild(animals);
    var mammals = new TreeViewItem { id = 2, displayName = "Mammals" };
    animals.AddChild(mammals);
    mammals.AddChild(new TreeViewItem { id = 3, displayName = "Tiger" });
    mammals.AddChild(new TreeViewItem { id = 4, displayName = "Elephant" });
    mammals.AddChild(new TreeViewItem { id = 5, displayName = "Okapi" });
    mammals.AddChild(new TreeViewItem { id = 6, displayName = "Armadillo" });
    var reptiles = new TreeViewItem { id = 7, displayName = "Reptiles" };
    animals.AddChild(reptiles);
    reptiles.AddChild(new TreeViewItem { id = 8, displayName = "Crocodile" });
    reptiles.AddChild(new TreeViewItem { id = 9, displayName = "Lizard" });

    SetupDepthsFromParentsAndChildren(root);

    // Return root of the tree
    return root;
}

使用 EditorWindow 显示 TreeView:

cs 复制代码
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;

public class SimpleTreeViewWindow : EditorWindow
{
    // SerializeField is used to ensure the view state is written to the window 
    // layout file. This means that the state survives restarting Unity as long as the window
    // is not closed. If the attribute is omitted then the state is still serialized/deserialized.
    [SerializeField]
    TreeViewState m_TreeViewState;

    //The TreeView is not serializable, so it should be reconstructed from the tree data.
    SimpleTreeView m_SimpleTreeView;

    void OnEnable()
    {
        // Check whether there is already a serialized view state (state 
        // that survived assembly reloading)
        if (m_TreeViewState == null)
            m_TreeViewState = new TreeViewState();
        
        //创建 TreeView
        m_SimpleTreeView = new SimpleTreeView(m_TreeViewState);
    }

    void OnGUI()
    {
        //绘制 TreeView
        m_SimpleTreeView.OnGUI(new Rect(0, 0, position.width, position.height));
    }

    // Add menu named "My Window" to the Window menu
    [MenuItem("TreeView Examples/Simple Tree Window")]
    static void ShowWindow()
    {
        // Get existing open window or if none, make a new one:
        var window = GetWindow<SimpleTreeViewWindow>();
        window.titleContent = new GUIContent("SimpleTreeView Window");
        window.Show();
    }
}
Example 2: A multi-column TreeView:

最终效果:

(列Header鼠标右键菜单)

EditorWindow:

cs 复制代码
using System;
using System.Collections.Generic;
using UnityEditor.Callbacks;
using UnityEditor.IMGUI.Controls;
using UnityEngine;


namespace UnityEditor.TreeViewExamples
{

	class MultiColumnWindow : EditorWindow
	{
		[NonSerialized] bool m_Initialized;
        // Serialized in the window layout file so it survives assembly reloading
        [SerializeField] TreeViewState m_TreeViewState;
		[SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState;

		SearchField m_SearchField;
		MultiColumnTreeView m_TreeView;
		MyTreeAsset m_MyTreeAsset;

		[MenuItem("TreeView Examples/Multi Columns")]
		public static MultiColumnWindow GetWindow ()
		{
			var window = GetWindow<MultiColumnWindow>();
			window.titleContent = new GUIContent("Multi Columns");
			window.Focus();
			window.Repaint();
			return window;
		}

		//双击资源时触发
		[OnOpenAsset]
		public static bool OnOpenAsset (int instanceID, int line)
		{
			var myTreeAsset = EditorUtility.InstanceIDToObject (instanceID) as MyTreeAsset;
			if (myTreeAsset != null)
			{
				var window = GetWindow ();
				window.SetTreeAsset(myTreeAsset);
				return true;
			}
			return false; // we did not handle the open
		}

		void SetTreeAsset (MyTreeAsset myTreeAsset)
		{
			m_MyTreeAsset = myTreeAsset;
			m_Initialized = false;
		}

		// TreeView 显示区域
		Rect multiColumnTreeViewRect
		{
			get { return new Rect(20, 30, position.width-40, position.height-60); }
		}

		// 搜索显示区域
		Rect toolbarRect
		{
			get { return new Rect (20f, 10f, position.width-40f, 20f); }
		}

		//底部工具栏区域
		Rect bottomToolbarRect
		{
			get { return new Rect(20f, position.height - 18f, position.width - 40f, 16f); }
		}

		public MultiColumnTreeView treeView
		{
			get { return m_TreeView; }
		}

		void InitIfNeeded ()
		{
			if (!m_Initialized)
			{
				// Check if it already exists (deserialized from window layout file or scriptable object)
				if (m_TreeViewState == null)
					m_TreeViewState = new TreeViewState();

                //创建 HeaderState,包含各列头部ui相关显示
                bool firstInit = m_MultiColumnHeaderState == null;
				var headerState = MultiColumnTreeView.CreateDefaultMultiColumnHeaderState(multiColumnTreeViewRect.width);
				//应用本地数据
				if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState))
					MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState);
				m_MultiColumnHeaderState = headerState;

                //根据 HeaderState 创建 MultiColumnHeader
                var multiColumnHeader = new MyMultiColumnHeader(headerState);
				if (firstInit)
					multiColumnHeader.ResizeToFit ();

				var treeModel = new TreeModel<MyTreeElement>(GetData());

                //根据 TreeViewState,MultiColumnHeader, TreeModel 创建 TreeView
                m_TreeView = new MultiColumnTreeView(m_TreeViewState, multiColumnHeader, treeModel);

				//搜索输入框
				m_SearchField = new SearchField();
				m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem;

				m_Initialized = true;
			}
		}
		
		// 获取列表数据
		IList<MyTreeElement> GetData ()
		{
			if (m_MyTreeAsset != null && m_MyTreeAsset.treeElements != null && m_MyTreeAsset.treeElements.Count > 0)
				return m_MyTreeAsset.treeElements;

			// generate some test data
			return MyTreeElementGenerator.GenerateRandomTree(130); 
		}

		// 选择某个资源时触发
		void OnSelectionChange ()
		{
			if (!m_Initialized)
				return;

			var myTreeAsset = Selection.activeObject as MyTreeAsset;
			if (myTreeAsset != null && myTreeAsset != m_MyTreeAsset)
			{
				m_MyTreeAsset = myTreeAsset;
				m_TreeView.treeModel.SetData (GetData ());
				m_TreeView.Reload ();
			}
		}

		void OnGUI ()
		{
			//初始化
			InitIfNeeded();

			SearchBar (toolbarRect);
			DoTreeView (multiColumnTreeViewRect);
			BottomToolBar (bottomToolbarRect);
		}

		void SearchBar (Rect rect)
		{
			//绘制搜索输入框
			treeView.searchString = m_SearchField.OnGUI (rect, treeView.searchString);
		}

		void DoTreeView (Rect rect)
		{
			//绘制 TreeView
			m_TreeView.OnGUI(rect);
		}

		//底部工具栏
		void BottomToolBar (Rect rect)
		{
			GUILayout.BeginArea (rect);

			using (new EditorGUILayout.HorizontalScope ())
			{

				var style = "miniButton";
				if (GUILayout.Button("Expand All", style))
				{
					treeView.ExpandAll ();
				}

				if (GUILayout.Button("Collapse All", style))
				{
					treeView.CollapseAll ();
				}

				GUILayout.FlexibleSpace();

				GUILayout.Label (m_MyTreeAsset != null ? AssetDatabase.GetAssetPath (m_MyTreeAsset) : string.Empty);

				GUILayout.FlexibleSpace ();

				if (GUILayout.Button("Set sorting", style))
				{
					var myColumnHeader = (MyMultiColumnHeader)treeView.multiColumnHeader;
					myColumnHeader.SetSortingColumns (new int[] {4, 3, 2}, new[] {true, false, true});
					myColumnHeader.mode = MyMultiColumnHeader.Mode.LargeHeader;
				}


				GUILayout.Label ("Header: ", "minilabel");
				if (GUILayout.Button("Large", style))
				{
					var myColumnHeader = (MyMultiColumnHeader) treeView.multiColumnHeader;
					myColumnHeader.mode = MyMultiColumnHeader.Mode.LargeHeader;
				}
				if (GUILayout.Button("Default", style))
				{
					var myColumnHeader = (MyMultiColumnHeader)treeView.multiColumnHeader;
					myColumnHeader.mode = MyMultiColumnHeader.Mode.DefaultHeader;
				}
				if (GUILayout.Button("No sort", style))
				{
					var myColumnHeader = (MyMultiColumnHeader)treeView.multiColumnHeader;
					myColumnHeader.mode = MyMultiColumnHeader.Mode.MinimumHeaderWithoutSorting;
				}

				GUILayout.Space (10);
				
				if (GUILayout.Button("values <-> controls", style))
				{
					treeView.showControls = !treeView.showControls;
				}
			}

			GUILayout.EndArea();
		}
	}


	internal class MyMultiColumnHeader : MultiColumnHeader
	{
		Mode m_Mode;

		public enum Mode
		{
			LargeHeader,
			DefaultHeader,
			MinimumHeaderWithoutSorting
		}

		public MyMultiColumnHeader(MultiColumnHeaderState state)
			: base(state)
		{
			mode = Mode.DefaultHeader;
		}

		public Mode mode
		{
			get
			{
				return m_Mode;
			}
			set
			{
				m_Mode = value;
				switch (m_Mode)
				{
					case Mode.LargeHeader:
						canSort = true;
						height = 37f;
						break;
					case Mode.DefaultHeader:
						canSort = true;
						height = DefaultGUI.defaultHeight;
						break;
					case Mode.MinimumHeaderWithoutSorting:
						canSort = false;
						height = DefaultGUI.minimumHeight;
						break;
				}
			}
		}

		// 自定义 Head GUI 显示
		protected override void ColumnHeaderGUI (MultiColumnHeaderState.Column column, Rect headerRect, int columnIndex)
		{
			// Default column header gui
			base.ColumnHeaderGUI(column, headerRect, columnIndex);

			// Add additional info for large header
			if (mode == Mode.LargeHeader)
			{
				// Show example overlay stuff on some of the columns
				if (columnIndex > 2)
				{
					headerRect.xMax -= 3f;
					var oldAlignment = EditorStyles.largeLabel.alignment;
					EditorStyles.largeLabel.alignment = TextAnchor.UpperRight;
					GUI.Label(headerRect, 36 + columnIndex + "%", EditorStyles.largeLabel);
					EditorStyles.largeLabel.alignment = oldAlignment;
				}
			}
		}
	}

}

MultiColumnTreeView:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;

namespace UnityEditor.TreeViewExamples
{
	internal class MultiColumnTreeView : TreeViewWithTreeModel<MyTreeElement>
	{
		const float kRowHeights = 20f;
		const float kToggleWidth = 18f;
		public bool showControls = true;

		//编辑器图标资源
		static GUIContent[] s_TestIcons =
		{
            EditorGUIUtility.IconContent ("Folder Icon"),
			EditorGUIUtility.IconContent ("AudioSource Icon"),
			EditorGUIUtility.IconContent ("Camera Icon"),
			EditorGUIUtility.IconContent ("Windzone Icon"),
			EditorGUIUtility.IconContent ("GameObject Icon")

		};

		// All columns
		enum MyColumns
		{
			Icon1,
			Icon2,
			Name,
			Value1,
			Value2,
			Value3,
		}

		public enum SortOption
		{
			Name,
			Value1,
			Value2,
			Value3,
		}

		// Sort options per column
		SortOption[] m_SortOptions = 
		{
			SortOption.Value1, 
			SortOption.Value3, 
			SortOption.Name, 
			SortOption.Value1, 
			SortOption.Value2,
			SortOption.Value3
		};

		//展开Tree,扁平处理,不包含Root
		public static void TreeToList (TreeViewItem root, IList<TreeViewItem> result)
		{
			if (root == null)
				throw new NullReferenceException("root");
			if (result == null)
				throw new NullReferenceException("result");

			result.Clear();
	
			if (root.children == null)
				return;

			//通过一个堆来展开
			Stack<TreeViewItem> stack = new Stack<TreeViewItem>();
			//后面的元素先入栈,后弹出
			for (int i = root.children.Count - 1; i >= 0; i--)
				stack.Push(root.children[i]);

			while (stack.Count > 0)
			{
				TreeViewItem current = stack.Pop();
				result.Add(current);

				if (current.hasChildren && current.children[0] != null)
				{
					for (int i = current.children.Count - 1; i >= 0; i--)
					{
						stack.Push(current.children[i]);
					}
				}
			}
		}

		public MultiColumnTreeView (TreeViewState state, MultiColumnHeader multicolumnHeader, TreeModel<MyTreeElement> model) : base (state, multicolumnHeader, model)
		{
			Assert.AreEqual(m_SortOptions.Length , Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");

			// Custom setup
			rowHeight = kRowHeights;				//行高
			columnIndexForTreeFoldouts = 2;			//折叠图标在哪列显示
			showAlternatingRowBackgrounds = true;	//背景交替变化
			showBorder = true;						//显示边框
			customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
			extraSpaceBeforeIconAndLabel = kToggleWidth;
			multicolumnHeader.sortingChanged += OnSortingChanged;	//排序发生变化
			
			Reload();
		}

        //构建显示的行 TreeViewItem 数据
        // Note we We only build the visible rows, only the backend has the full tree information. 
        // The treeview only creates info for the row list.
        protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
		{
			var rows = base.BuildRows (root);
			SortIfNeeded (root, rows);
			return rows;
		}

		void OnSortingChanged (MultiColumnHeader multiColumnHeader)
		{
			SortIfNeeded (rootItem, GetRows());
		}

		void SortIfNeeded (TreeViewItem root, IList<TreeViewItem> rows)
		{
			if (rows.Count <= 1)
				return;
			
			if (multiColumnHeader.sortedColumnIndex == -1)
			{
				return; // No column to sort for (just use the order the data are in)
			}
			
			// Sort the roots of the existing tree items
			SortByMultipleColumns ();
			TreeToList(root, rows);
			Repaint();
		}

		void SortByMultipleColumns ()
		{
			//设置排序的列
			var sortedColumns = multiColumnHeader.state.sortedColumns;

			if (sortedColumns.Length == 0)
				return;
			//只排序根节点直属的子节点
			var myTypes = rootItem.children.Cast<TreeViewItem<MyTreeElement> >();

			//首选列优先排序
			var orderedQuery = InitialOrder (myTypes, sortedColumns);

			//其他列排序
			for (int i=1; i<sortedColumns.Length; i++)
			{
				SortOption sortOption = m_SortOptions[sortedColumns[i]];
				bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);

				switch (sortOption)
				{
					case SortOption.Name:
                        // ThenBy 在现有排序的基础上,对于相等的元素,再深入排序
                        orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending);
						break;
					case SortOption.Value1:
						orderedQuery = orderedQuery.ThenBy(l => l.data.floatValue1, ascending);
						break;
					case SortOption.Value2:
						orderedQuery = orderedQuery.ThenBy(l => l.data.floatValue2, ascending);
						break;
					case SortOption.Value3:
						orderedQuery = orderedQuery.ThenBy(l => l.data.floatValue3, ascending);
						break;
				}
			}

			rootItem.children = orderedQuery.Cast<TreeViewItem> ().ToList ();
		}

		IOrderedEnumerable<TreeViewItem<MyTreeElement>> InitialOrder(IEnumerable<TreeViewItem<MyTreeElement>> myTypes, int[] history)
		{
			SortOption sortOption = m_SortOptions[history[0]];
			//是否升序
			bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
			switch (sortOption)
			{
				case SortOption.Name:
					return myTypes.Order(l => l.data.name, ascending);
				case SortOption.Value1:
					return myTypes.Order(l => l.data.floatValue1, ascending);
				case SortOption.Value2:
					return myTypes.Order(l => l.data.floatValue2, ascending);
				case SortOption.Value3:
					return myTypes.Order(l => l.data.floatValue3, ascending);
				default:
					Assert.IsTrue(false, "Unhandled enum");
					break;
			}

			// default
			return myTypes.Order(l => l.data.name, ascending);
		}

		int GetIcon1Index(TreeViewItem<MyTreeElement> item)
		{
			return (int)(Mathf.Min(0.99f, item.data.floatValue1) * s_TestIcons.Length);
		}

		int GetIcon2Index (TreeViewItem<MyTreeElement> item)
		{
			return Mathf.Min(item.data.text.Length, s_TestIcons.Length-1);
		}

		//绘制一行的 GUI
		protected override void RowGUI (RowGUIArgs args)
		{
			var item = (TreeViewItem<MyTreeElement>) args.item;

            //列可通过Header菜单隐藏,故需要 args.GetNumVisibleColumns
            for (int i = 0; i < args.GetNumVisibleColumns (); ++i)
			{
				//绘制每列
				CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
			}
		}

		void CellGUI (Rect cellRect, TreeViewItem<MyTreeElement> item, MyColumns column, ref RowGUIArgs args)
		{
			// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
			CenterRectUsingSingleLineHeight(ref cellRect);

			//根据类型绘制 GUI
			switch (column)
			{
				case MyColumns.Icon1:
					{
						GUI.DrawTexture(cellRect, s_TestIcons[GetIcon1Index(item)].image, ScaleMode.ScaleToFit);
					}
					break;
				case MyColumns.Icon2:
					{
						GUI.DrawTexture(cellRect, s_TestIcons[GetIcon2Index(item)].image, ScaleMode.ScaleToFit);
					}
					break;

				case MyColumns.Name:
					{
						// Do toggle
						Rect toggleRect = cellRect;
						toggleRect.x += GetContentIndent(item);
						toggleRect.width = kToggleWidth;
						if (toggleRect.xMax < cellRect.xMax)
							item.data.enabled = EditorGUI.Toggle(toggleRect, item.data.enabled); // hide when outside cell rect

						// Default icon and label
						args.rowRect = cellRect;
						base.RowGUI(args);
					}
					break;

				case MyColumns.Value1:
				case MyColumns.Value2:
				case MyColumns.Value3:
					{
						if (showControls)
						{
							cellRect.xMin += 5f; // When showing controls make some extra spacing

							if (column == MyColumns.Value1)
								item.data.floatValue1 = EditorGUI.Slider(cellRect, GUIContent.none, item.data.floatValue1, 0f, 1f);
							if (column == MyColumns.Value2)
								item.data.material = (Material)EditorGUI.ObjectField(cellRect, GUIContent.none, item.data.material, typeof(Material), false);
							if (column == MyColumns.Value3)
								item.data.text = GUI.TextField(cellRect, item.data.text);
						}
						else
						{
							string value = "Missing";
							if (column == MyColumns.Value1)
								value = item.data.floatValue1.ToString("f5");
							if (column == MyColumns.Value2)
								value = item.data.floatValue2.ToString("f5");
							if (column == MyColumns.Value3)
								value = item.data.floatValue3.ToString("f5");

							DefaultGUI.LabelRightAligned(cellRect, value, args.selected, args.focused);
						}
					}
					break;
			}
		}

		// Rename
		//--------

		protected override bool CanRename(TreeViewItem item)
		{
			// Only allow rename if we can show the rename overlay with a certain width (label might be clipped by other columns)
			Rect renameRect = GetRenameRect (treeViewRect, 0, item);
			return renameRect.width > 30;
		}

		protected override void RenameEnded(RenameEndedArgs args)
		{
			// Set the backend name and reload the tree to reflect the new model
			if (args.acceptedRename)
			{
				var element = treeModel.Find(args.itemID);
				element.name = args.newName;
				Reload();
			}
		}

		protected override Rect GetRenameRect (Rect rowRect, int row, TreeViewItem item)
		{
			Rect cellRect = GetCellRectForTreeFoldouts (rowRect);
			CenterRectUsingSingleLineHeight(ref cellRect);
			return base.GetRenameRect (cellRect, row, item);
		}

		// Misc
		//--------

		protected override bool CanMultiSelect (TreeViewItem item)
		{
			return true;
		}

        //创建 HeaderState
        public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
		{
			var columns = new[] 
			{
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent(EditorGUIUtility.FindTexture("FilterByLabel"), "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "),
					contextMenuText = "Asset",		//右键点击出现的菜单,可配合 allowToggleVisibility 来控制该列是否显示
					headerTextAlignment = TextAlignment.Center,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Right,
					width = 30, 
					minWidth = 30,
					maxWidth = 60,
					autoResize = false,
					allowToggleVisibility = true
				},
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent(EditorGUIUtility.FindTexture("FilterByType"), "Sed hendrerit mi enim, eu iaculis leo tincidunt at."),
					contextMenuText = "Type",
					headerTextAlignment = TextAlignment.Center,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Right,
					width = 30, 
					minWidth = 30,
					maxWidth = 60,
					autoResize = false,
					allowToggleVisibility = true
				},
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent("Name"),
					headerTextAlignment = TextAlignment.Left,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Center,
					width = 150, 
					minWidth = 60,
					autoResize = false,
					allowToggleVisibility = false
				},
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent("Multiplier", "In sed porta ante. Nunc et nulla mi."),
					headerTextAlignment = TextAlignment.Right,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Left,
					width = 110,
					minWidth = 60,
					autoResize = true
				},
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent("Material", "Maecenas congue non tortor eget vulputate."),
					headerTextAlignment = TextAlignment.Right,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Left,
					width = 95,
					minWidth = 60,
					autoResize = true,
					allowToggleVisibility = true
				},
				new MultiColumnHeaderState.Column 
				{
					headerContent = new GUIContent("Note", "Nam at tellus ultricies ligula vehicula ornare sit amet quis metus."),
					headerTextAlignment = TextAlignment.Right,
					sortedAscending = true,
					sortingArrowAlignment = TextAlignment.Left,
					width = 70,
					minWidth = 60,
					autoResize = true
				}
			};

			Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");

			var state =  new MultiColumnHeaderState(columns);
			return state;
		}
	}

	static class MyExtensionMethods
	{
		public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
		{
			if (ascending)
			{
				return source.OrderBy(selector);
			}
			else
			{
				return source.OrderByDescending(selector);
			}
		}

		public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
		{
			if (ascending)
			{
				return source.ThenBy(selector);
			}
			else
			{
				return source.ThenByDescending(selector);
			}
		}
	}
}

基类 TreeViewWithTreeModel:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;


namespace UnityEditor.TreeViewExamples
{

	internal class TreeViewItem<T> : TreeViewItem where T : TreeElement
	{
		public T data { get; set; }

		public TreeViewItem (int id, int depth, string displayName, T data) : base (id, depth, displayName)
		{
			this.data = data;
		}
	}

	internal class TreeViewWithTreeModel<T> : TreeView where T : TreeElement
	{
		TreeModel<T> m_TreeModel;
		readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
		public event Action treeChanged;

		public TreeModel<T> treeModel { get { return m_TreeModel; } }
		public event Action<IList<TreeViewItem>>  beforeDroppingDraggedItems;


		public TreeViewWithTreeModel (TreeViewState state, TreeModel<T> model) : base (state)
		{
			Init (model);
		}

		public TreeViewWithTreeModel (TreeViewState state, MultiColumnHeader multiColumnHeader, TreeModel<T> model)
			: base(state, multiColumnHeader)
		{
			Init (model);
		}

		void Init (TreeModel<T> model)
		{
			m_TreeModel = model;
			m_TreeModel.modelChanged += ModelChanged;
		}

		void ModelChanged ()
		{
			if (treeChanged != null)
				treeChanged ();

			Reload ();
		}

		//构建根节点
		protected override TreeViewItem BuildRoot()
		{
			int depthForHiddenRoot = -1;
			return new TreeViewItem<T>(m_TreeModel.root.id, depthForHiddenRoot, m_TreeModel.root.name, m_TreeModel.root);
		}

        //构建显示的行 TreeViewItem
        protected override IList<TreeViewItem> BuildRows (TreeViewItem root)
		{
			if (m_TreeModel.root == null)
			{
				Debug.LogError ("tree model root is null. did you call SetData()?");
			}

			m_Rows.Clear ();
			if (!string.IsNullOrEmpty(searchString))
			{
				Search (m_TreeModel.root, searchString, m_Rows);
			}
			else
			{
				if (m_TreeModel.root.hasChildren)
					AddChildrenRecursive(m_TreeModel.root, 0, m_Rows);
			}

			// We still need to setup the child parent information for the rows since this 
			// information is used by the TreeView internal logic (navigation, dragging etc)
			SetupParentsAndChildrenFromDepths (root, m_Rows);

			return m_Rows;
		}

		void AddChildrenRecursive (T parent, int depth, IList<TreeViewItem> newRows)
		{
			foreach (T child in parent.children)
			{
				var item = new TreeViewItem<T>(child.id, depth, child.name, child);
				newRows.Add(item);

				if (child.hasChildren)
				{
					if (IsExpanded(child.id))	//展开显示的行
					{
						AddChildrenRecursive (child, depth + 1, newRows);
					}
					else
					{
						item.children = CreateChildListForCollapsedParent();
					}
				}
			}
		}

		void Search(T searchFromThis, string search, List<TreeViewItem> result)
		{
			if (string.IsNullOrEmpty(search))
				throw new ArgumentException("Invalid search: cannot be null or empty", "search");

			const int kItemDepth = 0; // tree is flattened when searching

			Stack<T> stack = new Stack<T>();
			foreach (var element in searchFromThis.children)
				stack.Push((T)element);
			while (stack.Count > 0)
			{
				T current = stack.Pop();
				// 名字匹配的行
				if (current.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					result.Add(new TreeViewItem<T>(current.id, kItemDepth, current.name, current));
				}

				if (current.children != null && current.children.Count > 0)
				{
					foreach (var element in current.children)
					{
						stack.Push((T)element);
					}
				}
			}
			SortSearchResult(result);
		}

		protected virtual void SortSearchResult (List<TreeViewItem> rows)
		{
			rows.Sort ((x,y) => EditorUtility.NaturalCompare (x.displayName, y.displayName)); // sort by displayName by default, can be overriden for multicolumn solutions
		}
	
		protected override IList<int> GetAncestors (int id)
		{
			return m_TreeModel.GetAncestors(id);
		}

		protected override IList<int> GetDescendantsThatHaveChildren (int id)
		{
			return m_TreeModel.GetDescendantsThatHaveChildren(id);
		}


		// Dragging
		//-----------
	
		const string k_GenericDragID = "GenericDragColumnDragging";

		protected override bool CanStartDrag (CanStartDragArgs args)
		{
			return true;
		}

		protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
		{
			if (hasSearch)
				return;

			DragAndDrop.PrepareStartDrag();
			var draggedRows = GetRows().Where(item => args.draggedItemIDs.Contains(item.id)).ToList();
			DragAndDrop.SetGenericData(k_GenericDragID, draggedRows);
			DragAndDrop.objectReferences = new UnityEngine.Object[] { }; // this IS required for dragging to work
			string title = draggedRows.Count == 1 ? draggedRows[0].displayName : "< Multiple >";
			DragAndDrop.StartDrag (title);
		}

		protected override DragAndDropVisualMode HandleDragAndDrop (DragAndDropArgs args)
		{
			// Check if we can handle the current drag data (could be dragged in from other areas/windows in the editor)
			var draggedRows = DragAndDrop.GetGenericData(k_GenericDragID) as List<TreeViewItem>;
			if (draggedRows == null)
				return DragAndDropVisualMode.None;

			// Parent item is null when dragging outside any tree view items.
			switch (args.dragAndDropPosition)
			{
				case DragAndDropPosition.UponItem:
				case DragAndDropPosition.BetweenItems:
					{
						bool validDrag = ValidDrag(args.parentItem, draggedRows);
						if (args.performDrop && validDrag)
						{
							T parentData = ((TreeViewItem<T>)args.parentItem).data;
							OnDropDraggedElementsAtIndex(draggedRows, parentData, args.insertAtIndex == -1 ? 0 : args.insertAtIndex);
						}
						return validDrag ? DragAndDropVisualMode.Move : DragAndDropVisualMode.None;
					}

				case DragAndDropPosition.OutsideItems:
					{
						if (args.performDrop)
							OnDropDraggedElementsAtIndex(draggedRows, m_TreeModel.root, m_TreeModel.root.children.Count);

						return DragAndDropVisualMode.Move;
					}
				default:
					Debug.LogError("Unhandled enum " + args.dragAndDropPosition);
					return DragAndDropVisualMode.None;
			}
		}

		public virtual void OnDropDraggedElementsAtIndex (List<TreeViewItem> draggedRows, T parent, int insertIndex)
		{
			if (beforeDroppingDraggedItems != null)
				beforeDroppingDraggedItems (draggedRows);

			var draggedElements = new List<TreeElement> ();
			foreach (var x in draggedRows)
				draggedElements.Add (((TreeViewItem<T>) x).data);
		
			var selectedIDs = draggedElements.Select (x => x.id).ToArray();
			m_TreeModel.MoveElements (parent, insertIndex, draggedElements);
			SetSelection(selectedIDs, TreeViewSelectionOptions.RevealAndFrame);
		}


		bool ValidDrag(TreeViewItem parent, List<TreeViewItem> draggedItems)
		{
			TreeViewItem currentParent = parent;
			while (currentParent != null)
			{
				if (draggedItems.Contains(currentParent))
					return false;
				currentParent = currentParent.parent;
			}
			return true;
		}
	
	}

}
相关推荐
秋夫人2 小时前
IntelliJ IDEA 中 Editor > General > Appearance 设置:编辑器的视觉外观和行为
java·编辑器·intellij-idea
Hylan_J2 小时前
【VSCode】工作区及设置
ide·vscode·编辑器
两水先木示6 小时前
【Unity3D】Jobs、Burst并行计算裁剪Texture3D物体
unity·jobs·burst
Thomas_YXQ12 小时前
Unity3D Huatuo技术原理剖析详解
unity·unity3d·游戏开发·性能调优·热更新
zh路西法15 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式
橘子遇见BUG19 小时前
Unity Shader学习日记 part 3 线性代数--矩阵变换
学习·线性代数·unity·矩阵·图形渲染
莲动渔舟21 小时前
妙用编辑器:如何使用编辑器的筛选功能更高效的阅读日志
编辑器
神洛华21 小时前
Y3编辑器教程8:资源管理器与存档、防作弊设置
编辑器·游戏引擎·游戏程序
Artistation Game1 天前
一、c#基础
游戏·unity·c#·游戏引擎
成都渲染101云渲染66661 天前
云渲染,Enscape、D5、Lumion渲染提速教程
运维·服务器·unity·电脑·图形渲染·blender·houdini