转载出处:http://blog.csdn.net/ecidevilin/article/details/52570871?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
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里最重要的一个方法,虽然很长但是值得贴一下。
-
-
-
-
-
-
-
-
- public void Show()
- {
- if (!IsActive() || !IsInteractable() || m_Dropdown != null)
- return;
-
- if (!validTemplate)
- {
- SetupTemplate();
- if (!validTemplate)
- return;
- }
-
-
- var list = ListPool<Canvas>.Get();
- gameObject.GetComponentsInParent(false, list);
- if (list.Count == 0)
- return;
- 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);
-
-
-
-
- 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;
-
-
- Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition;
- Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition;
- Vector2 itemSize = itemTemplateRect.size;
-
- m_Items.Clear();
-
- Toggle prev = null;
- for (int i = 0; i < options.Count; ++i)
- {
- OptionData data = options[i];
- DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items);
- if (item == null)
- continue;
-
-
- item.toggle.isOn = value == i;
- item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle));
-
-
- if (item.toggle.isOn)
- item.toggle.Select();
-
-
- 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);
-
-
-
-
- Vector3[] corners = new Vector3[4];
- dropdownRectTransform.GetWorldCorners(corners);
- bool outside = false;
- RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform;
- for (int i = 0; i < 4; i++)
- {
- Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]);
- if (!rootCanvasRectTransform.rect.Contains(corner))
- {
- outside = true;
- break;
- }
- }
- if (outside)
- {
- RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, 0, false, false);
- RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, 1, false, false);
- }
-
- 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);
-
- m_Blocker = CreateBlocker(rootCanvas);
- }
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方法要简单的多:
-
- 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上显示选中的选项。