编辑器控件自适应
之前已经尝试分析 EditorGUILayout
和 EditorGUI
两个类的区别和适用情况。但在实际代码中可以发现几个问题:
EditorGUI
理论上适用于编辑器窗口的扩展,因为开发者可以通过一个Rect
参数来定义控件的位置和长宽,灵活度很高。而事实证明,EditorGUI
确实可以基本胜任编辑器窗口的扩展,但是随着布局的多变,代码实现已经变得很复杂。例如在 Prefab Center 中为了生成一个网格状的显示样式,每次绘制都要进行以下计算:var buttonWidth = (position.width - 25) / 4; var buttonHeight = buttonWidth + 15; // ... foreach (var item in _prefabItems) { // ... var buttonLeft = 5 + (i % 4) * (buttonWidth + 5); var buttonTop = 27 + (i / 4) * (buttonHeight + 5); }
- 尝试后可以发现,
EditorGUILayout
在编辑器窗口中也是有效的,并且可以生成自动布局的控件,也非常适用于编辑器窗口的扩展。
- 代码中已经多次出现过
GUILayout
和GUI
类的使用。
如何自适应
通过 Unity 提供的内部方法或者开发者自行实现,控件的自适应可以有很多种实现方式。以如何扩展出网格样式为例:
- 手动实现
private void DrawGridWithRawScripts () { DrawControls (); float buttonWidth = (position.width - 5) / _column - 5; float buttonHeight = (position.height - 53) / _row - 5; for (int i = 0; i < _row; i++) { float buttonTop = 53 + (i % _row) * (buttonHeight + 5); for (int j = 0; j < _column; j++) { float buttonLeft = 5 + (j % _column) * (buttonWidth + 5); GUI.Button (new Rect (buttonLeft, buttonTop, buttonWidth, buttonHeight), string.Format ("Button {0}-{1}", i, j)); } } }
private void DrawControls () { GUI.BeginGroup (new Rect (5, 5, position.width - 10, 43), GUIContent.none, "box"); _row = EditorGUI.IntSlider (new Rect (3, 3, position.width - 16, 17), "row", _row, 0, 30); _column = EditorGUI.IntSlider (new Rect (3, 23, position.width - 16, 17), "column", _column, 0, 30); GUI.EndGroup (); }
- 自动布局的
GUILayout
或EditorGUILayout
private void DrawGridWithGuiLayout () { _row = EditorGUILayout.IntSlider ("row", _row, 0, 30); _column = EditorGUILayout.IntSlider ("column", _column, 0, 30); for (int i = 0; i < _row; i++) { GUILayout.BeginHorizontal (); for (int j = 0; j < _column; j++) { GUILayout.Button (string.Format ("Button {0}-{1}", i, j)); } GUILayout.EndHorizontal (); } }
SelectionGrid
private void DrawGridWithSelectionGrid () { _row = EditorGUILayout.IntSlider ("row", _row, 0, 30); _column = EditorGUILayout.IntSlider ("column", _column, 0, 30); List<string> contents = new List<string> (); for (int i = 0; i < _row; i++) { for (int j = 0; j < _column; j++) { contents.Add (string.Format ("Button {0}-{1}", i, j)); } } GUILayout.SelectionGrid (-1, contents.ToArray (), _column); }
上述实现方法各有特点:
手动实现利用了 EditorGUI
和 GUI
类的灵活性,只要计算好网格中每个按钮的具体位置,然后传入对应的方法中,就可以实现各种样式。但是布局和样式有可能多变而且不具规律性,所以需要考虑并计算多种情况下的位置和尺寸,往往会导致代码规模变大,而且各种数字使得代码的可读性和可维护性变差。
为了提高手动实现的效率和代码的质量,可以考虑维护一些全局的变量和方法,自动计算下一个控件的位置和尺寸。个人认为自动布局的 GUILayout
类就是这么处理的。
EditorGUILayout
和 GUILayout
则是已经计算好了各个按钮的位置和尺寸,只需代码简单定义绘制的样式即可。可以看出,EditorGUILayout
和 GUILayout
默认是不会在垂直方向上填充整个窗口的,每个按钮都有固定的高度(除非通过代码明确修改其高度)。另外,按钮还有一个固定的最小宽度,一般会保证内部的文本都可以显示,如果窗口过于窄,部分按钮就会被截断。
SelectionGrid
则是已经封装好的专门绘制网格的方法,最少只需要传入两个参数:显示在按钮中的 GUIContent
数组,和网格中每一行显示多少个按钮。SelectionGrid
生成的窗口效果和 GUILayout
一样,只是不具有固定的最小宽度,会讲所有按钮都显示在窗口中,即使文本有所扭曲。
如何选择
以往的例子中,EditorGUILayout
和 EditorGUI
,GUILayout
和 GUI
交替出现,究竟是否存在一个明确的取舍,即什么时候选用哪个类更合适。笔者认为这是一个误区,从开发工具的角度上说,更应该从需求的角度思考,也就是只要可以达成目标,没有必要说必须使用哪一个类,开发者可以进行各种尝试,然后按照需要选择一个最合适的。
不过根据名字和经验,我们可以得出一些大致上的结论:
-
XXXLayout
更适用于自动布局的情况,开发小工具时使用比较合适。
-
EditorXXX
适用于编辑器的扩展,而GUILayout
和GUI
不仅可以扩展编辑器,还可以用于扩展 Scene View 等非编辑器的情况。
GUI
包含的方法更全、更底层。