2021.1.25:加入按钮置灰功能

各位看官们~只需要引入两个脚本即可简便的监听各种按钮事件噢!!使用说明都在注释里咯
UIButtonExtension:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.Serialization;
#endif

/// <summary>
/// 按钮拓展,可用于单击,双击,长按(单次),长按(间隔),按下,抬起,进入,移出事件,创建透明按钮(删去了网格不会触发dc,只调透明度的普通按钮会触发dc)。
/// 使用方法:场景中右键-UI-ButtonEx可创建普通拓展按钮,包含image以及text组件;场景中右键-UI-ButtonEx_Transparent创建透明按钮,不包含image和text。
/// 代码调用:使用简便,获取UIbuttonExtension脚本之后直接.onClick(需要的事件).AddListener()即可。
/// Write By Alin
/// </summary>
public class UIButtonExtension : UnityEngine.UI.Selectable,
    IPointerClickHandler,
    ISubmitHandler,
    IPointerDownHandler,
    IPointerUpHandler,
    IPointerEnterHandler,
    IPointerExitHandler {
    /// <summary>
    /// 长按等待时间
    /// </summary>
    public float onLongWaitTime = 1.5f;
    /// <summary>
    /// 长按触发后特定等待时间触发长按,此参数用于间隔时间触发事件
    /// </summary>
    public float onLongUpdateTime = 1.5f;
    /// <summary>
    /// 不断的产生长按事件,这个是间隔长按等待时间触发长按事件,此参数用于将单次长按事件变成间隔时间事件,但不可调时间
    /// </summary>
    public bool onLongContinue = false;
    /// <summary>
    /// 双击有效时间,在此时间内双击触发双击事件
    /// </summary>
    public float onDoubleClickTime = 0.5f;
     /// <summary>
    /// 是否需要置灰
    /// </summary>
    public bool isNeedGray = false;
    /// <summary>
    /// 置灰按钮是否需要点击事件
    /// </summary>
    public bool isGrayNeedClick = true;
    /// <summary>
    /// 是否在长按中
    /// </summary>
    private bool isLongClick;
    [System.Serializable]
    public class ButtonClickedEvent : UnityEvent { }

#if UNITY_EDITOR
    [FormerlySerializedAs("onClick")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();//点击事件
#if UNITY_EDITOR
    [FormerlySerializedAs("onLongClick")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnLongClick = new ButtonClickedEvent();//长按事件(触发一次)
#if UNITY_EDITOR
    [FormerlySerializedAs("onDown")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnDown = new ButtonClickedEvent();//按下事件
#if UNITY_EDITOR
    [FormerlySerializedAs("onUp")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnUp = new ButtonClickedEvent();//抬起事件
#if UNITY_EDITOR
    [FormerlySerializedAs("onEnter")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnEnter = new ButtonClickedEvent();//进入事件
#if UNITY_EDITOR
    [FormerlySerializedAs("onExit")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnExit = new ButtonClickedEvent();//移出事件
#if UNITY_EDITOR
    [FormerlySerializedAs("onLongClickUpdate")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_OnLongClickUpdate = new ButtonClickedEvent();//长按事件(间隔时间触发)
#if UNITY_EDITOR
    [FormerlySerializedAs("onDoubleClick")]
#endif
    [SerializeField]
    private ButtonClickedEvent m_onDoubleClick = new ButtonClickedEvent();//双击事件

    private Coroutine log;
    protected UIButtonExtension() : base() { }

    /// <summary>
    /// 文本值
    /// </summary>
    public string text
    {
        get
        {
            Text v = getText();
            if (v != null)
                return v.text;
            return null;
        }
        set
        {
            Text v = getText();
            if (v != null)
                v.text = value;
        }
    }

    private bool isPointerDown = false;
    private bool isPointerInside = false;

    /// <summary>
    /// 是否被按下
    /// </summary>
    public bool isDown
    {
        get
        {
            return isPointerDown;
        }
    }

    /// <summary>
    /// 是否进入
    /// </summary>
    public bool isEnter
    {
        get
        {
            return isPointerInside;
        }
    }

    /// <summary>
    /// 点击事件
    /// </summary>
    public ButtonClickedEvent onClick
    {
        get { return m_OnClick; }
        set { m_OnClick = value; }
    }

    /// <summary>
    /// 长按事件
    /// </summary>
    public ButtonClickedEvent onLongClick
    {
        get { return m_OnLongClick; }
        set { m_OnLongClick = value; }
    }

    /// <summary>
    /// 长按事件
    /// </summary>
    public ButtonClickedEvent onLongClickUpdate
    {
        get { return m_OnLongClickUpdate; }
        set { m_OnLongClickUpdate = value; }
    }
    /// <summary>
    /// 双击事件
    /// </summary>
    public ButtonClickedEvent onDoubleClick
    {
        get { return m_onDoubleClick; }
        set { m_onDoubleClick = value; }
    }
    /// <summary>
    /// 按下事件
    /// </summary>
    public ButtonClickedEvent onDown
    {
        get { return m_OnDown; }
        set { m_OnDown = value; }
    }

    /// <summary>
    /// 松开事件
    /// </summary>
    public ButtonClickedEvent onUp
    {
        get { return m_OnUp; }
        set { m_OnUp = value; }
    }

    /// <summary>
    /// 进入事件
    /// </summary>
    public ButtonClickedEvent onEnter
    {
        get { return m_OnEnter; }
        set { m_OnEnter = value; }
    }

    /// <summary>
    /// 离开事件
    /// </summary>
    public ButtonClickedEvent onExit
    {
        get { return m_OnExit; }
        set { m_OnExit = value; }
    }
    private UnityEngine.UI.Text getText()
    {
        Transform f = transform.Find("Text");
        Text v = null;
        if (f != null)
        {
            v = f.gameObject.GetComponent<Text>();
        }
        if (v == null && transform.childCount > 0)
        {
            GameObject obj = transform.GetChild(0).gameObject;
            v = obj.GetComponent<Text>();
        }
        return v;
    }

    private void Press()
    {
        if (!IsActive() || !IsInteractable())
            return;
        m_OnClick.Invoke();
    }

    private float lastClickTime;
    private void Down()
    {
        if (!IsActive() || !IsInteractable())
            return;
        m_OnDown.Invoke();
        if(lastClickTime != 0&&Time.time - lastClickTime < onDoubleClickTime)
        {
            DoubleClick();
        }
        lastClickTime = Time.time;
        log = StartCoroutine(grow());
    }

    private void Up()
    {
        if (!IsActive() || !IsInteractable() || !isDown)
            return;
        m_OnUp.Invoke();
        if (log != null)
        {
            StopCoroutine(log);
            log = null;
        }
    }

    private void Enter()
    {
        if (!IsActive())
            return;
        m_OnEnter.Invoke();
    }

    private void Exit()
    {
        if (!IsActive() || !isEnter)
            return;
        m_OnExit.Invoke();
    }

    private void LongClick()
    {
        if (!IsActive() || !isDown)
            return;
        m_OnLongClick.Invoke();
    }
    private void DoubleClick()
    {
        if (!IsActive() || !isDown)
            return;
        m_onDoubleClick.Invoke();
    }
    private void LongClickUpdate()
    {
        if (!IsActive() || !isDown)
            return;
        m_OnLongClickUpdate.Invoke();
    }

    private float downTime = 0f;
    
    private IEnumerator grow()
    {
        downTime = Time.time;
        while (isDown)
        {
            if (Time.time - downTime > onLongWaitTime)
            {
                LongClick();
                isLongClick = true;
                if (onLongContinue)
                    downTime = Time.time;
                else
                    break;
            }
            else
                yield return null;
        }
        WaitForSeconds times = new WaitForSeconds(onLongUpdateTime);
        while (isLongClick)
        {
            if (!isDown)
            {
                isLongClick = false;
                break;
            }
            LongClickUpdate();
            yield return times;
        }
    }

    protected override void OnDisable()
    {
        isPointerDown = false;
        isPointerInside = false;
    }

    public virtual void OnPointerClick(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        Press();
    }

    public override void OnPointerDown(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        isPointerDown = true;
        Down();
        base.OnPointerDown(eventData);
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        Up();
        isPointerDown = false;
        base.OnPointerUp(eventData);
    }

    public override void OnPointerEnter(PointerEventData eventData)
    {
        base.OnPointerEnter(eventData);
        isPointerInside = true;
        Enter();
    }

    public override void OnPointerExit(PointerEventData eventData)
    {
        Exit();
        isPointerInside = false;
        base.OnPointerExit(eventData);
    }

    public virtual void OnSubmit(BaseEventData eventData)
    {
        Press();
        if (!IsActive() || !IsInteractable())
            return;

        DoStateTransition(SelectionState.Pressed, false);
        StartCoroutine(OnFinishSubmit());
    }

    private IEnumerator OnFinishSubmit()
    {
        var fadeTime = colors.fadeDuration;
        var elapsedTime = 0f;

        while (elapsedTime < fadeTime)
        {
            elapsedTime += Time.unscaledDeltaTime;
            yield return null;
        }
        DoStateTransition(currentSelectionState, false);
    }
    public static GameObject CreateUIElementRoot(string name, Vector2 size)
    {
        GameObject go = new GameObject(name);
        RectTransform rect = go.AddComponent<RectTransform>();
        rect.sizeDelta = size;
        return go;
    }
    public static GameObject CreateUIElementRoot(string name, float w, float h)
    {
        return CreateUIElementRoot(name, new Vector2(w, h));
    }

    public static GameObject CreateUIText(string name, string text, GameObject parent)
    {
        GameObject childText = CreateUIObject(name, parent);
        Text v = childText.AddComponent<Text>();
        v.text = text;
        v.alignment = TextAnchor.MiddleCenter;
        SetDefaultTextValues(v);

        RectTransform r = childText.GetComponent<RectTransform>();
        r.anchorMin = Vector2.zero;
        r.anchorMax = Vector2.one;
        r.sizeDelta = Vector2.zero;

        return childText;
    }

    public static GameObject CreateUIObject(string name, GameObject parent)
    {
        GameObject go = new GameObject(name);
        go.AddComponent<RectTransform>();
        SetParentAndAlign(go, parent);
        return go;
    }
    //设置默认值
    public static void SetDefaultTextValues(Text lbl)
    {
        lbl.color = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f);
    }
    //设置父物体
    public static void SetParentAndAlign(GameObject child, GameObject parent)
    {
        if (parent == null)
            return;

        child.transform.SetParent(parent.transform, false);
        SetLayerRecursively(child, parent.layer);
    }
    //设置层级
    public static void SetLayerRecursively(GameObject go, int layer)
    {
        go.layer = layer;
        Transform t = go.transform;
        for (int i = 0; i < t.childCount; i++)
            SetLayerRecursively(t.GetChild(i).gameObject, layer);
    }
    //找文件赋值默认材质
    public static T findRes<T>(string name) where T : Object
    {
        T[] objs = Resources.FindObjectsOfTypeAll<T>();
        if (objs != null && objs.Length > 0)
        {
            foreach (Object obj in objs)
            {
                if (obj.name == name)
                    return obj as T;
            }
        }
        objs = AssetBundle.FindObjectsOfType<T>();
        if (objs != null && objs.Length > 0)
        {
            foreach (Object obj in objs)
            {
                if (obj.name == name)
                    return obj as T;
            }
        }
        return default(T);
    }
#if UNITY_EDITOR
    public static void PlaceUIElementRoot(GameObject element, MenuCommand menuCommand)
    {
        GameObject parent = menuCommand.context as GameObject;
        if (parent == null || parent.GetComponentInParent<Canvas>() == null)
        {
            parent = GetOrCreateCanvasGameObject();
        }

        string uniqueName = GameObjectUtility.GetUniqueNameForSibling(parent.transform, element.name);
        element.name = uniqueName;
        Undo.RegisterCreatedObjectUndo(element, "Create " + element.name);
        Undo.SetTransformParent(element.transform, parent.transform, "Parent " + element.name);
        GameObjectUtility.SetParentAndAlign(element, parent);
        if (parent != menuCommand.context)
            SetPositionVisibleinSceneView(parent.GetComponent<RectTransform>(), element.GetComponent<RectTransform>());

        Selection.activeGameObject = element;
    }
#endif
    //拿画布
    public static GameObject GetOrCreateCanvasGameObject()
    {
#if UNITY_EDITOR
        GameObject selectedGo = Selection.activeGameObject;


        Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null;
        if (canvas != null && canvas.gameObject.activeInHierarchy)
            return canvas.gameObject;


        canvas = Object.FindObjectOfType(typeof(Canvas)) as Canvas;
        if (canvas != null && canvas.gameObject.activeInHierarchy)
            return canvas.gameObject;


        return CreateNewUI();
#else
			return null;
#endif
    }

#if UNITY_EDITOR
    //设置位置在屏幕可见区域中
    private static void SetPositionVisibleinSceneView(RectTransform canvasRTransform, RectTransform itemTransform)
    {

        SceneView sceneView = SceneView.lastActiveSceneView;
        if (sceneView == null && SceneView.sceneViews.Count > 0)
            sceneView = SceneView.sceneViews[0] as SceneView;


        if (sceneView == null || sceneView.camera == null)
            return;


        Vector2 localPlanePosition;
        Camera camera = sceneView.camera;
        Vector3 position = Vector3.zero;
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRTransform, new Vector2(camera.pixelWidth / 2, camera.pixelHeight / 2), camera, out localPlanePosition))
        {
            // Adjust for canvas pivot
            localPlanePosition.x = localPlanePosition.x + canvasRTransform.sizeDelta.x * canvasRTransform.pivot.x;
            localPlanePosition.y = localPlanePosition.y + canvasRTransform.sizeDelta.y * canvasRTransform.pivot.y;

            localPlanePosition.x = Mathf.Clamp(localPlanePosition.x, 0, canvasRTransform.sizeDelta.x);
            localPlanePosition.y = Mathf.Clamp(localPlanePosition.y, 0, canvasRTransform.sizeDelta.y);

            // Adjust for anchoring
            position.x = localPlanePosition.x - canvasRTransform.sizeDelta.x * itemTransform.anchorMin.x;
            position.y = localPlanePosition.y - canvasRTransform.sizeDelta.y * itemTransform.anchorMin.y;

            Vector3 minLocalPosition;
            minLocalPosition.x = canvasRTransform.sizeDelta.x * (0 - canvasRTransform.pivot.x) + itemTransform.sizeDelta.x * itemTransform.pivot.x;
            minLocalPosition.y = canvasRTransform.sizeDelta.y * (0 - canvasRTransform.pivot.y) + itemTransform.sizeDelta.y * itemTransform.pivot.y;

            Vector3 maxLocalPosition;
            maxLocalPosition.x = canvasRTransform.sizeDelta.x * (1 - canvasRTransform.pivot.x) - itemTransform.sizeDelta.x * itemTransform.pivot.x;
            maxLocalPosition.y = canvasRTransform.sizeDelta.y * (1 - canvasRTransform.pivot.y) - itemTransform.sizeDelta.y * itemTransform.pivot.y;

            position.x = Mathf.Clamp(position.x, minLocalPosition.x, maxLocalPosition.x);
            position.y = Mathf.Clamp(position.y, minLocalPosition.y, maxLocalPosition.y);
        }

        itemTransform.anchoredPosition = position;
        itemTransform.localRotation = Quaternion.identity;
        itemTransform.localScale = Vector3.one;
    }
#endif
    //如果没有画布就创建一个
    public static GameObject CreateNewUI()
    {
        var root = new GameObject("Canvas");
        root.layer = LayerMask.NameToLayer("UI");
        Canvas canvas = root.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        root.AddComponent<CanvasScaler>();
        root.AddComponent<GraphicRaycaster>();
#if UNITY_EDITOR
        Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);
#endif
        CreateEventSystem(false, null);
        return root;
    }
    //如果没有eventsystem就创建一个
    public static void CreateEventSystem(bool select, GameObject parent)
    {
#if UNITY_EDITOR
        var esys = Object.FindObjectOfType<EventSystem>();
        if (esys == null)
        {
            var eventSystem = new GameObject("EventSystem");
            GameObjectUtility.SetParentAndAlign(eventSystem, parent);
            esys = eventSystem.AddComponent<EventSystem>();
            eventSystem.AddComponent<StandaloneInputModule>();

            Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name);
        }

        if (select && esys != null)
        {
            Selection.activeGameObject = esys.gameObject;
        }
#endif
    }


#if UNITY_EDITOR
    [MenuItem("GameObject/UI/ButtonEx")]
    static void CreateButtonEx(MenuCommand menuCmd)
    {
        // 创建游戏对象
        float w = 160f;
        float h = 30f;
        GameObject btnRoot = CreateUIElementRoot("ButtonEx", w, h);

        // 创建Text对象
        CreateUIText("Text", "Button", btnRoot);

        // 添加脚本
        btnRoot.AddComponent<CanvasRenderer>();
        Image img = btnRoot.AddComponent<Image>();
        img.color = Color.white;
        img.fillCenter = true;
        img.raycastTarget = true;
        img.sprite = findRes<Sprite>("UISprite");
        if (img.sprite != null)
            img.type = Image.Type.Sliced;

        btnRoot.AddComponent<UIButtonExtension>();
        btnRoot.GetComponent<Selectable>().image = img;

        // 放入到UI Canvas中
        PlaceUIElementRoot(btnRoot, menuCmd);
    }
#endif

#if UNITY_EDITOR
    [MenuItem("GameObject/UI/ButtonEx_Transparent")]
    static void CreateButtonEx_Transparent(MenuCommand menuCmd)
    {
        // 创建游戏对象
        float w = 160f;
        float h = 30f;
        GameObject btnRoot = CreateUIElementRoot("ButtonEx", w, h);
        // 添加脚本
        btnRoot.AddComponent<CanvasRenderer>();
        btnRoot.AddComponent<UIButtonExtension>();
        UIButtonEx_transparent temp = btnRoot.AddComponent<UIButtonEx_transparent>();
        btnRoot.GetComponent<UIButtonExtension>().targetGraphic = temp;
        // 放入到UI Canvas中
        PlaceUIElementRoot(btnRoot, menuCmd);
    }
#endif
///置灰功能
#if UNITY_EDITOR
    protected override void OnValidate()
    {        
            if (isNeedGray == true && image.material.shader == Shader.Find("UI/Default"))
        {
            Material material = new Material(Shader.Find("Alintool/ToGray"));
            image.material = material;
        }
        if (isNeedGray == false && image.material.shader == Shader.Find("Alintool/ToGray"))
        {
            image.material = new Material(Shader.Find("UI/Default"));
        }
        if (isNeedGray && !isGrayNeedClick && interactable)
        {
            interactable = false;
        }
        if (isNeedGray && isGrayNeedClick && !interactable)
        {
            interactable = true;
        }
        if (!isNeedGray && isGrayNeedClick)
        {
            isGrayNeedClick = false;
            interactable = true;
        }      
    }
#endif
}

UIButtonEx_transparent:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIButtonEx_transparent : MaskableGraphic {
    protected UIButtonEx_transparent()
    {
        useLegacyMeshGeneration = false;
    }

    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        toFill.Clear();
    }
}

置灰功能需要加上材质和shader代码:
材质:新建一个默认的材质球。
置灰shader:

Shader "Alintool/ToGray"
{
    Properties
    {
        [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
    }
 
        SubShader
    {
        Tags
    {
        "Queue" = "Transparent"
        "IgnoreProjector" = "True"
        "RenderType" = "Transparent"
        "PreviewType" = "Plane"
        "CanUseSpriteAtlas" = "True"
    }
        // 源rgba*源a + 背景rgba*(1-源A值)   
        Blend SrcAlpha OneMinusSrcAlpha
 
        Pass
    {
        CGPROGRAM
#pragma vertex vert     
#pragma fragment frag     
#include "UnityCG.cginc"     
 
        struct appdata_t
    {
        float4 vertex   : POSITION;
        float4 color    : COLOR;
        float2 texcoord : TEXCOORD0;
    };
 
    struct v2f
    {
        float4 vertex   : SV_POSITION;
        fixed4 color : COLOR;
        half2 texcoord  : TEXCOORD0;
    };
 
    sampler2D _MainTex;
 
    v2f vert(appdata_t IN)
    {
        v2f OUT;
        OUT.vertex = UnityObjectToClipPos(IN.vertex);
        OUT.texcoord = IN.texcoord;
        OUT.color = IN.color;
        return OUT;
    }
 
    fixed4 frag(v2f IN) : SV_Target
    {
        half4 color = tex2D(_MainTex, IN.texcoord) * IN.color;
        float grey = dot(color.rgb, fixed3(0.22, 0.707, 0.071));
        return half4(grey,grey,grey,color.a);
    }
        ENDCG
    }
    }

    
}