UGUI源码分析系列总览
相关前置:
UGUI EventSystem源码分析
UGUI源码分析:Selectable交互组件的基类
UGUI源码分析:开关组件Toggle与ToggleGroup
BaseClass: Selectable
Interface:IPointerClickHandler,ISubmitHandler,ICancelHandler
Intro: UGUI中下拉列表组件
Dropdown,是UGUI中下拉列表功能组件。它属于直接介绍的组件的混合体,Dropdown组件中运用到了Text、Toggle、Scrollbar、ScrollRect组件,更具具体需求可以舍弃除Toggle之外的组件进行自己的改造。
Dropdown的初始化过程仅有一个Awake,做了帮助实现动画过渡模块初始化。
protected override void Awake()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
return;
#endif
m_AlphaTweenRunner = new TweenRunner();
m_AlphaTweenRunner.Init(this); // 初始化渐变模块
if (m_CaptionImage)
m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
if (m_Template)
m_Template.gameObject.SetActive(false);
}
由事件为导向的触发:在点击事件与确定事件中都是相同的处理,执行了 Show() 方法显示下拉列表
public virtual void OnPointerClick(PointerEventData eventData)
{
Show();
}
public virtual void OnSubmit(BaseEventData eventData)
{
Show();
}
Show()方法:显示下拉列表的方法,也是整个Dropdown组件最关键的方法,它做到了以下几点内容
popupCanvas.sortingOrder = 30000;
)public void Show()
{
if (!IsActive() || !IsInteractable() || m_Dropdown != null)
return;
//初始状态时validTemplate为false来触发对于列表模板的初始化设置
if (!validTemplate)
{
//模板初始化方法:检测并设置模板,初始化模板绑定相关组件并调整模板UI层级,若没有通过检查则模板标记为不可用状态。
SetupTemplate();
//若检测不通过则无法正常显示下拉列表
if (!validTemplate)
return;
}
....
}
//模板初始化
private void SetupTemplate()
{
validTemplate = false;
if (!m_Template)
{
return;
}
GameObject templateGo = m_Template.gameObject;
templateGo.SetActive(true);
Toggle itemToggle = m_Template.GetComponentInChildren<Toggle>();
validTemplate = true;
//各种条件检查模板是否满足要求
if (!itemToggle || itemToggle.transform == template)
{
validTemplate = false;
}
else if (!(itemToggle.transform.parent is RectTransform))
{
validTemplate = false;
}
else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform))
{
validTemplate = false;
}
else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform))
{
validTemplate = false;
}
if (!validTemplate)
{
templateGo.SetActive(false);
return;
}
//获取模板中的Item并添加DropdownItem组件
DropdownItem item = itemToggle.gameObject.AddComponent<DropdownItem>();
item.text = m_ItemText; // 绑定text
item.image = m_ItemImage;// 绑定image
item.toggle = itemToggle;// 绑定toggle
item.rectTransform = (RectTransform)itemToggle.transform; // 绑定transform
Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo);
popupCanvas.overrideSorting = true;
popupCanvas.sortingOrder = 30000; // 让列表模板处于UI的最高层
GetOrAddComponent<GraphicRaycaster>(templateGo);
GetOrAddComponent<CanvasGroup>(templateGo);
templateGo.SetActive(false);
validTemplate = true;
}
public void Show()
{
....
var list = ListPool<Canvas>.Get();
gameObject.GetComponentsInParent(false, list);
if (list.Count == 0)
return;
//获取父级路径下最近的canvas
Canvas rootCanvas = list[0];
ListPool<Canvas>.Release(list);
//显示模板准备复制列表
m_Template.gameObject.SetActive(true);
//复制列表模板
m_Dropdown = CreateDropdownList(m_Template.gameObject);
//进行改名
m_Dropdown.name = "Dropdown List";
m_Dropdown.SetActive(true);
// 设置新的列表模板的父级
RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform;
dropdownRectTransform.SetParent(m_Template.transform.parent, false);
// 创建列表Item
DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>();
GameObject content = itemTemplate.rectTransform.parent.gameObject;
RectTransform contentRectTransform = content.transform as RectTransform;
itemTemplate.rectTransform.gameObject.SetActive(true);
Rect dropdownContentRect = contentRectTransform.rect;
Rect itemTemplateRect = itemTemplate.rectTransform.rect;
//计算Item与背景边界的偏移量
Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition;
Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition;
Vector2 itemSize = itemTemplateRect.size;
//清空DropdownItem List 准备开始选项Itme的创建
m_Items.Clear();
Toggle prev = null;
for (int i = 0; i < options.Count; ++i)
{
OptionData data = options[i];
//创建Item
DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items);
if (item == null)
continue;
// 设置toggle初始状态以及注册事件监听
item.toggle.isOn = value == i;
item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle));
//标记当前选项
if (item.toggle.isOn)
item.toggle.Select();
// 设置Item的导航
if (prev != null)
{
Navigation prevNav = prev.navigation;
Navigation toggleNav = item.toggle.navigation;
prevNav.mode = Navigation.Mode.Explicit;
toggleNav.mode = Navigation.Mode.Explicit;
prevNav.selectOnDown = item.toggle;
prevNav.selectOnRight = item.toggle;
toggleNav.selectOnLeft = prev;
toggleNav.selectOnUp = prev;
prev.navigation = prevNav;
item.toggle.navigation = toggleNav;
}
prev = item.toggle;
}
// 计算内容区域的高度
Vector2 sizeDelta = contentRectTransform.sizeDelta;
sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y;
contentRectTransform.sizeDelta = sizeDelta;
//计算是否有额外空区域(当内容区域小于列表本身的区域时调整列表大小)
float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height;
if (extraSpace > 0)
dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace);
// 当列表处于canvas外部时,将其按坐标轴进行翻转
Vector3[] corners = new Vector3[4];
dropdownRectTransform.GetWorldCorners(corners);
RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform;
Rect rootCanvasRect = rootCanvasRectTransform.rect;
for (int axis = 0; axis < 2; axis++)
{
bool outside = false;
for (int i = 0; i < 4; i++)
{
Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]);
if (corner[axis] < rootCanvasRect.min[axis] || corner[axis] > rootCanvasRect.max[axis])
{
outside = true;
break;
}
}
if (outside)
RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false);
}
....
}
例:白色框区域代表Canvas区域,此时模板处于Canvas的外部。
当运行时点击Dropdown,出现的列表被水平翻转了。
blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1; 即 29999
)public void Show()
{
....
for (int i = 0; i < m_Items.Count; i++)
{
RectTransform itemRect = m_Items[i].rectTransform;
itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0);
itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0);
itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y);
itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y);
}
// 下拉列表渐出效果
AlphaFadeList(0.15f, 0f, 1f);
// 隐藏模板
m_Template.gameObject.SetActive(false);
itemTemplate.gameObject.SetActive(false);
// 创建拦截模板,用于监听点击事件来隐藏下拉列表,层级会低于下拉列表(2999)
m_Blocker = CreateBlocker(rootCanvas);
}
当下拉列表中的Item被选择时(即Toggle监听事件触发时)会执行选择操作,更新选项值并隐藏列表
private void OnSelectItem(Toggle toggle)
{
if (!toggle.isOn)
toggle.isOn = true;
int selectedIndex = -1;
Transform tr = toggle.transform;
Transform parent = tr.parent;
for (int i = 0; i < parent.childCount; i++)
{
if (parent.GetChild(i) == tr)
{
selectedIndex = i - 1;
break;
}
}
if (selectedIndex < 0)
return;
//更新 当前选项值
value = selectedIndex;
// 隐藏下拉列表
Hide();
}
value值发生变化时,会执行 RefreshShownValue() 刷新显示,并执行事件m_OnValueChanged.Invoke(m_Value);
//更新当前选项的信息至captionText、captionImage
public void RefreshShownValue()
{
OptionData data = s_NoOptionData;
if (options.Count > 0)
data = options[Mathf.Clamp(m_Value, 0, options.Count - 1)];
if (m_CaptionText)
{
if (data != null && data.text != null)
m_CaptionText.text = data.text;
else
m_CaptionText.text = "";
}
if (m_CaptionImage)
{
if (data != null)
m_CaptionImage.sprite = data.image;
else
m_CaptionImage.sprite = null;
m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
}
}
.
.
.
.
.
嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)