功能说明
今天要实现的其实是一个使用范围很广的UI组件,即App中常见的Tablebar/导航栏,看到U3D方面没有人写这个,我就把自己的设计流程分享一下。先看效果:
设计思路上还是挺简单的,将按钮与Panel进行绑定,当某个按钮触发点击事件后,就显示对应Panel而隐藏其余的Panel。
一、UI设计
1. 层级设计
如下图设计层级。其中,Table Panel、Table Bar和Container只需要构造空物体对象即可。
2. UI布局
对于Tablebar而言,将布局更改为底端扩展,并添加一个Horizontal Layout Group组件以保证新加子物体的动态适配。同时,将Horizontal Layout Group组件中的Control Child Size的两个属性都选中,以自动缩放子对象内容。之后,向Container中添加Panel的同时,也向Tablebar内加入按钮,按钮都会自动排版。
二、功能实现
1. Tablebar
新建一个脚本“TableBar”,将其添加到Table Panel对象下。Tablebar的主要功能有两个:在脚本开始时绑定按钮和Panel,在按钮触发时切换Panel,其完整的代码如下:
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 导航栏组件类。
/// </summary>
public class TableBar : MonoBehaviour
{
#region 可视变量
public int size = 0;
public int defaultIndex = 0;
public Button[] buttons = new Button[100];
public GameObject[] panels = new GameObject[100];
#endregion
#region 成员变量
private int selected = 0; // 当前选择的面板序号
#endregion
#region 私有方法
/// <summary>
/// 脚本实例化后立即触发。
/// </summary>
private void Awake()
{
selected = defaultIndex;
// 绑定按钮监听事件
for (int i = 0; i < size; i++)
{
int temp = i;
panels[temp].SetActive(false);
buttons[temp].onClick.AddListener(delegate () { OnClick(temp); });
}
}
/// <summary>
/// 当脚本生效时触发。
/// </summary>
private void OnEnable()
{
// 仅激活默认面板
SelectPanel(defaultIndex);
}
/// <summary>
/// 按钮被单击时触发。
/// </summary>
/// <param name="index">按钮序号。</param>
private void OnClick(int index)
{
SelectPanel(index);
}
/// <summary>
/// 切换面板。
/// </summary>
/// <param name="index">面板序号。</param>
private void SelectPanel(int index)
{
panels[selected].SetActive(false);
panels[index].SetActive(true);
selected = index;
}
#endregion
}
2. Tablebar的Editor设计
重写Tablebar的inspector面板,让组件更加简洁易用。如下图所示,Size代表绑定数量,Switch Elements中是按钮和Panel,Default Index表示默认情况下显示的Panel序号。
要实现以上的面板效果,只需要新建一个继承了Editor的TableBarEditor类,并重载OnInspectorGUI()方法即可,TableBarEditor的完整代码如下:
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 导航栏组件的面板类。
/// </summary>
[CustomEditor(typeof(TableBar))]
public class TableBarEditor : Editor
{
#region 成员变量
private TableBar tableBar;
private bool showSwitch = true;
#endregion
#region 序列化变量
private SerializedProperty sizeSerialize;
private SerializedProperty defaultIndexSerialize;
#endregion
#region 私有方法
/// <summary>
/// 当脚本生效时触发。
/// </summary>
private void OnEnable()
{
// 获取对象
tableBar = (TableBar)target;
// 保存数据
sizeSerialize = serializedObject.FindProperty("Size");
defaultIndexSerialize = serializedObject.FindProperty("Default Index");
}
/// <summary>
/// 当面板绘制时触发。
/// </summary>
public override void OnInspectorGUI()
{
// 垂直布局
EditorGUILayout.BeginVertical();
BaseGUI();
EditorGUILayout.EndVertical();
// 保存数据
serializedObject.ApplyModifiedProperties();
// 当面板修改后触发
if (GUI.changed)
EditorUtility.SetDirty(target);
}
/// <summary>
/// 基础属性。
/// </summary>
private void BaseGUI()
{
tableBar.size = EditorGUILayout.IntField("Size", tableBar.size);
showSwitch = EditorGUILayout.Foldout(showSwitch, "Switch Elements");
if (showSwitch)
{
for (int i = 0; i < tableBar.size; i++)
{
tableBar.buttons[i] = (Button)EditorGUILayout.ObjectField("Button " + i, tableBar.buttons[i], typeof(Button), true);
tableBar.panels[i] = (GameObject)EditorGUILayout.ObjectField("Panel " + i, tableBar.panels[i], typeof(GameObject), true);
EditorGUILayout.Space();
}
}
tableBar.defaultIndex = EditorGUILayout.IntField("Default Index", tableBar.defaultIndex);
}
#endregion
}
#endif