UGUI内核大探究(十三)Dropdown

转载出处: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里最重要的一个方法,虽然很长但是值得贴一下。

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // Show the dropdown.  
  2. //  
  3. // Plan for dropdown scrolling to ensure dropdown is contained within screen.  
  4. //  
  5. // We assume the Canvas is the screen that the dropdown must be kept inside.  
  6. // This is always valid for screen space canvas modes.  
  7. // For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor.  
  8. // We consider it a fair constraint that the canvas must be big enough to contains dropdowns.  
  9. public void Show()  
  10. {  
  11.     if (!IsActive() || !IsInteractable() || m_Dropdown != null)  
  12.         return;  
  13.   
  14.     if (!validTemplate)  
  15.     {  
  16.         SetupTemplate();  
  17.         if (!validTemplate)  
  18.             return;  
  19.     }  
  20.   
  21.     // Get root Canvas.  
  22.     var list = ListPool<Canvas>.Get();  
  23.     gameObject.GetComponentsInParent(false, list);  
  24.     if (list.Count == 0)  
  25.         return;  
  26.     Canvas rootCanvas = list[0];  
  27.     ListPool<Canvas>.Release(list);  
  28.   
  29.     m_Template.gameObject.SetActive(true);  
  30.   
  31.     // Instantiate the drop-down template  
  32.     m_Dropdown = CreateDropdownList(m_Template.gameObject);  
  33.     m_Dropdown.name = "Dropdown List";  
  34.     m_Dropdown.SetActive(true);  
  35.   
  36.     // Make drop-down RectTransform have same values as original.  
  37.     RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform;  
  38.     dropdownRectTransform.SetParent(m_Template.transform.parent, false);  
  39.   
  40.     // Instantiate the drop-down list items  
  41.   
  42.     // Find the dropdown item and disable it.  
  43.     DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>();  
  44.   
  45.     GameObject content = itemTemplate.rectTransform.parent.gameObject;  
  46.     RectTransform contentRectTransform = content.transform as RectTransform;  
  47.     itemTemplate.rectTransform.gameObject.SetActive(true);  
  48.   
  49.     // Get the rects of the dropdown and item  
  50.     Rect dropdownContentRect = contentRectTransform.rect;  
  51.     Rect itemTemplateRect = itemTemplate.rectTransform.rect;  
  52.   
  53.     // Calculate the visual offset between the item's edges and the background's edges  
  54.     Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition;  
  55.     Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition;  
  56.     Vector2 itemSize = itemTemplateRect.size;  
  57.   
  58.     m_Items.Clear();  
  59.   
  60.     Toggle prev = null;  
  61.     for (int i = 0; i < options.Count; ++i)  
  62.     {  
  63.         OptionData data = options[i];  
  64.         DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items);  
  65.         if (item == null)  
  66.             continue;  
  67.   
  68.         // Automatically set up a toggle state change listener  
  69.         item.toggle.isOn = value == i;  
  70.         item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle));  
  71.   
  72.         // Select current option  
  73.         if (item.toggle.isOn)  
  74.             item.toggle.Select();  
  75.   
  76.         // Automatically set up explicit navigation  
  77.         if (prev != null)  
  78.         {  
  79.             Navigation prevNav = prev.navigation;  
  80.             Navigation toggleNav = item.toggle.navigation;  
  81.             prevNav.mode = Navigation.Mode.Explicit;  
  82.             toggleNav.mode = Navigation.Mode.Explicit;  
  83.   
  84.             prevNav.selectOnDown = item.toggle;  
  85.             prevNav.selectOnRight = item.toggle;  
  86.             toggleNav.selectOnLeft = prev;  
  87.             toggleNav.selectOnUp = prev;  
  88.   
  89.             prev.navigation = prevNav;  
  90.             item.toggle.navigation = toggleNav;  
  91.         }  
  92.         prev = item.toggle;  
  93.     }  
  94.   
  95.     // Reposition all items now that all of them have been added  
  96.     Vector2 sizeDelta = contentRectTransform.sizeDelta;  
  97.     sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y;  
  98.     contentRectTransform.sizeDelta = sizeDelta;  
  99.   
  100.     float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height;  
  101.     if (extraSpace > 0)  
  102.         dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace);  
  103.   
  104.     // Invert anchoring and position if dropdown is partially or fully outside of canvas rect.  
  105.     // Typically this will have the effect of placing the dropdown above the button instead of below,  
  106.     // but it works as inversion regardless of initial setup.  
  107.     Vector3[] corners = new Vector3[4];  
  108.     dropdownRectTransform.GetWorldCorners(corners);  
  109.     bool outside = false;  
  110.     RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform;  
  111.     for (int i = 0; i < 4; i++)  
  112.     {  
  113.         Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]);  
  114.         if (!rootCanvasRectTransform.rect.Contains(corner))  
  115.         {  
  116.             outside = true;  
  117.             break;  
  118.         }  
  119.     }  
  120.     if (outside)  
  121.     {  
  122.         RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, 0, falsefalse);  
  123.         RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, 1, falsefalse);  
  124.     }  
  125.   
  126.     for (int i = 0; i < m_Items.Count; i++)  
  127.     {  
  128.         RectTransform itemRect = m_Items[i].rectTransform;  
  129.         itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0);  
  130.         itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0);  
  131.         itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y);  
  132.         itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y);  
  133.     }  
  134.   
  135.     // Fade in the popup  
  136.     AlphaFadeList(0.15f, 0f, 1f);  
  137.   
  138.     // Make drop-down template and item template inactive  
  139.     m_Template.gameObject.SetActive(false);  
  140.     itemTemplate.gameObject.SetActive(false);  
  141.   
  142.     m_Blocker = CreateBlocker(rootCanvas);  
  143. }  

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方法要简单的多:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // Hide the dropdown.  
  2. public void Hide()  
  3. {  
  4.     if (m_Dropdown != null)  
  5.     {  
  6.         AlphaFadeList(0.15f, 0f);  
  7.         StartCoroutine(DelayedDestroyDropdownList(0.15f));  
  8.     }  
  9.     if (m_Blocker != null)  
  10.         DestroyBlocker(m_Blocker);  
  11.     m_Blocker = null;  
  12.     Select();  
  13. }  
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上显示选中的选项。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值