UGUI_Dropdown用法以及解决 多次点击同一项无回调问题
用法
添加事件代码
using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; public class DropDownTest : MonoBehaviour { public Dropdown dropDown; // 是否是代码设置下拉 Item 值 private bool isCodeSetItemValue = false; void Start() { // 设置监听 SetDropDownAddListener(OnValueChange); SetDropDownItemValue(1); } ////// 当点击后值改变是触发 (切换下拉选项) /// /// 是点击的选项在OptionData下的索引值 void OnValueChange(int v) { //切换选项 时处理其他的逻辑... Debug.Log("点击下拉控件的索引是..." + v); Debug.Log("点击下拉控件的内容是..." + dropDown.options[v].text); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { List listOptions = new List (); listOptions.Add(new Dropdown.OptionData("Option 0")); listOptions.Add(new Dropdown.OptionData("Option 1")); AddDropDownOptionsData(listOptions); } if (Input.GetKeyDown(KeyCode.A)) { AddDropDownOptionsData("Option " + dropDown.options.Count); } if (Input.GetKeyDown(KeyCode.R)) { RemoveAtDropDownOptionsData(dropDown.options.Count - 1); } if (Input.GetKeyDown(KeyCode.C)) { ClearDropDownOptionsData(); } } /// /// 设置选择的下拉Item /// /// void SetDropDownItemValue(int ItemIndex) { // 代码设置的值 isCodeSetItemValue = true; if (dropDown.options == null) { Debug.Log(GetType()+ "/SetDropDownItemValue()/下拉列表为空,请检查"); return; } if (ItemIndex >= dropDown.options.Count) { ItemIndex = dropDown.options.Count - 1; } if (ItemIndex < 0 ) { ItemIndex = 0; } dropDown.value = ItemIndex; } /// /// 是否可以点击 /// void SetDropDownInteractable() { //是否可以点击 dropDown.interactable = true; } /// /// 设置显示字体大小 /// /// void SetDropDownCaptionTextFontSize(int fontSize) { //设置显示字体大小 dropDown.captionText.fontSize = fontSize; } /// /// 设置下拉Item显示字体大小 /// /// void SetDropDownItemTextFontSize(int fontSize) { //设置下拉Item显示字体大小 dropDown.itemText.fontSize = fontSize; } /// /// 添加一个列表下拉数据 /// /// void AddDropDownOptionsData(List listOptions) { dropDown.AddOptions(listOptions); } /// /// 添加一个下拉数据 /// /// void AddDropDownOptionsData(string itemText) { //添加一个下拉选项 Dropdown.OptionData data = new Dropdown.OptionData(); data.text = itemText; //data.image = "指定一个图片做背景不指定则使用默认"; dropDown.options.Add(data); } /// /// 移除指定位置 参数:索引 /// /// void RemoveAtDropDownOptionsData(int index) { // 安全校验 if (index >= dropDown.options.Count || index < 0) { return; } //移除指定位置 参数:索引 dropDown.options.RemoveAt(index); } /// /// 直接清理掉所有的下拉选项 /// void ClearDropDownOptionsData() { //直接清理掉所有的下拉选项, dropDown.ClearOptions(); } /// /// 当点击后值改变是触发 (切换下拉选项) /// void SetDropDownAddListener(UnityAction<int> OnValueChangeListener) { //当点击后值改变是触发 (切换下拉选项) dropDown.onValueChanged.AddListener((value)=> { // 手动代码设置的值不触发事件(根据需要可以保留或者去掉) if (isCodeSetItemValue == true) { isCodeSetItemValue = false; return; } OnValueChangeListener(value); }); } }
多次点击同一项无回调问题
截止目前Unity2018.3.3版本下的UGUI Dropdown依旧如题。
当然,目前的功能是基本能满足大部分场景应用的。
但如果你需要每次点击都有回调的话,那么,你就需要扩展或者重写Dropdown。
我们新建一个类:DropdownExtent,并且让它继承Dropdown,然后重写OnPointerClick:
using UnityEngine.EventSystems; using UnityEngine.UI; public class DropdownExtend : Dropdown { public bool AlwaysCallback = false; public void Show() { base.Show(); var toggleRoot = transform.Find("Dropdown List/Viewport/Content"); Toggle[] toggleList = toggleRoot.GetComponentsInChildren(false); foreach (var temp in toggleList) { temp.onValueChanged.RemoveAllListeners(); temp.isOn = false; var temp1 = temp; temp.onValueChanged.AddListener(x => OnSelectItemExtend(temp1)); } } public override void OnPointerClick(PointerEventData eventData) { Show(); } public void OnSelectItemExtend(Toggle toggle) { if (!toggle.isOn) { toggle.isOn = true; return; } var selectedIndex = -1; var tr = toggle.transform; var parent = tr.parent; for (var i = 0; i < parent.childCount; i++) { if (parent.GetChild(i) != tr) continue; selectedIndex = i - 1; break; } if (selectedIndex < 0) return; if (value == selectedIndex && AlwaysCallback) onValueChanged.Invoke(value); else value = selectedIndex; Hide(); } }
测试代码
using UnityEngine; public class Test : MonoBehaviour { public DropdownExtend _dropdownEx; void Start() { _dropdownEx.AlwaysCallback = true; _dropdownEx.onValueChanged.AddListener(OnChanged); } private void OnChanged(int index) { Debug.Log(index); } }
说明:
扩展类将替换掉原来的Dropdown组件,重新关联属性面板上的引用。
DropDown 源码
using System; using System.Collections; using System.Collections.Generic; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI.CoroutineTween; namespace UnityEngine.UI { [AddComponentMenu("UI/Dropdown", 35)] [RequireComponent(typeof(RectTransform))] ////// A standard dropdown that presents a list of options when clicked, of which one can be chosen. /// /// /// The dropdown component is a Selectable. When an option is chosen, the label and/or image of the control changes to show the chosen option. /// /// When a dropdown event occurs a callback is sent to any registered listeners of onValueChanged. /// public class Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler { protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler { [SerializeField] private Text m_Text; [SerializeField] private Image m_Image; [SerializeField] private RectTransform m_RectTransform; [SerializeField] private Toggle m_Toggle; public Text text { get { return m_Text; } set { m_Text = value; } } public Image image { get { return m_Image; } set { m_Image = value; } } public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } } public Toggle toggle { get { return m_Toggle; } set { m_Toggle = value; } } public virtual void OnPointerEnter(PointerEventData eventData) { EventSystem.current.SetSelectedGameObject(gameObject); } public virtual void OnCancel(BaseEventData eventData) { Dropdown dropdown = GetComponentInParent (); if (dropdown) dropdown.Hide(); } } [Serializable] /// /// Class to store the text and/or image of a single option in the dropdown list. /// public class OptionData { [SerializeField] private string m_Text; [SerializeField] private Sprite m_Image; /// /// The text associated with the option. /// public string text { get { return m_Text; } set { m_Text = value; } } /// /// The image associated with the option. /// public Sprite image { get { return m_Image; } set { m_Image = value; } } public OptionData() { } public OptionData(string text) { this.text = text; } public OptionData(Sprite image) { this.image = image; } /// /// Create an object representing a single option for the dropdown list. /// /// Optional text for the option. /// Optional image for the option. public OptionData(string text, Sprite image) { this.text = text; this.image = image; } } [Serializable] /// /// Class used internally to store the list of options for the dropdown list. /// /// /// The usage of this class is not exposed in the runtime API. It's only relevant for the PropertyDrawer drawing the list of options. /// public class OptionDataList { [SerializeField] private List m_Options; /// /// The list of options for the dropdown list. /// public List options { get { return m_Options; } set { m_Options = value; } } public OptionDataList() { options = new List (); } } [Serializable] /// /// UnityEvent callback for when a dropdown current option is changed. /// public class DropdownEvent : UnityEvent<int> {} // Template used to create the dropdown. [SerializeField] private RectTransform m_Template; /// /// The Rect Transform of the template for the dropdown list. /// public RectTransform template { get { return m_Template; } set { m_Template = value; RefreshShownValue(); } } // Text to be used as a caption for the current value. It's not required, but it's kept here for convenience. [SerializeField] private Text m_CaptionText; /// /// The Text component to hold the text of the currently selected option. /// public Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } } [SerializeField] private Image m_CaptionImage; /// /// The Image component to hold the image of the currently selected option. /// public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } } [Space] [SerializeField] private Text m_ItemText; /// /// The Text component to hold the text of the item. /// public Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } } [SerializeField] private Image m_ItemImage; /// /// The Image component to hold the image of the item /// public Image itemImage { get { return m_ItemImage; } set { m_ItemImage = value; RefreshShownValue(); } } [Space] [SerializeField] private int m_Value; [Space] // Items that will be visible when the dropdown is shown. // We box this into its own class so we can use a Property Drawer for it. [SerializeField] private OptionDataList m_Options = new OptionDataList(); /// /// The list of possible options. A text string and an image can be specified for each option. /// /// /// This is the list of options within the Dropdown. Each option contains Text and/or image data that you can specify using UI.Dropdown.OptionData before adding to the Dropdown list. /// This also unlocks the ability to edit the Dropdown, including the insertion, removal, and finding of options, as well as other useful tools /// /// /// /// /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking __Create__>__UI__>__Dropdown__. Attach this script to the Dropdown GameObject. /// /// using UnityEngine; /// using UnityEngine.UI; /// using System.Collections.Generic; /// /// public class Example : MonoBehaviour /// { /// //Use these for adding options to the Dropdown List /// Dropdown.OptionData m_NewData, m_NewData2; /// //The list of messages for the Dropdown /// List
m_Messages = new List (); /// /// /// //This is the Dropdown /// Dropdown m_Dropdown; /// string m_MyString; /// int m_Index; /// /// void Start() /// { /// //Fetch the Dropdown GameObject the script is attached to /// m_Dropdown = GetComponent (); /// //Clear the old options of the Dropdown menu /// m_Dropdown.ClearOptions(); /// /// //Create a new option for the Dropdown menu which reads "Option 1" and add to messages List /// m_NewData = new Dropdown.OptionData(); /// m_NewData.text = "Option 1"; /// m_Messages.Add(m_NewData); /// /// //Create a new option for the Dropdown menu which reads "Option 2" and add to messages List /// m_NewData2 = new Dropdown.OptionData(); /// m_NewData2.text = "Option 2"; /// m_Messages.Add(m_NewData2); /// /// //Take each entry in the message List /// foreach (Dropdown.OptionData message in m_Messages) /// { /// //Add each entry to the Dropdown /// m_Dropdown.options.Add(message); /// //Make the index equal to the total number of entries /// m_Index = m_Messages.Count - 1; /// } /// } /// /// //This OnGUI function is used here for a quick demonstration. See the [[wiki:UISystem|UI Section]] for more information about setting up your own UI. /// void OnGUI() /// { /// //TextField for user to type new entry to add to Dropdown /// m_MyString = GUI.TextField(new Rect(0, 40, 100, 40), m_MyString); /// /// //Press the "Add" Button to add a new entry to the Dropdown /// if (GUI.Button(new Rect(0, 0, 100, 40), "Add")) /// { /// //Make the index the last number of entries /// m_Index = m_Messages.Count; /// //Create a temporary option /// Dropdown.OptionData temp = new Dropdown.OptionData(); /// //Make the option the data from the TextField /// temp.text = m_MyString; /// /// //Update the messages list with the TextField data /// m_Messages.Add(temp); /// /// //Add the Textfield data to the Dropdown /// m_Dropdown.options.Insert(m_Index, temp); /// } /// /// //Press the "Remove" button to delete the selected option /// if (GUI.Button(new Rect(110, 0, 100, 40), "Remove")) /// { /// //Remove the current selected item from the Dropdown from the messages List /// m_Messages.RemoveAt(m_Dropdown.value); /// //Remove the current selection from the Dropdown /// m_Dropdown.options.RemoveAt(m_Dropdown.value); /// } /// } /// } /// /// public List options { get { return m_Options.options; } set { m_Options.options = value; RefreshShownValue(); } } [Space] // Notification triggered when the dropdown changes. [SerializeField] private DropdownEvent m_OnValueChanged = new DropdownEvent(); /// /// A UnityEvent that is invoked when when a user has clicked one of the options in the dropdown list. /// /// /// Use this to detect when a user selects one or more options in the Dropdown. Add a listener to perform an action when this UnityEvent detects a selection by the user. See https://unity3d.com/learn/tutorials/topics/scripting/delegates for more information on delegates. /// /// /// /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown. Attach this script to the Dropdown GameObject. /// //Set your own Text in the Inspector window /// /// using UnityEngine; /// using UnityEngine.UI; /// /// public class Example : MonoBehaviour /// { /// Dropdown m_Dropdown; /// public Text m_Text; /// /// void Start() /// { /// //Fetch the Dropdown GameObject /// m_Dropdown = GetComponent
(); /// //Add listener for when the value of the Dropdown changes, to take action /// m_Dropdown.onValueChanged.AddListener(delegate { /// DropdownValueChanged(m_Dropdown); /// }); /// /// //Initialise the Text to say the first value of the Dropdown /// m_Text.text = "First Value : " + m_Dropdown.value; /// } /// /// //Ouput the new value of the Dropdown into Text /// void DropdownValueChanged(Dropdown change) /// { /// m_Text.text = "New Value : " + change.value; /// } /// } /// /// public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } [SerializeField] private float m_AlphaFadeSpeed = 0.15f; /// /// The time interval at which a drop down will appear and disappear /// public float alphaFadeSpeed { get { return m_AlphaFadeSpeed; } set { m_AlphaFadeSpeed = value; } } private GameObject m_Dropdown; private GameObject m_Blocker; private List m_Items = new List (); private TweenRunner m_AlphaTweenRunner; private bool validTemplate = false; private static OptionData s_NoOptionData = new OptionData(); /// /// The Value is the index number of the current selection in the Dropdown. 0 is the first option in the Dropdown, 1 is the second, and so on. /// /// /// /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking __Create__>__UI__>__Dropdown__. Attach this script to the Dropdown GameObject. /// //Set your own Text in the Inspector window /// /// using UnityEngine; /// using UnityEngine.UI; /// /// public class Example : MonoBehaviour /// { /// //Attach this script to a Dropdown GameObject /// Dropdown m_Dropdown; /// //This is the string that stores the current selection m_Text of the Dropdown /// string m_Message; /// //This Text outputs the current selection to the screen /// public Text m_Text; /// //This is the index value of the Dropdown /// int m_DropdownValue; /// /// void Start() /// { /// //Fetch the DropDown component from the GameObject /// m_Dropdown = GetComponent
(); /// //Output the first Dropdown index value /// Debug.Log("Starting Dropdown Value : " + m_Dropdown.value); /// } /// /// void Update() /// { /// //Keep the current index of the Dropdown in a variable /// m_DropdownValue = m_Dropdown.value; /// //Change the message to say the name of the current Dropdown selection using the value /// m_Message = m_Dropdown.options[m_DropdownValue].text; /// //Change the onscreen Text to reflect the current Dropdown selection /// m_Text.text = m_Message; /// } /// } /// /// public int value { get { return m_Value; } set { Set(value); } } /// /// Set index number of the current selection in the Dropdown without invoking onValueChanged callback. /// /// The new index for the current selection. public void SetValueWithoutNotify(int input) { Set(input, false); } void Set(int value, bool sendCallback = true) { if (Application.isPlaying && (value == m_Value || options.Count == 0)) return; m_Value = Mathf.Clamp(value, 0, options.Count - 1); RefreshShownValue(); if (sendCallback) { // Notify all listeners UISystemProfilerApi.AddMarker("Dropdown.value", this); m_OnValueChanged.Invoke(m_Value); } } protected Dropdown() {} 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); } protected override void Start() { base.Start(); RefreshShownValue(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); if (!IsActive()) return; RefreshShownValue(); } #endif protected override void OnDisable() { //Destroy dropdown and blocker in case user deactivates the dropdown when they click an option (case 935649) ImmediateDestroyDropdownList(); if (m_Blocker != null) DestroyBlocker(m_Blocker); m_Blocker = null; base.OnDisable(); } /// /// Refreshes the text and image (if available) of the currently selected option. /// /// /// If you have modified the list of options, you should call this method afterwards to ensure that the visual state of the dropdown corresponds to the updated options. /// 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); } } /// /// Add multiple options to the options of the Dropdown based on a list of OptionData objects. /// /// The list of OptionData to add. /// /// /// See AddOptions(List options) for code example of usages. /// public void AddOptions(List options) { this.options.AddRange(options); RefreshShownValue(); } /// /// Add multiple text-only options to the options of the Dropdown based on a list of strings. /// /// /// Add a List of string messages to the Dropdown. The Dropdown shows each member of the list as a separate option. /// /// The list of text strings to add. /// /// /// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown. Attach this script to the Dropdown GameObject. /// /// using System.Collections.Generic; /// using UnityEngine; /// using UnityEngine.UI; /// /// public class Example : MonoBehaviour /// { /// //Create a List of new Dropdown options /// List
m_DropOptions = new List { "Option 1", "Option 2"}; /// //This is the Dropdown /// Dropdown m_Dropdown; /// /// void Start() /// { /// //Fetch the Dropdown GameObject the script is attached to /// m_Dropdown = GetComponent (); /// //Clear the old options of the Dropdown menu /// m_Dropdown.ClearOptions(); /// //Add the options created in the List above /// m_Dropdown.AddOptions(m_DropOptions); /// } /// } /// /// public void AddOptions(List<string> options) { for (int i = 0; i < options.Count; i++) this.options.Add(new OptionData(options[i])); RefreshShownValue(); } /// /// Add multiple image-only options to the options of the Dropdown based on a list of Sprites. /// /// The list of Sprites to add. /// /// See AddOptions(List options) for code example of usages. /// public void AddOptions(List options) { for (int i = 0; i < options.Count; i++) this.options.Add(new OptionData(options[i])); RefreshShownValue(); } /// /// Clear the list of options in the Dropdown. /// public void ClearOptions() { options.Clear(); m_Value = 0; RefreshShownValue(); } private void SetupTemplate() { validTemplate = false; if (!m_Template) { Debug.LogError("The dropdown template is not assigned. The template needs to be assigned and must have a child GameObject with a Toggle component serving as the item.", this); return; } GameObject templateGo = m_Template.gameObject; templateGo.SetActive(true); Toggle itemToggle = m_Template.GetComponentInChildren (); validTemplate = true; if (!itemToggle || itemToggle.transform == template) { validTemplate = false; Debug.LogError("The dropdown template is not valid. The template must have a child GameObject with a Toggle component serving as the item.", template); } else if (!(itemToggle.transform.parent is RectTransform)) { validTemplate = false; Debug.LogError("The dropdown template is not valid. The child GameObject with a Toggle component (the item) must have a RectTransform on its parent.", template); } else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform)) { validTemplate = false; Debug.LogError("The dropdown template is not valid. The Item Text must be on the item GameObject or children of it.", template); } else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform)) { validTemplate = false; Debug.LogError("The dropdown template is not valid. The Item Image must be on the item GameObject or children of it.", template); } if (!validTemplate) { templateGo.SetActive(false); return; } DropdownItem item = itemToggle.gameObject.AddComponent (); item.text = m_ItemText; item.image = m_ItemImage; item.toggle = itemToggle; item.rectTransform = (RectTransform)itemToggle.transform; // Find the Canvas that this dropdown is a part of Canvas parentCanvas = null; Transform parentTransform = m_Template.parent; while (parentTransform != null) { parentCanvas = parentTransform.GetComponent