Toggle真的是让人又爱又恨,它既有和ToggleGroup组合搭配的方便,又有一些不可控的情况,我就在项目中遇到了一个棘手的问题。
我们常常用到Toggle的onValueChanged改变事件,它很方便,即可改变自己状态,又可以通知同一个分组内的Toggle改变对应的状态,但是有时候我们需要判断点击的时候它是否可以被有效的点击并且执行对应的操作,令人失望的是,当你点击的时候onValueChanged已经执行了,就算你对点击的Toggle进行拦截,你也无法拦截ToggleGroup通知组内的其他Toggle改变状态。
比如我有一个单选的列表,这个列表有些是需要金币解锁的,当我们点击列表其中一个的时候我们需要进行解锁并且在解锁成功之后自动选择,这时候我们就需要对其判断金币是否足够解锁,不够的时候我们不进行选择操作,所以我们要在onValueChanged之前对其进行拦截,这就是我对Toggle的拓展的初衷。
我们新建一个脚本ToggleExp,这个脚本主要是添加了CheckSelectEvent事件,对其判断是否是有效的点击。
//
// Copyright (C) XM.All Rights Reserved.
// Author: 吴肖牧(XM)
// Date: 2019-04-10
// Desc: Toggle的拓展
//
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform))]
public class ToggleExp : UIBehaviour, IPointerClickHandler
{
public bool selectable = true;
public CheckSelectEvent onCheckSelect;
public ToggleExpEvent onValueChanged;
[Serializable]
public class CheckSelectEvent : UnityEvent {}
[Serializable]
public class ToggleExpEvent : UnityEvent { }
public Graphic graphic;
[SerializeField]
private bool m_IsOn;
[SerializeField]
private ToggleExpGroup m_Group;
public ToggleExpGroup group
{
get { return m_Group; }
set
{
m_Group = value;
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
SetToggleGroup(m_Group, true);
}
}
}
protected ToggleExp()
{ }
protected override void OnEnable()
{
base.OnEnable();
SetToggleGroup(m_Group, false);
if (graphic)
{
graphic.gameObject.SetActive(m_IsOn);
}
}
protected override void OnDisable()
{
SetToggleGroup(null, false);
base.OnDisable();
}
private void SetToggleGroup(ToggleExpGroup newGroup, bool setMemberValue)
{
ToggleExpGroup oldGroup = m_Group;
if (m_Group != null)
m_Group.UnregisterToggle(this);
if (setMemberValue)
m_Group = newGroup;
if (newGroup != null && IsActive())
newGroup.RegisterToggle(this);
if (newGroup != null && newGroup != oldGroup && isOn && IsActive())
newGroup.NotifyToggleOn(this);
}
public bool isOn
{
get { return m_IsOn; }
set
{
Set(value);
}
}
void Set(bool value)
{
Set(value, true);
}
void Set(bool value, bool sendCallback)
{
if (m_IsOn == value)
return;
onCheckSelect.Invoke();//在这里判断是否选择有效 selectable = value
if (selectable)
{
m_IsOn = value;
if (graphic)
{
graphic.gameObject.SetActive(m_IsOn);
}
if (sendCallback)
{
onValueChanged.Invoke(m_IsOn, selectable);
}
if (m_Group != null && IsActive())
{
if (m_IsOn || (!m_Group.AnyTogglesOn() && !m_Group.allowSwitchOff))
{
isOn = true;
m_Group.NotifyToggleOn(this);
}
}
selectable = true;
}
}
public void OnPointerClick(PointerEventData eventData)
{
Select();
}
public virtual void Select()
{
isOn = !isOn;
}
}
这个脚本基本和原来的ToggleGroup是一样的,主要是为了把原来的Toggle改成我们拓展的ToggleExp。
//
// Copyright (C) XM.All Rights Reserved.
// Author: 吴肖牧(XM)
// Date: 2019-04-10
// Desc: ToggleGroup的改写
//
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine;
[AddComponentMenu("UI/Toggle Group", 32)]
[DisallowMultipleComponent]
public class ToggleExpGroup : UIBehaviour
{
[SerializeField] private bool m_AllowSwitchOff = false;
public bool allowSwitchOff { get { return m_AllowSwitchOff; } set { m_AllowSwitchOff = value; } }
private List m_Toggles = new List();
protected ToggleExpGroup()
{ }
private void ValidateToggleIsInGroup(ToggleExp toggle)
{
if (toggle == null || !m_Toggles.Contains(toggle))
throw new ArgumentException(string.Format("Toggle {0} is not part of ToggleGroup {1}", new object[] { toggle, this }));
}
public void NotifyToggleOn(ToggleExp toggle)
{
ValidateToggleIsInGroup(toggle);
for (var i = 0; i < m_Toggles.Count; i++)
{
if (m_Toggles[i] == toggle)
continue;
m_Toggles[i].isOn = false;
}
}
public void UnregisterToggle(ToggleExp toggle)
{
if (m_Toggles.Contains(toggle))
m_Toggles.Remove(toggle);
}
public void RegisterToggle(ToggleExp toggle)
{
if (!m_Toggles.Contains(toggle))
m_Toggles.Add(toggle);
}
public bool AnyTogglesOn()
{
return m_Toggles.Find(x => x.isOn) != null;
}
public IEnumerable ActiveToggles()
{
return m_Toggles.Where(x => x.isOn);
}
public void SetAllTogglesOff()
{
bool oldAllowSwitchOff = m_AllowSwitchOff;
m_AllowSwitchOff = true;
for (var i = 0; i < m_Toggles.Count; i++)
m_Toggles[i].isOn = false;
m_AllowSwitchOff = oldAllowSwitchOff;
}
}
我们在创建UI组件的地方拓展一下ToggleExp组件,方便创建。
有个小问题就是,我不知道怎么去单独加载Unity自带的资源,我只能遍历所有的自带资源,然后找到自带的那个“Checkmark”图片,有点小坑,希望知道怎么单独加载的朋友告诉我一下。
[MenuItem("GameObject/UI/ToggleExp")]
static void CreatImage()
{
if (Selection.activeTransform)
{
GameObject toggle = new GameObject("ToggleExp", typeof(ToggleExp));
ToggleExp toggleExp = toggle.GetComponent();
GameObject background = new GameObject("Background", typeof(Image));
background.transform.SetParent(toggle.transform);
GameObject mark = new GameObject("Checkmark", typeof(Image));
Sprite sprite = null;
Object[] objs = AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
for (int i = 0; i < objs.Length; i++)
{
if (objs[i].name == "Checkmark" && objs[i].GetType() == typeof(Sprite))
{
//Debug.Log(objs[i]);
sprite = (Sprite)objs[i];
break;
}
}
Image markImage = mark.GetComponent();
markImage.sprite = sprite;
markImage.raycastTarget = false;
toggleExp.graphic = markImage;
mark.transform.SetParent(toggle.transform);
toggle.transform.SetParent(Selection.activeTransform);
toggle.GetComponent().anchoredPosition = Vector2.zero;
}
}
1.创建一个ToggleGroup,然后为其添加脚本ToggleExpGroup。
2.创建ToggleExp组件。
跟创建其他UI组件的方式一样,在ToggleGroup上右键选择UI>ToggleExp,然后把ToggleGroup附加给ToggleExp的Group。
我们可以用同样的操作多创建几个ToggleExp。
3.我们创建一个控制Toggle的脚本,名字随便你取。
重点需要注意的是,如果你需要在onValueChanged之前判断点击操作是否有效,一定要注册onCheckSelect事件,然后在里面判断并且设置selectable的值,这个selectable的值会通过onValueChanged事件传递出去,最后我们就可以根据selectable的值对点击无效的操作进行拦截了。
//
// Copyright (C) XM.All Rights Reserved.
// Author: 吴肖牧(XM)
// Date: 2019-04-10
// Desc:
//
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToggleExpItem : MonoBehaviour {
ToggleExp toggleExp;
// Use this for initialization
void Start () {
toggleExp = GetComponent();
toggleExp.onCheckSelect.AddListener(CheckSelect);
toggleExp.onValueChanged.AddListener(Select);
}
void OnDestroy()
{
toggleExp.onCheckSelect.RemoveListener(CheckSelect);
toggleExp.onValueChanged.RemoveListener(Select);
}
private void CheckSelect()
{
//TODO 对于金币不够或者禁用的toggle进行加锁不让选择
toggleExp.selectable = false;
}
private void Select(bool select, bool selectable)
{
if (!selectable)
{
//对无效操作进行拦截
return;
}
}
}