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<Canvas>.Get();gameObject.GetComponentsInParent(false, list);if (list.Count == 0)return;Canvas rootCanvas = list[0];ListPool<Canvas>.Release(list);m_Template.gameObject.SetActive(true);// Instantiate the drop-down templatem_Dropdown = CreateDropdownList(m_Template.gameObject);m_Dropdown.name = "Dropdown List";m_Dropdown.SetActive(true);// Make drop-down RectTransform have same values as original.RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform;dropdownRectTransform.SetParent(m_Template.transform.parent, false);// Instantiate the drop-down list items// Find the dropdown item and disable it.DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>();GameObject content = itemTemplate.rectTransform.parent.gameObject;RectTransform contentRectTransform = content.transform as RectTransform;itemTemplate.rectTransform.gameObject.SetActive(true);// Get the rects of the dropdown and itemRect dropdownContentRect = contentRectTransform.rect;Rect itemTemplateRect = itemTemplate.rectTransform.rect;// Calculate the visual offset between the item's edges and the background's edgesVector2 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;// Automatically set up a toggle state change listeneritem.toggle.isOn = value == i;item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle));// Select current optionif (item.toggle.isOn)item.toggle.Select();// Automatically set up explicit navigationif (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;}// Reposition all items now that all of them have been addedVector2 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);// Invert anchoring and position if dropdown is partially or fully outside of canvas rect.// Typically this will have the effect of placing the dropdown above the button instead of below,// but it works as inversion regardless of initial setup.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);}// Fade in the popupAlphaFadeList(0.15f, 0f, 1f);// Make drop-down template and item template inactivem_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方法要简单的多:

        // 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上显示选中的选项。

UGUI内核大探究(十三)Dropdown相关推荐

  1. UGUI内核大探究(十六)InputField

    InputField是UGUI的重要组件,可以提供文本输入功能,是与用户交互的一个重要手段.我们可以在编辑器里,为OnValueChanged和OnEndEdit两个事件添加监听,这样就可以获得用户输 ...

  2. UGUI内核大探究(八)MaskableGraphic

    MaskableGraphic是UGUI的核心组件,它继承自Graphic.MaskableGraphic是一个抽象类,它的派生类有RawImage.Image.Text.顾名思义,MaskableG ...

  3. UGUI内核大探究(十八)Raycaster

    射线其实是属于事件系统,它在EventSystem/Raycasters目录下,有BaseRaycaster.PhysicsRaycaster和Physics2DRaycaster三个类,命名空间也是 ...

  4. UGUI内核大探究(十二)Slider

    Slider是UGUI的一个组件,使用它可以实现滑动条,算是一个比较常用的组件,它与ScrollBar(参考UGUI内核大探究(十一)ScrollRect与ScrollBar)有些类似,但又不太相同. ...

  5. UGUI内核大探究(二)执行事件

    UGUI内核大探究(一)EventSystem我们探究了事件系统,其中我们讲到EventSystem可以通过ExecuteEvents这个类来执行事件,那么事件是如何执行的呢?这里涉及到了两个文件Ev ...

  6. UGUI内核大探究(十一)ScrollRect与ScrollBar

    当我们在Unity Editor里创建一个Scroll View的时候含有ScrollRect的对象,它下面还有三个子对象,两个含有ScrollBar组件的子对象是作为滚动条,一个Viewport用于 ...

  7. UGUI内核大探究(九)Image与RawImage

    Image组件是UGUI里最常用的组件(可能没有之一),我们知道其实还有一个RawImage组件.那么二者的区别是什么呢?之前的文章UGUI内核大探究(八)MaskableGraphic中我们提到过, ...

  8. UGUI内核大探究(一)EventSystem

    2019独角兽企业重金招聘Python工程师标准>>> UGUI是Unity3D官方推出的UI系统,为了更好的使用UGUI,我们就需要去了解它. UGUI代码开源,我们可以从bitb ...

  9. C++新特性探究(十三):右值引用(r-value ref)探究

    相关博文: C++新特性探究(十三):右值引用(r-value ref)&&探究 C++新特性探究(十六):move constructor移动构造 C++新特性探究(13.5):右值 ...

最新文章

  1. php获取访问浏览器,php获取访问者浏览器
  2. 1、Spring简介
  3. 【ZOJ - 3329】One Person Game(带循环的概率dp,数学期望,高斯消元,数学)
  4. NOIP2016 复赛普及组第 1 题 买铅笔 方法一
  5. 计算机网络之网络概述:6、TCP/IP模型和5层参考模型
  6. JAVA字符串占位符替换
  7. ssis 计划任务_SSIS FTP任务概述
  8. moment获取几小时前_请问怎么把“多少小时前”精确到“多少小时多少分钟前”...
  9. java的继承实例_Java 继承方法实例详解
  10. [源码和文档分享]基于QT实现的alpha-beta剪枝算法搜索的象棋人机博弈游戏
  11. 错误:“应用程序无法启动,因为应用程序的并行配置不正确。请参阅应用程序事件日志,或使用命令行sxstrace.exe工具” 的解决
  12. 通过doi可以检索到文献_什么是DOI?如何获取文献的DOI?
  13. 智媒链全球社区见面会 中国·北京(首场)
  14. 基于JAVA-超市会员积分管理系统-计算机毕业设计源码+系统+lw文档+部署
  15. 苹果账号开启双重认证,以及如何在移动设备上同时添加私人账号和开发者账号
  16. 敲击键盘后字符怎么出现在显示器
  17. 洛谷-P2550-彩票摇奖
  18. SwiftUI AVKit 之合并和叠加音频mp3 wav 并输出(教程含源码)
  19. 【Android】高德地图在Debug模式下运行正常但是打Release包时则闪退解决办法
  20. 单片机c语言中枚举,嵌入式开发-枚举详解---朱有鹏

热门文章

  1. PHP的MySQL持久化连接
  2. 为什么韭菜一定要割三次后开花?
  3. 腾讯云轻量应用服务器如何安装 Docker 并配置镜像加速源?
  4. 印度也算“IT”大国吗?-在印度电信的遭遇
  5. 用什么软件工具可以一键添加渐入效果同时虚化边框背景呢?
  6. CSMA/CD(具有冲突检测的载波侦听多路访问)
  7. 谷歌云端硬盘 转存_如何在Mac上设置和使用Google云端硬盘
  8. 关于为什么忽然很多博客文章都没有了
  9. 应用数据流状态管理框架Redux简介、设计思想、核心概念及工作流
  10. sa-token 注解式鉴权