Unity低版本Button点击边缘抽搐的问题

Selectable的状态切换

状态类型

  Selectable一共有Normal、Highlighted、Pressed、Disabled四个状态。
  新版本加入了Selected状态,老版本的Unity其实也对Selected状态进行了处理,当该Selectable是全局Selected对象时,将状态设置为Highlighted。

状态切换的触发时机

  1. OnEnable。初始化按钮状态。
  2. 设置interactable时。维护m_Interactable变量,记录当前是否可交互。
  3. OnPointerDown、OnPointerUp。维护isPointerDown变量,记录当前是否是按下状态。
  4. OnPointerEnter、OnPointerExit。维护isPointerInside变量,记录当前鼠标是否在该对象区域。

抽搐的原因

  老版本对Pressed状态的判定是要同时满足isPointerDown和isPointerInside时才时Pressed状态。当点击边缘时,如果有一个缩小的动画,会导致触发OnPointerExit,使isPointerInside为false,导致Pressed判定失败、Highlighted判定也失败、所以状态变为了Normal。这个时候按钮又变回了原来的大小,又会触发OnPointerEnter,使isPointerInside为true。Pressed判定成功。在这两个状态间不停循环,造成抽搐的表现。

判定代码

老版本(2018.3.0f2)

protected bool IsPressed()
{
    if (!IsActive())
        return false;

    return isPointerInside && isPointerDown
}

新版本(2018.4.3f1)

  移除了对Pressed状态的单独判定,整合在了currentSelectionState里,且移除了isPointerInside的限制。

protected SelectionState currentSelectionState
{
    get
    {
        if (!IsInteractable())
            return SelectionState.Disabled;
        if (isPointerDown)
            return SelectionState.Pressed;
        if (hasSelection)
            return SelectionState.Selected;
        if (isPointerInside)
            return SelectionState.Normal;
    }
}

Tips

  没看具体是哪个版本修复的,就贴了两个我看到有改变的版本。

修复方案

1.升级Unity版本

  最简单的方案。

2.如果不方便升级的话,重写Button

代码

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

//对状态切换的修复
//也可以加入一些长按逻辑、点击音效等功能,成为一个更完善的按钮类
public class BetterButton : Button
{
    private bool isPointerDown = false;

    public override void OnPointerDown(PointerEventData eventData)
    {
        isPointerDown = true;
        base.OnPointerDown(eventData);
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        isPointerDown = false;
        base.OnPointerUp(eventData);
        GameObject currentOverGo = eventData.pointerCurrentRaycast.gameObject;
        //如果pointerUp时所指向的press对象和按下时不一样,则需要主动调一下PointerExit去纠正isPointerInside
        GameObject curPress = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
        if (eventData.pointerPress != curPress)
        {
            OnPointerExit(eventData);
        }
    }

    public override void OnPointerExit(PointerEventData eventData)
    {
        if (isPointerDown == false)
        {
            base.OnPointerExit(eventData);
        }
    }
}

为什么这么改

  因为看过代码后我们可以确定在老版本中,OnPointerExit只负责isPointerInside和按钮状态的维护,没有其他逻辑。所以我们通过修改这部分代码区控制它的调用是一种比较安全的行为。
  我们强制让PointerExit在Selectable按下时不生效,在Selectable抬起时再尝试调用OnPointerExit纠正状态即可。

其他

  如果升版本的话要及时把这些Trick代码删掉,因为我们在没有看新版的代码之前,不能确定是否会产生其他意料外的结果。

上一篇:UGUI Button扩展


下一篇:Unity EventSystem 详解(Unity Version 5.5.1)不是原创