UFL7-1——UI面板基类

前置知识点

  1. 里氏替换原则
  2. Dictionary​ 相关知识点(C#四部曲之C#进阶中)
  3. 委托 闭包 相关知识点(C#四部曲之C#进阶中)
  4. GetComponentsInChildren​(Unity四部曲之Unity入门中)

实现UI面板基类

主要实现思路:
在基类中完成声明组件、查找组件、监听组件相关功能
让子类可以直接处理事件逻辑,获取指定控件

主要实现内容:

  1. 通用的查找组件功能

    我们可以使用GetComponentsInChildren​方法,来查找面板下所有的UI控件,将其添加到Dictionary<string, UIBehaviour>​字典内统一管理

    注意,往往面板上会存在一般不会使用的同名的默认名控件,如Text (TMP)​,Image​等等
    因此我们声明一个列表,记录各个控件的默认名,当获取到控件和默认名一致时,就跳过该控件

    还有一种情况是,一个UI对象上挂载了多个不同的控件脚本,对于这种情况,我们可以调整控件查找的先后顺序,越重要的越先找
    重要的控件就会先被记录,而后面即使找到了同一UI对象上的另外一个控件,它也不会覆盖原来在字典内的记录的控件
    这样,即使我们需要这个另外一个控件,我们也可以通过记录的控件找到依附的对象,进而找到另外一个控件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /// <summary>
    /// 用于存储所有要用到的UI控件,用里氏替换原则 父类装子类(所有UI控件)
    /// </summary>
    protected Dictionary<string, UIBehaviour> controlDic = new Dictionary<string, UIBehaviour>();

    /// <summary>
    /// 控件的默认名,如果得到的控件名字存在于这个容器,则说明该容器我们不会使用,它只是会起到显示作用的控件
    /// </summary>
    private static List<string> defaultNameList = new List<string>
    {
    "Image",
    "Text (TMP)",
    "RawImage",
    "Background",
    "Checkmark",
    "Label",
    "Text (Legacy)",
    "Arrow",
    "Placeholder",
    "Fill",
    "Handle",
    "Viewport",
    "Scrollbar Horizontal",
    "Scrollbar Vertical"
    };

    protected virtual void Awake()
    {
    FindChildrenControl<Button>();
    FindChildrenControl<Toggle>();
    FindChildrenControl<Slider>();
    FindChildrenControl<InputField>();
    FindChildrenControl<ScrollRect>();
    FindChildrenControl<Dropdown>();
    //即使对象上挂载了多个组件,只要优先找到了重要组件,之后也可以通过重要组件,得到身上其他的挂载的内容
    FindChildrenControl<Text>();
    FindChildrenControl<TextMeshPro>();
    FindChildrenControl<Image>();
    }

    private void FindChildrenControl<T>() where T : UIBehaviour
    {
    T[] controls = this.GetComponentsInChildren<T>(true);
    for (int i = 0; i < controls.Length; i++)
    {
    //通过正在方式,将对应组件记录到字典中
    if (!controlDic.ContainsKey(controls[i].gameObject.name))
    {
    if (!defaultNameList.Contains(controls[i].gameObject.name))
    controlDic.Add(controls[i].gameObject.name, controls[i]);
    }
    }
    }
  2. 通用的添加事件功能

    我们可以在每次找到一个控件时,就根据控件的类型来添加对应的监听方法,监听方法内执行通用的监听方法,通用方法在子类重写
    为了区分不同的控件,我们利用闭包,将装载控件名字的临时变量传入到通用监听方法内,
    这样,子类在重写通用监听方法时可以根据传入的控件名不同执行不同的逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    protected Dictionary<string, UIBehaviour> controlDic = new Dictionary<string, UIBehaviour>();

    private void FindChildrenControl<T>() where T : UIBehaviour
    {
    T[] controls = this.GetComponentsInChildren<T>(true);
    for (int i = 0; i < controls.Length; i++)
    {
    string controlName = controls[i].gameObject.name;
    //通过正在方式,将对应组件记录到字典中
    if (!controlDic.ContainsKey(controlName))
    {
    if (!defaultNameList.Contains(controlName))
    {
    controlDic.Add(controlName, controls[i]);
    //根据控件的类型,决定是否加事件监听
    if (controls[i] is Button btn)
    {
    btn.onClick.AddListener(() =>
    {
    ClickButton(controlName);
    });
    }
    else if (controls[i] is Toggle tog)
    {
    tog.onValueChanged.AddListener((value) =>
    {
    ToggleValueChange(controlName, value);
    });
    }
    else if (controls[i] is Slider slider)
    {
    slider.onValueChanged.AddListener((value) =>
    {
    SliderValueChange(controlName, value);
    });
    }
    }
    }
    }
    }

    protected virtual void ClickButton(string btnName) { }

    protected virtual void SliderValueChange(string sliderName, float value) { }

    protected virtual void ToggleValueChange(string toggleName, bool value) { }

    使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class BeginPanel : BasePanel
    {
    private void Start()
    {
    foreach (string item in controlDic.Keys)
    {
    print(item);
    }
    }

    protected override void ClickButton(string btnName)
    {
    switch (btnName)
    {
    case "btnBegin":
    print("开始按钮被点击");
    break;
    case "btnSetting":
    print("设置按钮被点击");
    break;
    case "btnQuit":
    print("退出按钮被点击");
    break;
    }
    }
    }
  3. 显示面板、隐藏面板时的逻辑执行虚函数

    将基类声明为抽象类,将显示和隐藏方法声明为抽象方法,这样,子类面板类就必须要实现它们

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public abstract class BasePanel : MonoBehaviour
    {
    /// <summary>
    /// 面板显示时会调用的逻辑
    /// </summary>
    public abstract void ShowMe();

    /// <summary>
    /// 面板隐藏时会调用的逻辑
    /// </summary>
    public abstract void HideMe();
    }
  4. 获取指定组件的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public T GetControl<T>(string name) where T : UIBehaviour
    {
    if (controlDic.ContainsKey(name))
    {
    T control = controlDic[name] as T;
    if (control == null)
    {
    Debug.LogError($"不存在对应名字{name}类型为{typeof(T)}的组件");
    return null;
    }
    return control;
    }
    else
    {
    Debug.LogError($"不存在对应名字{name}的组件");
    return null;
    }
    }

等等

关键点:制定控件命名规则

  1. 要使用的组件需要改名
  2. 不使用只用于显示的组件可以使用默认名

使用示例

要挂载到面板对象上面板类需要继承BasePanel​,需要重写ShowMe​和HideMe​供UI管理器显示面板时调用
如果要为面板上的控件添加监听方法,需要就需要重写通用监听方法,通过传入的控件名来执行不同的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using UnityEngine.UI;

public class BeginPanel : BasePanel
{
private void Start()
{
string name = GetControl<Button>("btnBegin").name;
print(name);
}

protected override void ClickButton(string btnName)
{
switch (btnName)
{
case "btnBegin":
print("开始按钮被点击");
break;
case "btnSetting":
print("设置按钮被点击");
break;
case "btnQuit":
print("退出按钮被点击");
break;
}
}

public override void ShowMe()
{
}

public override void HideMe()
{
}
}

具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public abstract class BasePanel : MonoBehaviour
{
/// <summary>
/// 用于存储所有要用到的UI控件,用里氏替换原则 父类装子类(所有UI控件)
/// </summary>
protected Dictionary<string, UIBehaviour> controlDic = new Dictionary<string, UIBehaviour>();

/// <summary>
/// 控件的默认名,如果得到的控件名字存在于这个容器,则说明该容器我们不会使用,它只是会起到显示作用的控件
/// </summary>
private static List<string> defaultNameList = new List<string>
{
"Image",
"Text (TMP)",
"RawImage",
"Background",
"Checkmark",
"Label",
"Text (Legacy)",
"Arrow",
"Placeholder",
"Fill",
"Handle",
"Viewport",
"Scrollbar Horizontal",
"Scrollbar Vertical"
};

protected virtual void Awake()
{
FindChildrenControl<Button>();
FindChildrenControl<Toggle>();
FindChildrenControl<Slider>();
FindChildrenControl<InputField>();
FindChildrenControl<ScrollRect>();
FindChildrenControl<Dropdown>();
//即使对象上挂载了多个组件,只要优先找到了重要组件
//之后也可以通过重要组件,得到身上其他的挂载的内容
FindChildrenControl<Text>();
FindChildrenControl<TextMeshPro>();
FindChildrenControl<Image>();
}

/// <summary>
/// 面板显示时会调用的逻辑
/// </summary>
public abstract void ShowMe();

/// <summary>
/// 面板隐藏时会调用的逻辑
/// </summary>
public abstract void HideMe();

/// <summary>
/// 获取指定名字以及指定类型的组件
/// </summary>
/// <typeparam name="T">组件类型</typeparam>
/// <param name="name">组件名字</param>
/// <returns>找到的组件(如果找不到会返回null)</returns>
public T GetControl<T>(string name) where T : UIBehaviour
{
if (controlDic.ContainsKey(name))
{
T control = controlDic[name] as T;
if (control == null)
{
Debug.LogError($"不存在对应名字{name}类型为{typeof(T)}的组件");
return null;
}
return control;
}
else
{
Debug.LogError($"不存在对应名字{name}的组件");
return null;
}
}

private void FindChildrenControl<T>() where T : UIBehaviour
{
T[] controls = this.GetComponentsInChildren<T>(true);
for (int i = 0; i < controls.Length; i++)
{
string controlName = controls[i].gameObject.name;
//通过正在方式,将对应组件记录到字典中
if (!controlDic.ContainsKey(controlName))
{
if (!defaultNameList.Contains(controlName))
{
controlDic.Add(controlName, controls[i]);
//根据控件的类型,决定是否加事件监听
if (controls[i] is Button btn)
{
btn.onClick.AddListener(() =>
{
ClickButton(controlName);
});
}
else if (controls[i] is Toggle tog)
{
tog.onValueChanged.AddListener((value) =>
{
ToggleValueChange(controlName, value);
});
}
else if (controls[i] is Slider slider)
{
slider.onValueChanged.AddListener((value) =>
{
SliderValueChange(controlName, value);
});
}
}
}
}
}

protected virtual void ClickButton(string btnName) { }

protected virtual void SliderValueChange(string sliderName, float value) { }

protected virtual void ToggleValueChange(string toggleName, bool value) { }
}