抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

简介

UI 界面在初始化的时候,我们往往需要获取 UI 组件,然后在 UI 组件的内容发生变化的时候对 UI 组件进行赋值。我们需要同时管理 UI 组件和数据,有时候会觉得很繁琐。于是我想到利用特性 Attribute 来反射初始化 UI 组件,使其和数据绑定在一起,更新数据就可以自动更新 UI。

在此基础上,我又拓展了 UI 动作绑定和 UI 监听绑定,这样只需要对方法标记特性,就可以自动完成对应的绑定工作。

本框架支持自定义拓展功能,具体的使用说明和拓展规则见:

原理

UI 数据类型

为了实现 UI 数据更新的同时更新 UI 界面,并且不需要我们自己在每个 UI 界面属性中设置 setter 响应事件,我们就需要一个新的类型来表示数据。

新的数据类型内容很简单,就是保存原始数据,以及设置一个数据更改时的响应函数 _onValueChange,一个 UI 更改时的响应函数 _onUIChange,在每次数据变化或 UI 变化的时候调用即可。

原理就是在值改变的时候在 set 方法中调用修改 UI 的委托;在通过 Get() 获取值的时候调用同步 UI 改变后的值的委托。

不过 List 类型有所不同,一般 List 不会在 UI 上直接修改,所以只有 _onValueChange

UI 数据类型 String,大部分情况下的选择。

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
using System;

public class StringUIProp
{
public string _value;

private string val
{
get
{
return _value;
}
set
{
_value = value;
InvokeUI();
}
}

private Action<string> _onValueChange = null;
private Func<string> _onUIChange = null;

public StringUIProp() { }

public StringUIProp(string value)
{
_value = value;
}

public void Set(string value)
{
this.val = value;
}

public void Set<T>(T value)
{
this.val = value.ToString();
}

public string Get()
{
InvokeValue();
return this.val;
}

public void InvokeValue()
{
if (_onUIChange != null)
{
_value = _onUIChange.Invoke();
}
}

public void InvokeUI()
{
_onValueChange?.Invoke(val);
}

public override string ToString()
{
return val;
}
}

UI 数据类型 Double,拖拽条之类的情况下使用。

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
using System;

public class DoubleUIProp
{
public double _value;

private double val
{
get { return _value; }
set
{
_value = value;
InvokeUI();
}
}

private Action<double> _onValueChange = null;
private Func<double> _onUIChange = null;

public DoubleUIProp()
{
}

public DoubleUIProp(double value)
{
_value = value;
}

public void Set(double value)
{
this.val = value;
}

public double Get()
{
InvokeValue();
return this.val;
}

public void InvokeValue()
{
if (_onUIChange != null)
{
_value = _onUIChange.Invoke();
}
}

public void InvokeUI()
{
_onValueChange?.Invoke(val);
}

public override string ToString()
{
return val.ToString();
}
}

List 的 UI 数据类型,响应事件和标准 UI 类型不同,并且数据类型为泛型List<T>以适应各种数据类型。

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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIListProp<T>
{
private List<T> _value;

private List<T> val
{
get
{
return _value;
}
set
{
_value = value;
Invoke();
}
}

public int Count { get; set; }

private Action<int> _onValueChange = null;

public UIListProp() { }

public UIListProp(List<T> value)
{
//this.val = value;
_value = value;
}

public List<T> Get()
{
return val;
}

public void Set(List<T> value)
{
val = value;
Count = val.Count;
}

public void Invoke()
{
if (val != null)
{
_onValueChange?.Invoke(val.Count);
}
}

public override string ToString()
{
string res = "[";
foreach (var item in val)
{
res += item.ToString() + ",";
}

res += "]";
return res;
}
}

特性初始化

除了用于绑定 UI 的特性外,我们还可以搭配辅助特性,例如 UIColorUIOption 等,辅助初始化的设置,具体用法参考 使用方法

绑定 UI 组件,需要传入组件类型和路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 绑定UI组件
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class UICompBind :Attribute
{
public string _type;
public string _path;
public UICompBind(string type,string path)
{
_type = type;
_path = path;
}
}

绑定 UI 数据,需要传入组件类型和路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

/// <summary>
/// 绑定UI组件和数据,使其根据数据修改UI
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class UIDataBind : Attribute
{
public string _type;
public string _path;
public UIDataBind(string type, string path)
{
_type = type;
_path = path;
}
}

绑定 UI 动作,需要传入组件类型和路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

/// <summary>
/// 绑定UI组件和动作事件,使其支持交互
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UIActionBind : Attribute
{
public string _type;

public string _path;

public UIActionBind(string type,string path)
{
_type = type;
_path = path;
}
}

绑定 UI 监听事件,只需要传入监听事件的名称。监听事件的 id 是界面 id,自动传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 绑定UI监听事件
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UIListenerBind : Attribute
{
public string _name;

public UIListenerBind(string name)
{
_name = name;
}
}

绑定 UI 类,需要传入类行为和额外参数。

1
2
3
4
5
6
7
8
9
10
11
12
using System;

[AttributeUsage(AttributeTargets.Class)]
public class UIClassBind : Attribute
{
public UIClass type;

public UIClassBind(UIClass type)
{
this.type = type;
}
}

反射绑定

我们通过基类初始化获取 Type,然后通过 Type 获取 properties 和 method(如果有需要也可以自己添加 fields),之后通过遍历属性和方法,在其循环体内获取对应属性或方法的特性标签,判断标签类型是否为 UI 绑定类型,是则进行对应的操作。

不同类型的组件绑定方法可自行添加或修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void BindComp(PropertyInfo prop, object attr, GComponent main)
{
UICompBind uiBind = (UICompBind)attr;

switch (uiBind._type)
{
case "Comp":
GComponent comp = FguiUtils.GetUI<GComponent>(main, uiBind._path);
prop.SetValue(this, comp);
break;
//......
}
}
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
private void BindData(PropertyInfo prop, object attr, GComponent main)
{
UIDataBind uiBind = (UIDataBind)attr;

var onValueChange = prop.PropertyType.GetField("_onValueChange", _flag);
var onUIChange = prop.PropertyType.GetField("_onUIChange", _flag);

var value = prop.GetValue(this);
if (value == null)
{
if (prop.PropertyType.Equals(typeof(UIProp)))
{
value = new UIProp();
}
else
{
Type genericType = typeof(UIListProp<>).MakeGenericType(prop.PropertyType.GenericTypeArguments);
value = Activator.CreateInstance(genericType);
}
prop.SetValue(this, value);
}

switch (uiBind._type)
{
case "TextField":
GTextField textField = FguiUtils.GetUI<GTextField>(main, uiBind._path);
void ActionText(string data)
{
if (textField != null)
{
textField.text = data;
}
}

string ActionTextUI()
{
return textField.text;
}

onValueChange?.SetValue(value, (Action<string>)ActionText);
onUIChange?.SetValue(value, (Func<string>)ActionTextUI);
break;
//......
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void BindAction(MethodInfo method, object attr, GComponent main)
{
UIActionBind uiBind = (UIActionBind)attr;
GObject obj = FguiUtils.GetUI<GObject>(main, uiBind._path);
switch (uiBind._type)
{
case "Click":
var methondParamsClick = method.GetParameters();
Delegate click = null;
if (methondParamsClick.Length == 0)
{
click = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.onClick.Set((EventCallback0)click);
}
else
{
click = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.onClick.Set((EventCallback1)click);
}
break;
//......
}
}
1
2
3
4
5
6
private void BindListener(MethodInfo method, object attr, string id)
{
UIListenerBind uiBind = (UIListenerBind)attr;
var eventFunc = Delegate.CreateDelegate(typeof(Action<ArrayList>), this, method);
EventManager.AddListening(id, uiBind._name, (Action<ArrayList>)eventFunc);
}

本方法与其他方法不同,其他方法在 UIBase 中初始化,本方法在 BaseView 中初始化。

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
/// <summary>
/// 绑定 类
/// </summary>
/// <param name="attr"></param>
private void BindClass(object attr)
{
UIClassBind uiClassBind = (UIClassBind)attr;

switch (uiClassBind.type)
{
case UIClass.Model:
if (_model == null)
{
_model = new GGraph();
_model.displayObject.gameObject.name = id + "_" + name + "_Model";

UIColor colorAttr = _type.GetCustomAttribute<UIColor>();
Color color = new Color(0, 0, 0, 0);
if (colorAttr != null)
{
color = colorAttr.color;
}

Vector2 size = GRoot.inst.size;
_model.DrawRect(size.x, size.y, 0, new Color(), color);
}

UIManager.Ins().SetModel(uiNode, _model);

_model.onClick.Set(() =>
{
if (uiClassBind.extra.Length > 0 && uiClassBind.extra[0] == "Hide")
{
Hide();
main.AddChild(_model);
}
});

break;
//其他case
}
}

查找 UI

绑定组件必不可少的就是查找 UI,本项目使用 FGUI,因此查找 UI 也是根据 FGUI 写的。

根据传入的路径,拆分为每一层具体的索引,然后判断索引是数字还是字符串,是数字就调用 GetChildAt,是字符串就调用 GetChild。

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
/// <summary>
/// 根据路径获取UI组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comp"></param>
/// <param name="path"></param>
/// <returns></returns>
public static T GetUI<T>(GComponent comp, string path) where T : GObject
{
string[] paths = path.Split('/');
GObject res = null;
GComponent parent = comp;
foreach (string s in paths)
{
if (s == "") continue;
int output;
bool isNumeric = int.TryParse(s, out output);
if (isNumeric)
{
res = parent.GetChildAt(output);
}
else
{
res = parent.GetChild(s);
}
if (res == null)
{
ConsoleUtils.Error("ui路径错误", path);
return null;
}
if (res is GComponent)
{
parent = res.asCom;
}
else
{
break;
}
}
return res as T;
}

缓动动画

本项目实现了一个简单的缓动动画系统,用于控制 UI 窗口的进入和退出缓动动画。

缓动动画的原理很简单,就是在持续时间内每帧做插值。

使用 Laya 引擎内的插值函数修改而成。

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using System;
using UnityEngine;

public enum TweenEaseType
{
Linear,
SineIn,
SineOut,
SineInOut,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
QuartIn,
QuartOut,
QuartInOut,
QuintIn,
QuintOut,
QuintInOut,
ExpoIn,
ExpoOut,
ExpoInOut,
CircIn,
CircOut,
CircInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
Custom
}

public class EaseUtil
{
const float _PiOver2 = Mathf.PI * 0.5f;
const float _TwoPi = Mathf.PI * 2;

internal static float Evaluate(TweenEaseType TweenEaseType, float time, float duration, float overshootOrAmplitude,
float period)
{
if (duration <= 0)
return 1;

switch (TweenEaseType)
{
case TweenEaseType.Linear:
return time / duration;
case TweenEaseType.SineIn:
return -(float)Math.Cos(time / duration * _PiOver2) + 1;
case TweenEaseType.SineOut:
return (float)Math.Sin(time / duration * _PiOver2);
case TweenEaseType.SineInOut:
return -0.5f * ((float)Math.Cos(Mathf.PI * time / duration) - 1);
case TweenEaseType.QuadIn:
return (time /= duration) * time;
case TweenEaseType.QuadOut:
return -(time /= duration) * (time - 2);
case TweenEaseType.QuadInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time;
return -0.5f * ((--time) * (time - 2) - 1);
case TweenEaseType.CubicIn:
return (time /= duration) * time * time;
case TweenEaseType.CubicOut:
return ((time = time / duration - 1) * time * time + 1);
case TweenEaseType.CubicInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time;
return 0.5f * ((time -= 2) * time * time + 2);
case TweenEaseType.QuartIn:
return (time /= duration) * time * time * time;
case TweenEaseType.QuartOut:
return -((time = time / duration - 1) * time * time * time - 1);
case TweenEaseType.QuartInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time * time;
return -0.5f * ((time -= 2) * time * time * time - 2);
case TweenEaseType.QuintIn:
return (time /= duration) * time * time * time * time;
case TweenEaseType.QuintOut:
return ((time = time / duration - 1) * time * time * time * time + 1);
case TweenEaseType.QuintInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time * time * time;
return 0.5f * ((time -= 2) * time * time * time * time + 2);
case TweenEaseType.ExpoIn:
return (time == 0) ? 0 : (float)Math.Pow(2, 10 * (time / duration - 1));
case TweenEaseType.ExpoOut:
if (time == duration) return 1;
return (-(float)Math.Pow(2, -10 * time / duration) + 1);
case TweenEaseType.ExpoInOut:
if (time == 0) return 0;
if (time == duration) return 1;
if ((time /= duration * 0.5f) < 1) return 0.5f * (float)Math.Pow(2, 10 * (time - 1));
return 0.5f * (-(float)Math.Pow(2, -10 * --time) + 2);
case TweenEaseType.CircIn:
return -((float)Math.Sqrt(1 - (time /= duration) * time) - 1);
case TweenEaseType.CircOut:
return (float)Math.Sqrt(1 - (time = time / duration - 1) * time);
case TweenEaseType.CircInOut:
if ((time /= duration * 0.5f) < 1) return -0.5f * ((float)Math.Sqrt(1 - time * time) - 1);
return 0.5f * ((float)Math.Sqrt(1 - (time -= 2) * time) + 1);
case TweenEaseType.ElasticIn:
float s0;
if (time == 0) return 0;
if ((time /= duration) == 1) return 1;
if (period == 0) period = duration * 0.3f;
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s0 = period / 4;
}
else s0 = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

return -(overshootOrAmplitude * (float)Math.Pow(2, 10 * (time -= 1)) *
(float)Math.Sin((time * duration - s0) * _TwoPi / period));
case TweenEaseType.ElasticOut:
float s1;
if (time == 0) return 0;
if ((time /= duration) == 1) return 1;
if (period == 0) period = duration * 0.3f;
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s1 = period / 4;
}
else s1 = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

return (overshootOrAmplitude * (float)Math.Pow(2, -10 * time) *
(float)Math.Sin((time * duration - s1) * _TwoPi / period) + 1);
case TweenEaseType.ElasticInOut:
float s;
if (time == 0) return 0;
if ((time /= duration * 0.5f) == 2) return 1;
if (period == 0) period = duration * (0.3f * 1.5f);
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s = period / 4;
}
else s = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

if (time < 1)
return -0.5f * (overshootOrAmplitude * (float)Math.Pow(2, 10 * (time -= 1)) *
(float)Math.Sin((time * duration - s) * _TwoPi / period));
return overshootOrAmplitude * (float)Math.Pow(2, -10 * (time -= 1)) *
(float)Math.Sin((time * duration - s) * _TwoPi / period) * 0.5f + 1;
case TweenEaseType.BackIn:
return (time /= duration) * time * ((overshootOrAmplitude + 1) * time - overshootOrAmplitude);
case TweenEaseType.BackOut:
return ((time = time / duration - 1) * time *
((overshootOrAmplitude + 1) * time + overshootOrAmplitude) + 1);
case TweenEaseType.BackInOut:
if ((time /= duration * 0.5f) < 1)
return 0.5f * (time * time *
(((overshootOrAmplitude *= (1.525f)) + 1) * time - overshootOrAmplitude));
return 0.5f * ((time -= 2) * time *
(((overshootOrAmplitude *= (1.525f)) + 1) * time + overshootOrAmplitude) + 2);
case TweenEaseType.BounceIn:
return Bounce.EaseIn(time, duration);
case TweenEaseType.BounceOut:
return Bounce.EaseOut(time, duration);
case TweenEaseType.BounceInOut:
return Bounce.EaseInOut(time, duration);

default:
return -(time /= duration) * (time - 2);
}
}
}

/// <summary>
/// This class contains a C# port of the easing equations created by Robert Penner (http://robertpenner.com/easing).
/// </summary>
static class Bounce
{
/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in: accelerating from zero velocity.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseIn(float time, float duration)
{
return 1 - EaseOut(duration - time, duration);
}

/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out: decelerating from zero velocity.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseOut(float time, float duration)
{
if ((time /= duration) < (1 / 2.75f))
{
return (7.5625f * time * time);
}

if (time < (2 / 2.75f))
{
return (7.5625f * (time -= (1.5f / 2.75f)) * time + 0.75f);
}

if (time < (2.5f / 2.75f))
{
return (7.5625f * (time -= (2.25f / 2.75f)) * time + 0.9375f);
}

return (7.5625f * (time -= (2.625f / 2.75f)) * time + 0.984375f);
}

/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in/out: acceleration until halfway, then deceleration.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseInOut(float time, float duration)
{
if (time < duration * 0.5f)
{
return EaseIn(time * 2, duration) * 0.5f;
}

return EaseOut(time * 2 - duration, duration) * 0.5f + 0.5f;
}
}

包含了缓动需要的所有数据和方法。

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 System;
using UnityEngine;

public class UITween
{
public int id;

public float duration;

private int _time;

public bool isStop;

public Action<float> updater;

public Action callback;

public void Update(int delta)
{
_time += delta;
if (_time <= duration)
{
updater?.Invoke(_time);
}
else
{
updater?.Invoke(duration);
callback?.Invoke();
isStop = true;
updater = null;
callback = null;
}
}
}

缓动动画需要依托 MonoBehaviour 来进行更新,因此我们设置一个脚本专门用于更新缓动。

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
using System.Collections.Generic;
using UnityEngine;

public class TweenUpdater : MonoBehaviour
{
private Dictionary<int,UITween> _actions = new Dictionary<int,UITween>();

private List<int> _removeActionIndexes = new List<int>();

void Update()
{
int delta = (int)(Time.deltaTime * 1000);
foreach (var action in _actions)
{
UITween vt = action.Value;

if (vt.isStop)
{
_removeActionIndexes.Add(vt.id);
}
else
{
vt.Update(delta);
}
}

for (int i = _removeActionIndexes.Count - 1; i >= 0; i--)
{
_actions.Remove(_removeActionIndexes[i]);
_removeActionIndexes.RemoveAt(i);
}
}

public void AddTween(UITween tween)
{
_actions.Add(tween.id,tween);
}

public void StopTween(int id)
{
UITween tween;
_actions.TryGetValue(id, out tween);

if (tween != null)
{
tween.isStop = true;
}
}
}

本类为单例模式,实现了缓动的创建和停止。

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System;
using FairyGUI;
using UnityEngine;

public class UITweenManager : Singleton<UITweenManager>
{
private TweenUpdater _updater;

private int _id = 0;

public void Init()
{
GameObject obj = new GameObject();
obj.name = "TweenUpdater";
_updater = obj.AddComponent<TweenUpdater>();
}

public int AddTween(GObject obj, TweenTarget target, float end, int duration,
TweenEaseType ease = TweenEaseType.Linear, Action callback = null)
{
UITween vt = new UITween();
vt.id = _id++;
vt.duration = duration;
vt.updater = GenerateUpdater(obj, target, end, duration, ease);
vt.callback = callback;

_updater.AddTween(vt);

return vt.id;
}

public int AddTween(GObject obj, TweenTarget target, Vector2 end, int duration,
TweenEaseType ease = TweenEaseType.Linear, Action callback = null)
{
UITween vt = new UITween();
vt.id = _id++;
vt.duration = duration;
vt.updater = GenerateUpdater(obj, target, end, duration, ease);
vt.callback = callback;

_updater.AddTween(vt);
return vt.id;
}

public void StopTween(int id)
{
_updater.StopTween(id);
}

private Action<float> GenerateUpdater(GObject obj, TweenTarget target, float end, float duration,
TweenEaseType ease)
{
float origin = 0;
switch (target)
{
case TweenTarget.X:
origin = obj.x;
break;
case TweenTarget.Y:
origin = obj.y;
break;
case TweenTarget.ScaleX:
origin = obj.scaleX;
break;
case TweenTarget.ScaleY:
origin = obj.scaleY;
break;
case TweenTarget.Rotation:
origin = obj.rotation;
break;
case TweenTarget.Alpha:
origin = obj.alpha;
break;
case TweenTarget.Heihgt:
origin = obj.height;
break;
case TweenTarget.Width:
origin = obj.width;
break;
}

void Action(float time)
{

float ratio = EaseUtil.Evaluate(ease, time, duration, 1.7f, 0);
switch (target)
{
case TweenTarget.X:
obj.x = origin + ratio * end;
break;
case TweenTarget.Y:
obj.y = origin + ratio * end;
break;
case TweenTarget.ScaleX:
obj.scaleX = origin + ratio * end;
break;
case TweenTarget.ScaleY:
obj.scaleY = origin + ratio * end;
break;
case TweenTarget.Rotation:
obj.rotation = origin + ratio * end;
break;
case TweenTarget.Alpha:
obj.alpha = origin + ratio * end;
break;
case TweenTarget.Heihgt:
obj.height = origin + ratio * end;
break;
case TweenTarget.Width:
obj.width = origin + ratio * end;
break;
}
}

return Action;
}

private Action<float> GenerateUpdater(GObject obj, TweenTarget target, Vector2 end, float duration,
TweenEaseType ease)
{
Vector2 origin = Vector2.zero;
switch (target)
{
case TweenTarget.Position:
origin = obj.xy;
break;
case TweenTarget.Scale:
origin = obj.scale;
break;
case TweenTarget.Size:
origin = obj.size;
break;
}

void Action(float time)
{
float ratio = EaseUtil.Evaluate(ease, time, duration, 1.7f, 0);
switch (target)
{
case TweenTarget.Position:
obj.xy = origin + ratio * end;
break;
case TweenTarget.Scale:
obj.scale = origin + ratio * end;
break;
case TweenTarget.Size:
obj.size = origin + ratio * end;
break;
}
}

return Action;
}
}

UI 管理器

UI 节点通过树结构进行存储。

逻辑情况下,同级 UI 在同一个节点下,子级 UI 在其添加的节点下。

UI 元素情况下,级数相同的 UI 在同一个 Layer 元素(GComponent)下。

这样就可以在同一个层级中进行置顶操作并不影响其他层级的 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
using System.Collections.Generic;

public class UINode
{
/// <summary>
/// 父节点
/// </summary>
public UINode parent;

/// <summary>
/// 层级索引
/// </summary>
public int layer;

/// <summary>
/// 所属UI
/// </summary>
public BaseView ui;

/// <summary>
/// 子节点
/// </summary>
public Dictionary<string, UINode> children = new Dictionary<string, UINode>();
}

UI 管理器封装 UI 常用操作。

  • 展示 UI,如果传入 UINode,则在该节点下展示,否则在根节点下展示 UI。如果有保存过 UI,则直接展示保存的 UI。
  • 隐藏 | 销毁 UI,根据树结构,遍历子节点执行相同操作。
  • 查找 UI,如果传入 UINode,则从该节点开始查找,否则从根节点开始查找。

以下为 BaseView 自动执行的操作,无需主动调用:

  • ResetTop,原理是利用 FGUI 的 AddChild 重新插入 UI 元素,使其置顶显示。
  • SetModel,设置模态背景,原理是在当前需要模态的 UI 元素的位置插入一个 GGraph 对象,当前 UI 元素会在原来索引 +1 的位置,这样就保证背景在 UI 元素下方并且不受 UI 元素窗口的影响。
  • SaveNode,利用字典保存 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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
using System;
using System.Collections.Generic;
using FairyGUI;
using UnityEngine;

namespace ReflectionUI
{
public class UIManager : Singleton<UIManager>
{
private UINode _root = new UINode();

private int _id = 0;

private List<GComponent> _layer = new List<GComponent>();

private Dictionary<string, UINode> _savedView = new Dictionary<string, UINode>();

/// <summary>
/// 展示已存在的UI
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public UINode ShowUI(string name)
{
if (_savedView.TryGetValue(name, out var node))
{
node.ui.Show();
return node;
}

Debug.Log("UI未创建");
return null;
}

/// <summary>
/// 展示UI
/// </summary>
/// <param name="folder">UI所在文件夹</param>
/// <param name="package">UI包名</param>
/// <param name="name">自定义名称</param>
/// <param name="parent">父节点</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public UINode ShowUI<T>(string folder, string package, string name, UINode parent = null)
where T : BaseView, new()
{
//有保存的UI直接展示并返回
if (_savedView.TryGetValue(name, out var node))
{
node.ui.Show();
return node;
}

//创建没保存的UI
Type type = typeof(T);
//包加载逻辑暂时加载Resources文件夹内文件 如有需要可自行修改
string packagePath = folder + "/" + package;
UIPackage.AddPackage(packagePath);
//创建UI
T view = new T();
view.id = "ui_" + _id++;
view.name = name;
view.main = UIPackage.CreateObject(package, type.Name).asCom;

//创建UI节点
UINode ui = new UINode();
ui.ui = view;

if (parent != null)
{
int layerIndex = parent.layer + 1;
if (_layer.Count - 1 == parent.layer)
{
GComponent layer = new GComponent();
layer.displayObject.gameObject.name = "Layer_" + layerIndex;
GRoot.inst.AddChild(layer);
_layer.Add(layer);
}

_layer[layerIndex].AddChild(view.main);

parent.children.Add(view.id, ui);
ui.parent = parent;
ui.layer = layerIndex;
}
else
{
if (_layer.Count == 0)
{
GComponent layer = new GComponent();
layer.displayObject.gameObject.name = "Layer_0";
GRoot.inst.AddChild(layer);
_layer.Add(layer);
}

_layer[0].AddChild(view.main);

_root.children.Add(view.id, ui);
ui.parent = _root;
ui.layer = 0;
}

view.uiNode = ui;

view.OnAwake();
view.Show();

return ui;
}

public void HideUI(string name)
{
var ui = GetUI(name);
if (ui != null)
{
HideUI(ui);
}
}

/// <summary>
/// 隐藏UI
/// </summary>
/// <param name="ui">UI节点</param>
public void HideUI(UINode ui)
{
foreach (var child in ui.children)
{
UINode uiChild = child.Value;
HideUI(uiChild);
}

ui.ui.Hide();
}

/// <summary>
/// 根据名字获取UI
/// </summary>
/// <param name="name">自定义名称</param>
/// <param name="parent">父节点</param>
/// <returns></returns>
public UINode GetUI(string name, UINode parent = null)
{
if (parent == null)
{
parent = _root;
}

if (parent.ui != null && name == parent.ui.name)
{
return parent;
}

UINode node;

foreach (var child in parent.children)
{
node = GetUI(name, child.Value);
if (node != null)
{
return node;
}
}

return null;
}

/// <summary>
/// 销毁UI
/// </summary>
/// <param name="ui">UI节点</param>
public void DisposeUI(string name)
{
UINode ui = GetUI(name);
if (ui != null)
{
DisposeUI(ui);
}
}

/// <summary>
/// 销毁UI
/// </summary>
/// <param name="ui">UI节点</param>
public void DisposeUI(UINode ui)
{
ui.Dispose();

//移除保存的节点
_savedView.Remove(ui.ui.name);
}

/// <summary>
/// 重新置于上层
/// </summary>
/// <param name="ui">UI节点</param>
public void ResetTop(UINode ui)
{
_layer[ui.layer].AddChild(ui.ui.main);
ConsoleUtils.Log("重新置顶");
}

/// <summary>
/// 设置模态背景
/// </summary>
/// <param name="ui">UI节点</param>
/// <param name="model">模态背景对象</param>
public void SetModel(UINode ui, GGraph model)
{
GComponent layer = _layer[ui.layer];
int index = layer.GetChildIndex(ui.ui.main);
layer.AddChildAt(model, index);
}

/// <summary>
/// 保存节点 需要ui名称唯一
/// </summary>
/// <param name="name">自定义名称</param>
/// <param name="ui">UI节点</param>
public void SaveNode(string name, UINode ui)
{
_savedView[name] = ui;
}
}
}

BaseView 生命周期

flowchart TB;

    subgraph A["OnAwake 生命周期"]
        OnAwake --> Bind --> InitData --> BindClass;
    end

    subgraph B["Show 生命周期"]
        Show --> TweenIn --> OnShow;
    end

    subgraph C["Hide 生命周期"]
        Hide --> TweenOut --> OnHide;
    end

    InitConfig --> A;
    A --> B;
    B --> C;
    C --> Dispose;

Show 和 Hide 方法每次显示隐藏的时候都会调用。

InitConfig、InitData 和 OnAwake 方法只有在第一次创建窗口的时候会调用。

可以重写的生命周期:

  • InitConfig: 初始化配置。
  • InitData: 初始化数据。
  • OnShow: UI 展示事件。
  • OnHide: UI 隐藏事件。
  • TweenIn: 展示缓动。
  • TweenOut: 隐藏缓动。

使用

展示

基本功能

双向绑定

拖拽

↑ 代理拖拽 ↑

↑ 自体拖拽 ↑

↑ List 拖拽 | 手动设置拖拽 ↑

窗口缓动

悬浮窗

下拉框

窗口拖拽和顶层重置

UI 树

模态窗口

使用方法

请转到ReflectionBindUI 文档查看。

代码

详细代码
StringUIProp
StringUIProp
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
using System;

public class StringUIProp
{
public string _value;

private string val
{
get
{
return _value;
}
set
{
_value = value;
InvokeUI();
}
}

private Action<string> _onValueChange = null;
private Func<string> _onUIChange = null;

public StringUIProp() { }

public StringUIProp(string value)
{
_value = value;
}

public void Set(string value)
{
this.val = value;
}

public void Set<T>(T value)
{
this.val = value.ToString();
}

public string Get()
{
InvokeValue();
return this.val;
}

public void InvokeValue()
{
if (_onUIChange != null)
{
_value = _onUIChange.Invoke();
}
}

public void InvokeUI()
{
_onValueChange?.Invoke(val);
}

public override string ToString()
{
return val;
}
}

DoubleUIProp
DoubleUIProp
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
using System;

public class DoubleUIProp
{
public double _value;

private double val
{
get { return _value; }
set
{
_value = value;
InvokeUI();
}
}

private Action<double> _onValueChange = null;
private Func<double> _onUIChange = null;

public DoubleUIProp()
{
}

public DoubleUIProp(double value)
{
_value = value;
}

public void Set(double value)
{
this.val = value;
}

public double Get()
{
InvokeValue();
return this.val;
}

public void InvokeValue()
{
if (_onUIChange != null)
{
_value = _onUIChange.Invoke();
}
}

public void InvokeUI()
{
_onValueChange?.Invoke(val);
}

public override string ToString()
{
return val.ToString();
}
}
UIListProp
UIListProp
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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIListProp<T>
{
private List<T> _value;

private List<T> val
{
get
{
return _value;
}
set
{
_value = value;
Invoke();
}
}

public int Count { get; set; }

private Action<int> _onValueChange = null;

public UIListProp() { }

public UIListProp(List<T> value)
{
//this.val = value;
_value = value;
}

public List<T> Get()
{
return val;
}

public void Set(List<T> value)
{
val = value;
Count = val.Count;
}

public void Invoke()
{
if (val != null)
{
_onValueChange?.Invoke(val.Count);
}
}

public override string ToString()
{
string res = "[";
foreach (var item in val)
{
res += item.ToString() + ",";
}

res += "]";
return res;
}
}
绑定特性
UICompBind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 绑定UI组件
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class UICompBind :Attribute
{
public string _type;
public string _path;
public UICompBind(string type,string path)
{
_type = type;
_path = path;
}
}
UIDataBind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;

/// <summary>
/// 绑定UI组件和数据,使其根据数据修改UI
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class UIDataBind : Attribute
{
public string _type;
public string _path;
public string[] _extra;
public UIDataBind(string type, string path, params string[] extra)
{
_type = type;
_path = path;
_extra = extra;
}
}
UIActionBind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

/// <summary>
/// 绑定UI组件和动作事件,使其支持交互
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UIActionBind : Attribute
{
public string _type;

public string _path;

public string[] _extra;

public UIActionBind(string type,string path,params string[] extra)
{
_type = type;
_path = path;
_extra = extra;
}
}

UIListenerBind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 绑定UI监听事件
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UIListenerBind : Attribute
{
public string _name;

public UIListenerBind(string name)
{
_name = name;
}
}
UIBase
UIBase
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
using FairyGUI;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

/// <summary>
/// UI内部元素基类
/// </summary>
public class UIBase
{
/// <summary>
/// 界面UI根元素
/// </summary>
public GComponent main;
/// <summary>
/// 界面id
/// </summary>
public string id;
/// <summary>
/// 界面名
/// </summary>
public string name;

/// <summary>
/// 当前类的类型
/// </summary>
protected Type _type;

/// <summary>
/// 反射范围标志
/// </summary>
private readonly BindingFlags _flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
BindingFlags.Instance;

//----- 内建私有变量 -----

/// <summary>
/// 拖拽元素字典,用于保存元素的拖拽状态
/// </summary>
private readonly Dictionary<string, bool> _dropDic = new Dictionary<string, bool>();

/// <summary>
/// 代理拖拽元素
/// </summary>
private GameObject _copy;

/// <summary>
/// 当前代理拖拽脚本
/// </summary>
private UIDrag _uiDrag;

/// <summary>
/// 拖拽数据
/// </summary>
private readonly ArrayList _dropData = new ArrayList();

/// <summary>
/// 浮动窗口id
/// </summary>
private int _floatId = 0;

/// <summary>
/// 浮动窗口字典,用于保存所有浮动窗口
/// </summary>
private Dictionary<string, BaseView> _floatViews = new Dictionary<string, BaseView>();

/// <summary>
/// 正在显示的悬浮窗
/// </summary>
private BaseView _floatViewOnShow = null;

protected void Bind()
{
_type = GetType();
PropertyInfo[] props = _type.GetProperties(_flag);

MethodInfo[] methods = _type.GetMethods(_flag);

foreach (var method in methods)
{
var methodAttrs = method.GetCustomAttributes(true);
foreach (var attr in methodAttrs)
{
if (attr is UIActionBind)
{
BindAction(method, attr);
}
else if (attr is UIListenerBind)
{
BindListener(method, attr);
}
}
}

foreach (var prop in props)
{
var propAttrs = prop.GetCustomAttributes(true);
foreach (var attr in propAttrs)
{
if (attr is UICompBind)
{
BindComp(prop, attr);
}
else if (attr is UIDataBind)
{
BindData(prop, attr);
}
}
}
}

/// <summary>
/// 绑定组件
/// </summary>
/// <param name="prop"></param>
/// <param name="attr"></param>
private void BindComp(PropertyInfo prop, object attr)
{
UICompBind uiBind = (UICompBind)attr;

switch (uiBind._type)
{
case UIType.Comp:
GComponent comp = FguiUtils.GetUI<GComponent>(main, uiBind._path);
prop.SetValue(this, comp);
break;
case UIType.TextField:
GTextField textField = FguiUtils.GetUI<GTextField>(main, uiBind._path);
prop.SetValue(this, textField);
break;
case UIType.TextInput:
GTextInput textInput = FguiUtils.GetUI<GTextInput>(main, uiBind._path);
prop.SetValue(this, textInput);
break;
case UIType.Image:
GImage image = FguiUtils.GetUI<GImage>(main, uiBind._path);
prop.SetValue(this, image);
break;
case UIType.Loader:
GLoader loader = FguiUtils.GetUI<GLoader>(main, uiBind._path);
prop.SetValue(this, loader);
break;
case UIType.List:
GList list = FguiUtils.GetUI<GList>(main, uiBind._path);
prop.SetValue(this, list);
break;
case UIType.Slider:
GSlider slider = FguiUtils.GetUI<GSlider>(main, uiBind._path);
prop.SetValue(this,slider);
break;
case UIType.ComboBox:
GComboBox comboBox = FguiUtils.GetUI<GComboBox>(main, uiBind._path);
prop.SetValue(this,comboBox);
break;
}
}

/// <summary>
/// 绑定 组件-数据
/// </summary>
/// <param name="prop"></param>
/// <param name="attr"></param>
private void BindData(PropertyInfo prop, object attr)
{
UIDataBind uiBind = (UIDataBind)attr;

//获取双向绑定委托
var onValueChange = prop.PropertyType.GetField("_onValueChange", _flag);
var onUIChange = prop.PropertyType.GetField("_onUIChange", _flag);

var value = prop.GetValue(this);
if (value == null)
{
//初始化当前属性的值
Type propType = prop.PropertyType;
if (propType == typeof(StringUIProp))
{
value = new StringUIProp();
}
else if (propType == typeof(DoubleUIProp))
{
value = new DoubleUIProp();
}
else
{
//创建List
Type genericType = typeof(UIListProp<>).MakeGenericType(prop.PropertyType.GenericTypeArguments);
value = Activator.CreateInstance(genericType);
}

prop.SetValue(this, value);
}

switch (uiBind._type)
{
case UIType.TextField:
GTextField textField = FguiUtils.GetUI<GTextField>(main, uiBind._path);

void ActionText(string data)
{
textField.text = data;
}

string ActionTextUI()
{
return textField.text;
}

onValueChange?.SetValue(value, (Action<string>)ActionText);
onUIChange?.SetValue(value, (Func<string>)ActionTextUI);
break;
case UIType.TextInput:
GTextInput textInput = FguiUtils.GetUI<GTextInput>(main, uiBind._path);

void ActionInput(string data)
{
textInput.text = data;
}

string ActionInputUI()
{
return textInput.text;
}

onValueChange?.SetValue(value, (Action<string>)ActionInput);
onUIChange?.SetValue(value, (Func<string>)ActionInputUI);
break;
case UIType.Image:
GImage image = FguiUtils.GetUI<GImage>(main, uiBind._path);

void ActionImage(string data)
{
image.icon = data;
}

string ActionImageUI()
{
return image.icon;
}

onValueChange?.SetValue(value, (Action<string>)ActionImage);
onUIChange?.SetValue(value, (Func<string>)ActionImageUI);
break;
case UIType.Loader:
GLoader loader = FguiUtils.GetUI<GLoader>(main, uiBind._path);

void ActionLoader(string data)
{
loader.url = data;

// ConsoleUtils.Log("替换图片", loader?.url);
}

string ActionLoaderUI()
{
return loader.url;
}

onValueChange?.SetValue(value, (Action<string>)ActionLoader);
onUIChange?.SetValue(value, (Func<string>)ActionLoaderUI);
break;
case UIType.List:
GList list = FguiUtils.GetUI<GList>(main, uiBind._path);

void ActionList(int data)
{
list.SetVirtual();
list.numItems = data;

if (uiBind._extra.Length > 0)
{
switch (uiBind._extra[0])
{
case "height":
if (list.numChildren > 0)
{
list.height = data * list.GetChildAt(0).height + list.lineGap * (data - 1) +
list.margin.top + list.margin.bottom;
}

break;
case "width":
if (list.numChildren > 0)
{
list.width = data * list.GetChildAt(0).width + list.columnGap * (data - 1) +
list.margin.left + list.margin.right;
}

break;
}
}
}

onValueChange?.SetValue(value, (Action<int>)ActionList);
break;
case UIType.Slider:
GSlider slider = FguiUtils.GetUI<GSlider>(main, uiBind._path);

void ActionSlider(double data)
{
slider.value = data;
}

double ActionSliderUI()
{
return slider.value;
}

onValueChange?.SetValue(value, (Action<double>)ActionSlider);
onUIChange?.SetValue(value, (Func<double>)ActionSliderUI);
break;
case UIType.ComboBox:
GComboBox comboBox = FguiUtils.GetUI<GComboBox>(main, uiBind._path);

comboBox.items = uiBind._extra;

void ActionComboBox(double data)
{
comboBox.selectedIndex = (int)data;
}

double ActionComboBoxUI()
{
return comboBox.selectedIndex;
}

onValueChange?.SetValue(value, (Action<double>)ActionComboBox);
onUIChange?.SetValue(value, (Func<double>)ActionComboBoxUI);

break;
}
}

/// <summary>
/// 绑定 组件-行为
/// </summary>
/// <param name="method"></param>
/// <param name="attr"></param>
private void BindAction(MethodInfo method, object attr)
{
UIActionBind uiBind = (UIActionBind)attr;
GObject obj = FguiUtils.GetUI<GObject>(main, uiBind._path);
//获取方法的参数
ParameterInfo[] methodParamsList;
bool isAgent;
Delegate action;

switch (uiBind._type)
{
case UIAction.Click:
methodParamsList = method.GetParameters();
if (methodParamsList.Length == 0)
{
action = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.onClick.Set((EventCallback0)action);
}
else
{
action = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.onClick.Set((EventCallback1)action);
}

break;
case UIAction.ListRender:
action = Delegate.CreateDelegate(typeof(ListItemRenderer), this, method);
obj.asList.itemRenderer = (ListItemRenderer)action;
break;
case UIAction.ListProvider:
action = Delegate.CreateDelegate(typeof(ListItemProvider), this, method);
obj.asList.itemProvider = (ListItemProvider)action;
break;
case UIAction.ListClick:
methodParamsList = method.GetParameters();
if (methodParamsList.Length == 0)
{
action = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.asList.onClickItem.Set((EventCallback0)action);
}
else
{
action = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.asList.onClickItem.Set((EventCallback1)action);
}

break;
case UIAction.DragStart:
obj.draggable = true;
isAgent = uiBind._extra.Length == 0 || uiBind._extra[0] == "Self";
SetDragListener(obj, 0, method, isAgent);
break;
case UIAction.DragHold:
obj.draggable = true;
isAgent = uiBind._extra.Length == 0 || uiBind._extra[0] == "Self";
SetDragListener(obj, 1, method, isAgent);
break;
case UIAction.DragEnd:
obj.draggable = true;
isAgent = uiBind._extra.Length == 0 || uiBind._extra[0] != "Self";
SetDragListener(obj, 2, method, isAgent);
break;
case UIAction.Drop:
action = (Action<object>)Delegate.CreateDelegate(typeof(Action<object>), this, method);
_dropDic[obj.id] = true;
EventManager.AddListening(obj.id, "OnDrop_" + obj.id, data => ((Action<object>)action).Invoke(data));
break;
case UIAction.Hover:
methodParamsList = method.GetParameters();
if (methodParamsList.Length == 0)
{
action = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.onRollOver.Set((EventCallback0)action);
}
else
{
action = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.onRollOver.Set((EventCallback1)action);
}

obj.onRollOver.Add(() =>
{
if (_floatViewOnShow != null)
{
if (_floatViewOnShow.main.displayObject.gameObject.GetComponent<UIFollow>() != null)
{
_floatViewOnShow.main.xy = FguiUtils.GetMousePosition();
}
else
{
_floatViewOnShow.main.xy = obj.xy;
}

_floatViewOnShow.Show();
}
});

//退出隐藏
obj.onRollOut.Set(() =>
{
_floatViewOnShow?.Hide();
_floatViewOnShow = null;
});
break;
case UIAction.Slider:
methodParamsList = method.GetParameters();
if (methodParamsList.Length == 0)
{
action = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.asSlider.onChanged.Set((EventCallback0)action);
}
else
{
action = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.asSlider.onChanged.Set((EventCallback1)action);
}

break;
case UIAction.ComboBox:
methodParamsList = method.GetParameters();
if (methodParamsList.Length == 0)
{
action = Delegate.CreateDelegate(typeof(EventCallback0), this, method);
obj.asComboBox.onChanged.Set((EventCallback0)action);
}
else
{
action = Delegate.CreateDelegate(typeof(EventCallback1), this, method);
obj.asComboBox.onChanged.Set((EventCallback1)action);
}
break;
}
}

/// <summary>
/// 绑定监听
/// </summary>
/// <param name="method"></param>
/// <param name="attr"></param>
private void BindListener(MethodInfo method, object attr)
{
UIListenerBind uiBind = (UIListenerBind)attr;
var eventFunc = Delegate.CreateDelegate(typeof(Action<ArrayList>), this, method);
EventManager.AddListening(id, uiBind._name, (Action<ArrayList>)eventFunc);
}


private void ClearDropData()
{
_dropData.Clear();
}

/// <summary>
/// 添加放置数据
/// </summary>
/// <param name="data"></param>
protected void AddDropData(object data)
{
_dropData.Add(data);
}

/// <summary>
/// 设置拖拽,用于list内元素
/// </summary>
/// <param name="type"></param>
/// <param name="action"></param>
/// <param name="obj"></param>
protected void SetDrag(UIAction type, GObject obj, Action dragAction)
{
obj.draggable = true;
Action action = () =>
{
//停止本次滚动
obj.parent.asList.scrollPane.CancelDragging();
dragAction();
};
switch (type)
{
case UIAction.DragStart:
SetDragListener(obj, 0, action);
break;
case UIAction.DragHold:
SetDragListener(obj, 1, action);
break;
case UIAction.DragEnd:
SetDragListener(obj, 2, action);
break;
}
}

protected void SetDrop(GObject obj, Action<object> action)
{
_dropDic[obj.id] = true;
EventManager.AddListening(obj.id, "OnDrop_" + obj.id, data => action(_dropData));
}

/// <summary>
/// 添加放置数据
/// </summary>
/// <param name="datas"></param>
protected void AddDropData(params object[] datas)
{
foreach (var data in datas)
{
_dropData.Add(data);
}
}

/// <summary>
/// 展示悬浮窗
/// </summary>
/// <param name="name">悬浮窗名</param>
/// <param name="follow">是否跟随</param>
/// <typeparam name="T"></typeparam>
protected void ShowFloatView<T>(string name, bool follow = false) where T : BaseView, new()
{
BaseView view;
_floatViews.TryGetValue(name, out view);
if (view == null)
{
view = new T();
string uiName = typeof(T).Name;
view.id = "float_view_" + _floatId++;
view.name = name;
view.main = UIPackage.CreateObject("Test", uiName).asCom;
view.main.touchable = false;

if (follow)
{
UIFollow uiFollow = view.main.displayObject.gameObject.AddComponent<UIFollow>();
uiFollow.SetObj(view.main, main);
}

main.AddChild(view.main);
view.OnAwake();
_floatViews.Add(name, view);
}

_floatViewOnShow = view;
}

/// <summary>
/// 添加拖拽监听代理
/// </summary>
/// <param name="obj">拖拽UI</param>
/// <param name="type">拖拽类型 0:start,1:hold,2:end</param>
/// <param name="method">拖拽回调</param>
/// <param name="isAgent">是否代理拖拽</param>
private void SetDragListener(GObject obj, int type, MethodInfo method, bool isAgent)
{
ParameterInfo[] methodParamsList = method.GetParameters();

var drag = Delegate.CreateDelegate(
methodParamsList.Length == 0 ? typeof(EventCallback0) : typeof(EventCallback1), this, method);

if (isAgent)
{
obj.onDragStart.Add(context =>
{
context.PreventDefault();
//复制UI
GameObject origin = obj.displayObject.gameObject;
_copy = GameObject.Instantiate(origin, main.displayObject.gameObject.transform, true);
CompClone(_copy.transform, origin.transform);

//同步属性
_copy.transform.localPosition = origin.transform.localPosition;
_copy.transform.localScale = origin.transform.localScale;
_copy.transform.localRotation = origin.transform.localRotation;

//拖拽跟随逻辑
_uiDrag = _copy.AddComponent<UIDrag>();
_uiDrag.SetOriginMousePos();

Action action = () =>
{
//清除放置数据
ClearDropData();
if (methodParamsList.Length == 0)
{
((EventCallback0)drag).Invoke();
}
else
{
((EventCallback1)drag).Invoke(null);
}
};

switch (type)
{
case 0:
_uiDrag.SetStart(action);
break;
case 1:
_uiDrag.SetUpdate(action);
break;
case 2:
_uiDrag.SetEnd(action);
break;
}

AddDropListener(obj);
RemoveDragAgent();
});
}
else
{
if (methodParamsList.Length == 0)
{
EventCallback0 action = () =>
{
//清除放置数据
ClearDropData();
((EventCallback0)drag).Invoke();
};
//监听鼠标拖拽
switch (type)
{
case 0:
obj.onDragStart.Set(action);
break;
case 1:
obj.onDragMove.Set(action);
break;
case 2:
obj.onDragEnd.Set(action);
break;
}
}
else
{
EventCallback1 action = context =>
{
//清除放置数据
ClearDropData();
((EventCallback1)drag).Invoke(context);
};
//监听鼠标拖拽
switch (type)
{
case 0:
obj.onDragStart.Set(action);
break;
case 1:
obj.onDragMove.Set(action);
break;
case 2:
obj.onDragEnd.Set(action);
break;
}
}

AddDropListener(obj);
}
}

/// <summary>
/// 设置拖拽监听
/// </summary>
/// <param name="obj">拖拽UI</param>
/// <param name="type">拖拽类型 0:start,1:hold,2:end</param>
/// <param name="dragAction">拖拽回调</param>
private void SetDragListener(GObject obj, int type, Action dragAction)
{
obj.onDragStart.Add(context =>
{
context.PreventDefault();
//复制UI
GameObject origin = obj.displayObject.gameObject;
_copy = GameObject.Instantiate(origin, main.displayObject.gameObject.transform, true);
CompClone(_copy.transform, origin.transform);

//同步属性
_copy.transform.position = origin.transform.position;
_copy.transform.localScale = origin.transform.localScale;
_copy.transform.rotation = origin.transform.rotation;

//拖拽跟随逻辑
_uiDrag = _copy.AddComponent<UIDrag>();
_uiDrag.SetOriginMousePos();

Action action = () =>
{
//清除放置数据
ClearDropData();
dragAction.Invoke();
};

switch (type)
{
case 0:
_uiDrag.SetStart(action);
break;
case 1:
_uiDrag.SetUpdate(action);
break;
case 2:
_uiDrag.SetEnd(action);
break;
}

AddDropListener(obj);
RemoveDragAgent();
});
}

/// <summary>
/// 添加放置监听
/// </summary>
/// <param name="obj">放置UI</param>
private void AddDropListener(GObject obj)
{
if (_uiDrag)
{
_uiDrag.AddEnd(() =>
{
GObject target = GRoot.inst.touchTarget;
while (target != null)
{
if (_dropDic.ContainsKey(target.id))
{
EventManager.TriggerEvent("OnDrop_" + target.id, _dropData);
return;
}

target = target.parent;
}
});
}
else
{
obj.onDragEnd.Add(() =>
{
GObject target = GRoot.inst.touchTarget;
while (target != null)
{
if (_dropDic.ContainsKey(target.id))
{
EventManager.TriggerEvent("OnDrop_" + target.id, _dropData);
return;
}

target = target.parent;
}
});
}
}

/// <summary>
/// 移除拖拽监听代理
/// </summary>
private void RemoveDragAgent()
{
if (_uiDrag)
{
_uiDrag.AddEnd(() =>
{
_copy = null;
_uiDrag = null;
});
}
}

/// <summary>
/// 代理组件克隆及处理
/// </summary>
/// <param name="transCopy">克隆体transform</param>
/// <param name="transOrigin">原型transform</param>
private void CompClone(Transform transCopy, Transform transOrigin)
{
MeshFilter filter = transCopy.GetComponent<MeshFilter>();
MeshRenderer renderer = transCopy.GetComponent<MeshRenderer>();
if (filter)
{
filter.mesh = transOrigin.GetComponent<MeshFilter>().mesh;
}

if (renderer)
{
// renderer.materials = transOrigin.GetComponent<MeshRenderer>().materials;
Material[] origin = transOrigin.GetComponent<MeshRenderer>().materials;
Material[] copy = new Material[origin.Length];
for (int i = 0; i < origin.Length; i++)
{
copy[i] = new Material(origin[i]);
}

renderer.materials = copy;
renderer.sortingOrder = 9999;
}

if (transCopy.childCount > 0)
{
for (int i = 0; i < transCopy.childCount; i++)
{
CompClone(transCopy.GetChild(i), transOrigin.GetChild(i));
}
}
}
}
BaseView

该类没有自动执行的方法,请自行接管生命周期

BaseView
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
using System;
using System.Reflection;
using FairyGUI;
using UnityEngine;

namespace ReflectionUI
{
/// <summary>
/// UI界面基类
/// </summary>
public class BaseView : UIBase
{
/// <summary>
/// UI节点
/// </summary>
public UINode uiNode;

/// <summary>
/// 缓动最大持续时间
/// </summary>
private int _duration;

/// <summary>
/// 模态背景
/// </summary>
private GGraph _model;

/// <summary>
/// 是否保存节点 默认保存
/// </summary>
private bool _isSaveNode = true;

public void OnAwake()
{
//初始化配置
InitConfig();

if (_isSaveNode)
{
//保存节点 使该 UI 成为全局唯一 UI
//如果需要该 UI 可以重复创建,则令 _isSaveNode = false,或者设置 UI 的名字为不重名
UIManager.Ins().SaveNode(name, uiNode);
}

//绑定UI元素
Bind();
//初始化数据
InitData();

//绑定类
var classAttributes = _type.GetCustomAttributes();

foreach (var attr in classAttributes)
{
if (attr is UIClassBind)
{
BindClass(attr);
}
}
}

/// <summary>
/// 展示UI
/// </summary>
public void Show()
{
TweenIn();
DoTween(true);
}

/// <summary>
/// 隐藏UI
/// </summary>
public void Hide()
{
TweenOut();
DoTween(false);
}

/// <summary>
/// 销毁UI
/// </summary>
public void Dispose()
{
main.Dispose();
}

/// <summary>
/// 获取UI显示情况
/// </summary>
/// <returns></returns>
public bool GetVisible()
{
return main.visible;
}

/// <summary>
/// 设置UI显示情况
/// </summary>
/// <param name="visible"></param>
public void SetVisible(bool visible)
{
main.visible = visible;
}

/// <summary>
/// 添加缓动
/// </summary>
/// <param name="obj"></param>
/// <param name="target">缓动目标属性</param>
/// <param name="end">缓动目标值</param>
/// <param name="duration">持续时间</param>
/// <param name="ease">插值函数</param>
/// <param name="callback">回调</param>
protected int AddTween(GObject obj ,TweenTarget target, float end, int duration, TweenEaseType ease = TweenEaseType.Linear,
Action callback = null)
{
return UITweenManager.Ins().AddTween(obj, target, end, duration, ease, callback);
}

/// <summary>
/// 添加缓动
/// </summary>
/// <param name="obj"></param>
/// <param name="target">缓动目标属性</param>
/// <param name="end">缓动目标值</param>
/// <param name="duration">持续时间</param>
/// <param name="ease">插值函数</param>
/// <param name="callback">回调</param>
protected int AddTween(GObject obj ,TweenTarget target, Vector2 end, int duration, TweenEaseType ease = TweenEaseType.Linear,
Action callback = null)
{
return UITweenManager.Ins().AddTween(obj, target, end, duration, ease, callback);
}

/// <summary>
/// 停止缓动
/// </summary>
/// <param name="id"></param>
protected void StopTween(int id)
{
UITweenManager.Ins().StopTween(id);
}

/// <summary>
/// 添加缓动
/// </summary>
/// <param name="target">缓动目标属性</param>
/// <param name="end">缓动目标值</param>
/// <param name="duration">持续时间</param>
/// <param name="ease">插值函数</param>
/// <param name="callback">回调</param>
protected void AddTween(TweenTarget target, float end, int duration, TweenEaseType ease = TweenEaseType.Linear,
Action callback = null)
{
UITweenManager.Ins().AddTween(main, target, end, duration, ease, callback);
_duration = duration < _duration ? _duration : duration;
}

/// <summary>
/// 添加缓动
/// </summary>
/// <param name="target">缓动目标属性</param>
/// <param name="end">缓动目标值</param>
/// <param name="duration">持续时间</param>
/// <param name="ease">插值函数</param>
/// <param name="callback">回调</param>
protected void AddTween(TweenTarget target, Vector2 end, int duration,
TweenEaseType ease = TweenEaseType.Linear,
Action callback = null)
{
UITweenManager.Ins().AddTween(main, target, end, duration, ease, callback);
_duration = duration < _duration ? _duration : duration;
}

/// <summary>
/// 每次展示的时候执行
/// </summary>
protected virtual void OnShow()
{
}

/// <summary>
/// 每次隐藏的时候执行
/// </summary>
protected virtual void OnHide()
{
}

/// <summary>
/// 进场缓动初始化方法
/// </summary>
protected virtual void TweenIn()
{
}

/// <summary>
/// 退场缓动初始化方法
/// </summary>
protected virtual void TweenOut()
{
}

/// <summary>
/// 配置初始化
/// </summary>
protected virtual void InitConfig()
{
}

/// <summary>
/// 初始化数据
/// </summary>
protected virtual void InitData()
{

}

/// <summary>
/// 执行缓动
/// </summary>
/// <param name="start">进场或退场</param>
private void DoTween(bool start)
{
if (start)
{
SetVisible(true);
AddTween(TweenTarget.None, 0, _duration, TweenEaseType.Linear, OnShow);
}
else
{
AddTween(TweenTarget.None, 0, _duration, TweenEaseType.Linear, () =>
{
main.visible = false;
OnHide();
});
}
}

/// <summary>
/// 绑定 类
/// </summary>
/// <param name="attr"></param>
private void BindClass(object attr)
{
UIClassBind uiClassBind = (UIClassBind)attr;

switch (uiClassBind.type)
{
case UIClass.Model:
if (_model == null)
{
//创建模态背景
_model = new GGraph();
_model.displayObject.gameObject.name = id + "_" + name + "_Model";

UIColor colorAttr = _type.GetCustomAttribute<UIColor>();
Color color = new Color(0, 0, 0, 0);
if (colorAttr != null)
{
color = colorAttr.color;
}

Vector2 size = GRoot.inst.size;
_model.DrawRect(size.x, size.y, 0, new Color(), color);
}

UIManager.Ins().SetModel(uiNode, _model);

UICondition condition = _type.GetCustomAttribute<UICondition>();

//判断模态窗口是否需要点击模态部分关闭窗口
if (condition != null && condition.GetBool())
{
_model.onClick.Set(() =>
{
Hide();
main.AddChild(_model);
});
}

break;
case UIClass.Drag:

UIWindow uiWindow = _type.GetCustomAttribute<UIWindow>();

bool retop = uiWindow == null || uiWindow.IsReTop();
string path = uiWindow?.GetOperateItemPath();

if (!string.IsNullOrEmpty(path))
{
GObject obj = FGUIUtils.GetUI<GObject>(main, path);
bool isTouch = false;
bool isOut = true;

//监听四个事件,保证拖拽的实时性和严格性
obj.onTouchBegin.Set(() =>
{
main.draggable = true;
isTouch = true;
});

obj.onTouchEnd.Set(() =>
{
if (isOut)
{
main.draggable = false;
}

isTouch = false;
});

obj.onRollOver.Set(() =>
{
main.draggable = true;
isOut = false;
});

obj.onRollOut.Set(() =>
{
if (!isTouch)
{
main.draggable = false;
}

isOut = true;
});

//监听置顶
if (retop)
{
obj.onTouchBegin.Add(() => { UIManager.Ins().ResetTop(uiNode); });
}
}
else
{
//整体拖拽,不需要切换拖拽状态,因此不监听事件
main.draggable = true;

//监听置顶
if (retop)
{
main.onTouchBegin.Add(evt => { UIManager.Ins().ResetTop(uiNode); });
}
}


break;
}
}
}
}
悬浮窗跟随类
UIFollow
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
using System.Collections;
using System.Collections.Generic;
using FairyGUI;
using UnityEngine;

public class UIFollow : MonoBehaviour
{
private GObject _obj;

private GObject _parent;

void Update()
{
if (_obj.visible)
{
_obj.xy = FguiUtils.GetMousePosition(_parent);
}
}

public void SetObj(GObject obj,GObject parent)
{
_obj = obj;
_parent = parent;
}
}
Demo
TestView
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
130
131
132
133
134
135
136
137
using FairyGUI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[UIClassBind(UIClass.Drag,"Retop","n10"),UIColor(1,1,1,0.5f)]
public class TestView : BaseView
{
[UIDataBind(UIType.TextField, "1")]
private StringUIProp _testText { get; set; }

[UIDataBind(UIType.List, "n1")]
private UIListProp<string> _testList { get; set; }

[UIDataBind(UIType.Loader, "4")]
private StringUIProp _loaderUrl { get; set; }

[UIDataBind(UIType.Slider, "5")]
private DoubleUIProp _slideValue { get; set; }

[UIDataBind(UIType.TextInput, "6")]
private StringUIProp _input { get; set; }

[UIDataBind(UIType.ComboBox,"9","1","b")]
private DoubleUIProp _comboBoxIndex { get; set; }

[UICompBind(UIType.Loader, "4")]
private GLoader _loader { get; set; }


[UIActionBind(UIAction.ListRender, "2")]
private void ItemRenderer(int index, GObject item)
{
GComponent comp = item as GComponent;
GTextField content = comp.GetChildAt(0).asTextField;

content.text = _testList.Get()[index];

SetDrag(UIAction.DragStart, comp, () =>
{
ConsoleUtils.Log("开始拖拽");
AddDropData(index);
});

SetDrop(comp, (object data) => { ConsoleUtils.Log("放置", data); });
}

[UIActionBind(UIAction.Click, "3")]
private void OnBtnClick()
{
ConsoleUtils.Log("点击了按钮");
_comboBoxIndex.Set(1);
EventManager.TriggerEvent("show_console", null);
}

[UIActionBind(UIAction.DragEnd, "3", "Self")]
private void OnDrag(EventContext context)
{
//添加拖拽数据
AddDropData(1, 2, "测试数据");
Debug.Log("结束拖拽");
Debug.Log(GRoot.inst.touchTarget);
}

[UIActionBind(UIAction.Drop, "4")]
private void OnDrop(object data)
{
ConsoleUtils.Log("拖拽放置", data);
}

[UIListenerBind("show_console")]
private void ShowConsole(ArrayList data)
{
ConsoleUtils.Log("触发了事件");
_loaderUrl.Set("ui://Test/Icon");
_testList.Set(new List<string> { "a", "b", "c" });

// _slideValue.Set(70);
ConsoleUtils.Log(_slideValue.Get(), _input.Get());
}

[UIActionBind(UIAction.Hover,"7")]
private void ShowHint()
{
ShowFloatView<HintView>("input_hint","HintView",true);
// ConsoleUtils.Log("显示悬浮窗");
}

[UIActionBind(UIAction.Slider,"5")]
private void OnSliderChanged()
{
ConsoleUtils.Log("滑动条",_slideValue.Get());
}

[UIActionBind(UIAction.ComboBox,"9")]
private void OnComboBoxChanged()
{
ConsoleUtils.Log("下拉框",_comboBoxIndex.Get());
}

[UIActionBind(UIAction.Click,"n11")]
private void Close()
{
UIManager.Ins().HideUI(uiNode);
// UIManager.Ins().DisposeUI(uiNode);
}


// protected override void TweenIn()
// {
// main.x = -500;
// AddTween(TweenTarget.X,500,2000,TweenEaseType.CircOut);
//
// main.y = 500;
// AddTween(TweenTarget.Y,-500,2000,TweenEaseType.Linear);
//
// main.scaleX = 0.5f;
// AddTween(TweenTarget.ScaleX,0.5f,2000,TweenEaseType.CircOut);
//
// AddTween(TweenTarget.Rotation,50,2000,TweenEaseType.CircOut);
// }
//
protected override void OnShow()
{
ConsoleUtils.Log("OnShow",Time.time);
}

protected override void OnHide()
{
ConsoleUtils.Log("OnHide");
}

// protected override void TweenOut()
// {
// AddTween(TweenTarget.X,500,2000,TweenEaseType.CircOut);
// }
}
窗口缓动
缓动方法
Ease
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using System;
using UnityEngine;

public enum TweenEaseType
{
Linear,
SineIn,
SineOut,
SineInOut,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
QuartIn,
QuartOut,
QuartInOut,
QuintIn,
QuintOut,
QuintInOut,
ExpoIn,
ExpoOut,
ExpoInOut,
CircIn,
CircOut,
CircInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
Custom
}

public class EaseUtil
{
const float _PiOver2 = Mathf.PI * 0.5f;
const float _TwoPi = Mathf.PI * 2;

internal static float Evaluate(TweenEaseType TweenEaseType, float time, float duration, float overshootOrAmplitude,
float period)
{
if (duration <= 0)
return 1;

switch (TweenEaseType)
{
case TweenEaseType.Linear:
return time / duration;
case TweenEaseType.SineIn:
return -(float)Math.Cos(time / duration * _PiOver2) + 1;
case TweenEaseType.SineOut:
return (float)Math.Sin(time / duration * _PiOver2);
case TweenEaseType.SineInOut:
return -0.5f * ((float)Math.Cos(Mathf.PI * time / duration) - 1);
case TweenEaseType.QuadIn:
return (time /= duration) * time;
case TweenEaseType.QuadOut:
return -(time /= duration) * (time - 2);
case TweenEaseType.QuadInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time;
return -0.5f * ((--time) * (time - 2) - 1);
case TweenEaseType.CubicIn:
return (time /= duration) * time * time;
case TweenEaseType.CubicOut:
return ((time = time / duration - 1) * time * time + 1);
case TweenEaseType.CubicInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time;
return 0.5f * ((time -= 2) * time * time + 2);
case TweenEaseType.QuartIn:
return (time /= duration) * time * time * time;
case TweenEaseType.QuartOut:
return -((time = time / duration - 1) * time * time * time - 1);
case TweenEaseType.QuartInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time * time;
return -0.5f * ((time -= 2) * time * time * time - 2);
case TweenEaseType.QuintIn:
return (time /= duration) * time * time * time * time;
case TweenEaseType.QuintOut:
return ((time = time / duration - 1) * time * time * time * time + 1);
case TweenEaseType.QuintInOut:
if ((time /= duration * 0.5f) < 1) return 0.5f * time * time * time * time * time;
return 0.5f * ((time -= 2) * time * time * time * time + 2);
case TweenEaseType.ExpoIn:
return (time == 0) ? 0 : (float)Math.Pow(2, 10 * (time / duration - 1));
case TweenEaseType.ExpoOut:
if (time == duration) return 1;
return (-(float)Math.Pow(2, -10 * time / duration) + 1);
case TweenEaseType.ExpoInOut:
if (time == 0) return 0;
if (time == duration) return 1;
if ((time /= duration * 0.5f) < 1) return 0.5f * (float)Math.Pow(2, 10 * (time - 1));
return 0.5f * (-(float)Math.Pow(2, -10 * --time) + 2);
case TweenEaseType.CircIn:
return -((float)Math.Sqrt(1 - (time /= duration) * time) - 1);
case TweenEaseType.CircOut:
return (float)Math.Sqrt(1 - (time = time / duration - 1) * time);
case TweenEaseType.CircInOut:
if ((time /= duration * 0.5f) < 1) return -0.5f * ((float)Math.Sqrt(1 - time * time) - 1);
return 0.5f * ((float)Math.Sqrt(1 - (time -= 2) * time) + 1);
case TweenEaseType.ElasticIn:
float s0;
if (time == 0) return 0;
if ((time /= duration) == 1) return 1;
if (period == 0) period = duration * 0.3f;
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s0 = period / 4;
}
else s0 = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

return -(overshootOrAmplitude * (float)Math.Pow(2, 10 * (time -= 1)) *
(float)Math.Sin((time * duration - s0) * _TwoPi / period));
case TweenEaseType.ElasticOut:
float s1;
if (time == 0) return 0;
if ((time /= duration) == 1) return 1;
if (period == 0) period = duration * 0.3f;
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s1 = period / 4;
}
else s1 = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

return (overshootOrAmplitude * (float)Math.Pow(2, -10 * time) *
(float)Math.Sin((time * duration - s1) * _TwoPi / period) + 1);
case TweenEaseType.ElasticInOut:
float s;
if (time == 0) return 0;
if ((time /= duration * 0.5f) == 2) return 1;
if (period == 0) period = duration * (0.3f * 1.5f);
if (overshootOrAmplitude < 1)
{
overshootOrAmplitude = 1;
s = period / 4;
}
else s = period / _TwoPi * (float)Math.Asin(1 / overshootOrAmplitude);

if (time < 1)
return -0.5f * (overshootOrAmplitude * (float)Math.Pow(2, 10 * (time -= 1)) *
(float)Math.Sin((time * duration - s) * _TwoPi / period));
return overshootOrAmplitude * (float)Math.Pow(2, -10 * (time -= 1)) *
(float)Math.Sin((time * duration - s) * _TwoPi / period) * 0.5f + 1;
case TweenEaseType.BackIn:
return (time /= duration) * time * ((overshootOrAmplitude + 1) * time - overshootOrAmplitude);
case TweenEaseType.BackOut:
return ((time = time / duration - 1) * time *
((overshootOrAmplitude + 1) * time + overshootOrAmplitude) + 1);
case TweenEaseType.BackInOut:
if ((time /= duration * 0.5f) < 1)
return 0.5f * (time * time *
(((overshootOrAmplitude *= (1.525f)) + 1) * time - overshootOrAmplitude));
return 0.5f * ((time -= 2) * time *
(((overshootOrAmplitude *= (1.525f)) + 1) * time + overshootOrAmplitude) + 2);
case TweenEaseType.BounceIn:
return Bounce.EaseIn(time, duration);
case TweenEaseType.BounceOut:
return Bounce.EaseOut(time, duration);
case TweenEaseType.BounceInOut:
return Bounce.EaseInOut(time, duration);

default:
return -(time /= duration) * (time - 2);
}
}
}

/// <summary>
/// This class contains a C# port of the easing equations created by Robert Penner (http://robertpenner.com/easing).
/// </summary>
static class Bounce
{
/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in: accelerating from zero velocity.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseIn(float time, float duration)
{
return 1 - EaseOut(duration - time, duration);
}

/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out: decelerating from zero velocity.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseOut(float time, float duration)
{
if ((time /= duration) < (1 / 2.75f))
{
return (7.5625f * time * time);
}

if (time < (2 / 2.75f))
{
return (7.5625f * (time -= (1.5f / 2.75f)) * time + 0.75f);
}

if (time < (2.5f / 2.75f))
{
return (7.5625f * (time -= (2.25f / 2.75f)) * time + 0.9375f);
}

return (7.5625f * (time -= (2.625f / 2.75f)) * time + 0.984375f);
}

/// <summary>
/// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in/out: acceleration until halfway, then deceleration.
/// </summary>
/// <param name="time">
/// Current time (in frames or seconds).
/// </param>
/// <param name="duration">
/// Expected easing duration (in frames or seconds).
/// </param>
/// <returns>
/// The eased value.
/// </returns>
public static float EaseInOut(float time, float duration)
{
if (time < duration * 0.5f)
{
return EaseIn(time * 2, duration) * 0.5f;
}

return EaseOut(time * 2 - duration, duration) * 0.5f + 0.5f;
}
}
缓动单元
UITween
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 System;
using UnityEngine;

public class UITween
{
public int id;

public float duration;

private int _time;

public bool isStop;

public Action<float> updater;

public Action callback;

public void Update(int delta)
{
_time += delta;
if (_time <= duration)
{
updater?.Invoke(_time);
}
else
{
updater?.Invoke(duration);
callback?.Invoke();
isStop = true;
updater = null;
callback = null;
}
}
}
缓动更新
TweenUpdater
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
using System.Collections.Generic;
using UnityEngine;

namespace ReflectionUI
{
public class TweenUpdater : MonoBehaviour
{
private Dictionary<int, UITween> _actions = new Dictionary<int, UITween>();

private List<(int, UITween)> _waitToAdd = new List<(int, UITween)>();

private List<int> _removeActionIndexes = new List<int>();

void Update()
{
int delta = (int)(Time.deltaTime * 1000);

if (_waitToAdd.Count > 0)
{
for (int i = 0; i < _waitToAdd.Count; i++)
{
var data = _waitToAdd[i];
_actions.Add(data.Item1, data.Item2);
}
_waitToAdd.Clear();
}

foreach (var action in _actions)
{
UITween vt = action.Value;

if (vt.isStop)
{
_removeActionIndexes.Add(vt.id);
}
else
{
vt.Update(delta);
}
}

for (int i = _removeActionIndexes.Count - 1; i >= 0; i--)
{
_actions.Remove(_removeActionIndexes[i]);
_removeActionIndexes.RemoveAt(i);
}
}

public void AddTween(UITween tween)
{
// _actions.Add(tween.id, tween);
_waitToAdd.Add((tween.id, tween));
}

public void StopTween(int id)
{
UITween tween;
_actions.TryGetValue(id, out tween);

if (tween != null)
{
tween.isStop = true;
}

foreach (var data in _waitToAdd)
{
if (data.Item1 == id)
{
data.Item2.isStop = true;
}
}
}
}
}
缓动管理
UITweenManager
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System;
using FairyGUI;
using UnityEngine;

public class UITweenManager : Singleton<UITweenManager>
{
private TweenUpdater _updater;

private int _id = 0;

public void Init()
{
GameObject obj = new GameObject();
obj.name = "TweenUpdater";
_updater = obj.AddComponent<TweenUpdater>();
}

public int AddTween(GObject obj, TweenTarget target, float end, int duration,
TweenEaseType ease = TweenEaseType.Linear, Action callback = null)
{
UITween vt = new UITween();
vt.id = _id++;
vt.duration = duration;
vt.updater = GenerateUpdater(obj, target, end, duration, ease);
vt.callback = callback;

_updater.AddTween(vt);

return vt.id;
}

public int AddTween(GObject obj, TweenTarget target, Vector2 end, int duration,
TweenEaseType ease = TweenEaseType.Linear, Action callback = null)
{
UITween vt = new UITween();
vt.id = _id++;
vt.duration = duration;
vt.updater = GenerateUpdater(obj, target, end, duration, ease);
vt.callback = callback;

_updater.AddTween(vt);
return vt.id;
}

public void StopTween(int id)
{
_updater.StopTween(id);
}

private Action<float> GenerateUpdater(GObject obj, TweenTarget target, float end, float duration,
TweenEaseType ease)
{
float origin = 0;
switch (target)
{
case TweenTarget.X:
origin = obj.x;
break;
case TweenTarget.Y:
origin = obj.y;
break;
case TweenTarget.ScaleX:
origin = obj.scaleX;
break;
case TweenTarget.ScaleY:
origin = obj.scaleY;
break;
case TweenTarget.Rotation:
origin = obj.rotation;
break;
case TweenTarget.Alpha:
origin = obj.alpha;
break;
case TweenTarget.Heihgt:
origin = obj.height;
break;
case TweenTarget.Width:
origin = obj.width;
break;
}

void Action(float time)
{

float ratio = EaseUtil.Evaluate(ease, time, duration, 1.7f, 0);
switch (target)
{
case TweenTarget.X:
obj.x = origin + ratio * end;
break;
case TweenTarget.Y:
obj.y = origin + ratio * end;
break;
case TweenTarget.ScaleX:
obj.scaleX = origin + ratio * end;
break;
case TweenTarget.ScaleY:
obj.scaleY = origin + ratio * end;
break;
case TweenTarget.Rotation:
obj.rotation = origin + ratio * end;
break;
case TweenTarget.Alpha:
obj.alpha = origin + ratio * end;
break;
case TweenTarget.Heihgt:
obj.height = origin + ratio * end;
break;
case TweenTarget.Width:
obj.width = origin + ratio * end;
break;
}
}

return Action;
}

private Action<float> GenerateUpdater(GObject obj, TweenTarget target, Vector2 end, float duration,
TweenEaseType ease)
{
Vector2 origin = Vector2.zero;
switch (target)
{
case TweenTarget.Position:
origin = obj.xy;
break;
case TweenTarget.Scale:
origin = obj.scale;
break;
case TweenTarget.Size:
origin = obj.size;
break;
}

void Action(float time)
{
float ratio = EaseUtil.Evaluate(ease, time, duration, 1.7f, 0);
switch (target)
{
case TweenTarget.Position:
obj.xy = origin + ratio * end;
break;
case TweenTarget.Scale:
obj.scale = origin + ratio * end;
break;
case TweenTarget.Size:
obj.size = origin + ratio * end;
break;
}
}

return Action;
}
}
UI 管理器
UINode
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 System.Collections.Generic;

namespace ReflectionUI
{
public class UINode
{
/// <summary>
/// 父节点
/// </summary>
public UINode parent;

/// <summary>
/// 层级索引
/// </summary>
public int layer;

/// <summary>
/// 所属UI
/// </summary>
public BaseView ui;

/// <summary>
/// 子节点
/// </summary>
public Dictionary<string, UINode> children = new Dictionary<string, UINode>();

public void Dispose()
{
parent.children.Remove(ui.id);

ui.Dispose();
}
}
}
UIManager
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Collections.Generic;
using FairyGUI;

public class UIManager : Singleton<UIManager>
{
private UINode _root = new UINode();

private int _id = 0;

private List<GComponent> _layer = new List<GComponent>();

private Dictionary<string, UINode> _savedView = new Dictionary<string, UINode>();

/// <summary>
/// 展示已存在的UI
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public UINode ShowUI(string name)
{
if (_savedView.TryGetValue(name, out var node))
{
node.ui.Show();
return node;
}

Debug.Log("UI未创建");
return null;
}

/// <summary>
/// 展示UI
/// </summary>
/// <param name="folder">UI所在文件夹</param>
/// <param name="package">UI包名</param>
/// <param name="name">自定义名称</param>
/// <param name="parent">父节点</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public UINode ShowUI<T>(string folder, string package, string name, UINode parent = null)
where T : BaseView, new()
{
//有保存的UI直接展示并返回
if (_savedView.TryGetValue(name, out var node))
{
node.ui.Show();
return node;
}

//创建没保存的UI
Type type = typeof(T);
//包加载逻辑暂时加载Resources文件夹内文件 如有需要可自行修改
string packagePath = folder + "/" + package;
UIPackage.AddPackage(packagePath);
//创建UI
T view = new T();
view.id = "ui_" + _id++;
view.name = name;
view.main = UIPackage.CreateObject(package, type.Name).asCom;

//创建UI节点
UINode ui = new UINode();
ui.ui = view;

if (parent != null)
{
int layerIndex = parent.layer + 1;
if (_layer.Count - 1 == parent.layer)
{
GComponent layer = new GComponent();
layer.displayObject.gameObject.name = "Layer_" + layerIndex;
GRoot.inst.AddChild(layer);
_layer.Add(layer);
}

_layer[layerIndex].AddChild(view.main);

parent.children.Add(view.id, ui);
ui.parent = parent;
ui.layer = layerIndex;
}
else
{
if (_layer.Count == 0)
{
GComponent layer = new GComponent();
layer.displayObject.gameObject.name = "Layer_0";
GRoot.inst.AddChild(layer);
_layer.Add(layer);
}

_layer[0].AddChild(view.main);

_root.children.Add(view.id, ui);
ui.parent = _root;
ui.layer = 0;
}

view.uiNode = ui;

view.InitConfig();
view.OnAwake();
view.Show();

return ui;
}

/// <summary>
/// 隐藏UI
/// </summary>
/// <param name="ui">UI节点</param>
public void HideUI(UINode ui)
{
foreach (var child in ui.children)
{
UINode uiChild = child.Value;
HideUI(uiChild);
}

ui.ui.Hide();
}

/// <summary>
/// 根据名字获取UI
/// </summary>
/// <param name="name">自定义名称</param>
/// <param name="parent">父节点</param>
/// <returns></returns>
public UINode GetUI(string name, UINode parent = null)
{
if (parent == null)
{
parent = _root;
}

if (parent.ui != null && name == parent.ui.name)
{
return parent;
}

UINode node;

foreach (var child in parent.children)
{
node = GetUI(name, child.Value);
if (node != null)
{
return node;
}
}

return null;
}

/// <summary>
/// 销毁UI
/// </summary>
/// <param name="ui">UI节点</param>
public void DisposeUI(UINode ui)
{
foreach (var child in ui.children)
{
UINode uiChild = child.Value;
DisposeUI(uiChild);
}

//移除保存的节点
_savedView.Remove(ui.ui.name);
ui.children = null;
ui.parent = null;
ui.ui.Dispose();
}

/// <summary>
/// 重新置于上层
/// </summary>
/// <param name="ui">UI节点</param>
public void ResetTop(UINode ui)
{
_layer[ui.layer].AddChild(ui.ui.main);
}

/// <summary>
/// 设置模态背景
/// </summary>
/// <param name="ui">UI节点</param>
/// <param name="model">模态背景对象</param>
public void SetModel(UINode ui, GGraph model)
{
GComponent layer = _layer[ui.layer];
int index = layer.GetChildIndex(ui.ui.main);
layer.AddChildAt(model, index);
}

/// <summary>
/// 保存节点 需要ui名称唯一
/// </summary>
/// <param name="name">自定义名称</param>
/// <param name="ui">UI节点</param>
public void SaveNode(string name, UINode ui)
{
_savedView[name] = ui;
}
}

项目

更新日志

2024-09-21

  1. 修复销毁 UI 的 bug
  2. 修复UI缓动 Update 的bug

2024-07-03

  1. 补充数据初始化方法。

2024-06-12

  1. 更新界面 UI 元素缓动方法。

2024-05-30

  1. 因项目逻辑更新,修改相关说明。

2024-04-30

  1. 新增 UI 类绑定。
  2. 新增模态窗口。
  3. 新增拖拽窗口。
  4. 新增 UI 管理器。

2024-04-27

  1. 新增滑动条 Action、Comp。
  2. 新增下拉框 Data、Comp 和 Action。

2024-04-25

  1. 新增简易窗口缓动动画。
  2. 新增悬浮窗 Action。

2024-04-21

  1. 新增 double 类型数据。
  2. 新增 UI 数据双向绑定。
  3. 新增手动设置拖拽和放置方法(用于 List Item)。

2024-04-20

  1. 更新拖拽功能。
  2. 调整部分代码属性域。

2024-02-22

  1. 更新基础版本。

评论