ScrollRectの要素にあるボタンのタッチ反応改善について

uGUIの[ScrollRect](https://docs.unity3d.com/ja/current/Manual/script-ScrollRect.html)の要素にボタンを置いた上でスマホ上で実行すると、ボタンのタッチ反応が悪く感じられる件について調べてみた。
その原因及び改善策の1つをメモ。
※具体的な症状としては、ボタンを押下してもOnPointerClickイベントが呼ばれない時があったりする。

Unity5.5.1p3 + Android端末で検証

# 原因

先ず最初に各種OnPointer*イベントがどのようなタイミングでコールされているのかを調べるために、
下記のコードを実装してScrollRectの要素として設置してあるGameObjectにアタッチ。

```csharp

public class ButtonEventTest : MonoBehaviour,
    IPointerUpHandler,
    IPointerDownHandler,
    IPointerClickHandler
{
    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("OnPointerUp");
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.Log("OnPointerDown");
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("OnPointerClick");
    }
}
```

この状態でアタッチしたGameObjectを押下すると、正しく挙動すれば「OnPointerDown → OnPointerUp → OnPointerClick」の順でイベントが呼ばれるが、押している最中に少しでもリストがドラッグされてしまうと「OnPointerDown → OnPointerUp」と言った順でイベントが呼ばれてしまい、指を離していなくてもOnPointerUpが呼ばれてしまう上に離してもOnPointerClickイベントが呼ばれないと言う事が分かった。

→こちら、[BitbucketにあるuGUIのソース](https://bitbucket.org/Unity-Technologies/ui)を見てみた所、該当するイベントを呼び出していると思われる箇所に"Before doing drag we should cancel any pointer down state"、"And clear selection!"とのコメントが見受けられるのでドラッグ判定が取られたら意図的にOnPointerUpを呼び出している模様。

※その上でスマホの実機上でこの問題が発生する理由としては、ドラッグ判定時の閾値である[EventSystem.pixelDragThreshold](https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventSystem-pixelDragThreshold.html)のデフォルト値が5と低めの値で設定されており、タップ時に指の面積の影響で複数判定を取られてしまい、結果的に間の距離が閾値を超えてしまってドラッグ判定が取られていると言う点が考えられる。(指の腹辺りで押下すると顕著)

--------------------------------------------------------------------------------

# 解決策

解決策の1つとして、スワイプ時にドラッグ判定されるまでの距離にある程度の閾値を持たせる方法がある。
これの設定自体は至って簡単でHierarchy上にあるEventSystemの「EventSystem→Drag Threshold」の値を増やすだけで良い。
→試しに25~30ぐらいの間で設定してみるとドラッグ開始判定までに若干の間はある物の、ボタン自体はかなり押し易くなっている印象を受けた

![sample1.png](https://qiita-image-store.s3.amazonaws.com/0/80207/2f16b78e-8b83-7bbd-97e6-3aac5e6bf663.png)


Script上から設定する際には、[EventSystem.pixelDragThreshold](https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventSystem-pixelDragThreshold.html)から設定が可能。

```csharp
// デフォルト値は5
EventSystem.current.pixelDragThreshold = 30;
```




--------------------------------------------------------------------

## ※補足. pixelDragThresholdの影響範囲の制御について

pixelDragThresholdに設定した閾値については少なくともEventSystemが存在するシーン中全てのScrollRect(と言うよりはドラッグ制御が有効な物)に影響が及んでしまう模様。

これの制御方法としては[「IInitializePotentialDragHandler.OnInitializePotentialDrag」](https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.IInitializePotentialDragHandler.OnInitializePotentialDrag.html)の中で[「PointerEventData」](https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.PointerEventData.html)が持つメンバ変数[「useDragThreshold」](https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.PointerEventData-useDragThreshold.html)にfalseを設定する方法がある。


例えば下記の様にScene中に二つのScrollRectが存在する場面があったとする。

![sample2.png](https://qiita-image-store.s3.amazonaws.com/0/80207/54cdb931-4256-5b5b-b449-b032527ec741.png)

この中で「2のScrollRect」のみ閾値の制御を解除したい場合には後述するScrollRectを継承した独自のScrollRectを実装し、こちらを「2のScrollRect」側にアタッチし直す事で制御を外すことが出来る。




```csharp
using UnityEngine.UI;
using UnityEngine.EventSystems;
#if UNITY_EDITOR
using UnityEditor;
#endif

// 閾値の制御を受けないScrollRect
public class TestScrollRect : ScrollRect,
    IInitializePotentialDragHandler
{
    public override void OnInitializePotentialDrag(PointerEventData eventData)
    {
        // 閾値を設定したくない場合はfalse設定
        eventData.useDragThreshold = false;
    }
}

#if UNITY_EDITOR
[CustomEditor(typeof(TestScrollRect))]
public class TestScrollRectEditor : Editor
{
    // Inspector表示用
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
    }
}
#endif
```

※なお、[uGUIのソース](https://bitbucket.org/Unity-Technologies/ui)を参照した所、[Slider](https://docs.unity3d.com/ja/current/Manual/script-Slider.html)と[Scrollbar](https://docs.unity3d.com/ja/current/Manual/script-Scrollbar.html)についてはデフォルトでeventData.useDragThresholdがfalse設定になっている模様。