Dropdown(下拉框)可谓是UGUI的集大成者,在Unity Editor里新建一个Dropdown,会随之附赠Text(Label对象)、Image(Arrow对象)、ScrollRect(Template对象)、Toggle(Template\Viewport\Content\item)和ScrollBar(Template\Scrollbar)。点击运行展开下拉框后还会创建一个Button(Blocker),而且还根据Template再实例化一个可见的Dropdown List。如此复杂的一个组件,竟然代码只有600余行,不得不让我们感叹Unity官方深谙组合之道。本文就探究一下Dropdown的神奇之处。
按照惯例,附上UGUI源码下载地址。
我们首先看一下Dropdown的内部类DropdownItem(下拉项)。运行状态下展开下拉框,可以看到它被加到Item上面。
DropdownItem继承自MonoBehaviour和IPointerEnterHandler, ICancelHandler两个接口。
它包含了四个属性:text、image、rectTransform和toggle。
OnPointerEnter(当鼠标进入)方法继承自IPointerEnterHandler,调用EventSystem的SetSelectedGameObject将本对象设置为选中的对象(祥参UGUI内核大探究(一)EventSystem)。具体表现就是Item对象的背景颜色变了。
OnCancel(取消键按下)方法继承自ICancelHandler,获取父对象中的dropdown组件,调用Hide方法。具体表现就是选项表(Dropdown List)隐藏了。
Dropdown继承自Selectable和IPointerClickHandler, ISubmitHandler, ICancelHandler三个接口。
Dropdown重写了Awake方法,新建了一个FloatTween类型的TweenRunner变量m_AlphaTweenRunner并初始化,这个变量在显示/隐藏选项表(Dropdown List)的时候执行透明度渐变效果。然后设置了m_CaptionImage是否可用,这个变量对应于编辑器里的Caption Image,如果选中的选项(Options)设置了图片的话,就会使用m_CaptionImage显示在Dropdown的标题上。最后设置m_Template为false,这个变量对应于Template对象,用于作为模板实例化选项表。
OnPointerClick(继承自IPointerClickHandler,点击时)和OnSubmit(继承自ISubmitHandler,确认键按下时)调用了Show方法,而ICancelHandler(继承自ICancelHandler,取消键按下时)调用了Hide方法。
Show是Dropdown里最重要的一个方法,虽然很长但是值得贴一下。
// Show the dropdown.
//
// Plan for dropdown scrolling to ensure dropdown is contained within screen.
//
// We assume the Canvas is the screen that the dropdown must be kept inside.
// This is always valid for screen space canvas modes.
// For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor.
// We consider it a fair constraint that the canvas must be big enough to contains dropdowns.
public void Show()
{
if (!IsActive() || !IsInteractable() || m_Dropdown != null)
return;
if (!validTemplate)
{
SetupTemplate();
if (!validTemplate)
return;
}
// Get root Canvas.
var list = ListPool
Show的步骤:
1、调用SetupTemplate方法,设置模板。SetupTemplate方法里判断了一系列限定,接着为item对象添加了DropdownItem组件,并为DropdownItem的四个属性赋值,然后为自己添加Canvas组件,设置overrideSorting为true,并sortingOrder为30000,这可以让选项表尽可能的显示在最前面,然后添加GraphicRaycaster和CanvasGroup组件,为了接受到鼠标事件。
2、调用CreateDropdownList方法,以m_Template为模板创建m_Dropdown(选项表)。并为m_Dropdown修改名字,设置父对象。然后在子对象里找到DropdownItem保存为itemTemplate,以itemTemplate为模板,创建每一个Item(数据为OptionData,对应编辑器里的Options下的Option),为Item的Toggle的onValueChanged事件添加监听OnSelectItem(根据选中的Toggle,找到它在父对象Content中的Index,为Dropdown设置值value,并隐藏Dropdown List),最后设置导航。
3、根据Item的数量设置Content的尺寸,Content是Scroll Rect(祥参UGUI内核大探究(十一)ScrollRect与ScrollBar)里面用于显示内容的对象。并且如果Dropdown List的高度大于Content的高度,便修正它的高度与Content相同。然后判断Dropdown List的四角是否超出了rootCanvas(Dropdown最上层的Canvas)的边界,便翻转Dropdown List,这种时候,我们将会看到选项表在Dropdown的上面,如图。
然后设置Item的位置和尺寸。
4、Alpha渐变(m_AlphaTweenRunner)显示Dropdown List,并将m_Template和itemTemplate设置为无效的。
5、调用CreateBlocker创建Blocker。Blocker在rootCanvas下一级,尺寸与rootCanvas相同,sortingOrder比Dropdown List的小1(29999)。添加了Image组件,颜色为全透明,添加了Button组件,添加了onClick的监听,回调Hide方法。由此我们可知道Blocker是用于阻挡住鼠标事件,即Dropdown List显示时,点击选项表以外的区域,都只是隐藏选项表,不会触发其他的组件。
Hide方法要简单的多:
// Hide the dropdown.
public void Hide()
{
if (m_Dropdown != null)
{
AlphaFadeList(0.15f, 0f);
StartCoroutine(DelayedDestroyDropdownList(0.15f));
}
if (m_Blocker != null)
DestroyBlocker(m_Blocker);
m_Blocker = null;
Select();
}
Alpha渐变隐藏Dropdown List,并在渐变结束后Destroy所有的Item和Dropdown List。接着DestroyBlocker。最后设置本对象为Select(高亮状态)。
Dropdown的值value是一个属性(Property),对应变量m_Value。它的set访问器(参考C#语法小知识(六)属性与索引器)里,会将参数值限定在0到options.Count(选项数量) - 1之间。刷新,并发送m_OnValueChanged事件(可在编辑器里设置)。
刷新Refresh方法里,会在options里找到value值对应的OptionData,为m_CaptionText设置文本和m_CaptionImage设置图片,即在Dropdown上显示选中的选项。