Unity スクロール領域の端が表示されているかを検出する


概要

今回はUnityのScrollRectにおいて、スクロール領域の4辺いずれかが画面に表示されているかを実装しました。

分かりやすくするため以下の例では、端が表示されているかどうかを検出し、対応する辺を黄色くしています。画面の上や左端に達したとき黄色く表示されているのが確認できると思います。

プロジェクト全体はこちらから
https://github.com/Arihide/scroll-edge-detection

説明

今回作成したソースコードは以下の通りです。

using UnityEngine;
using UnityEngine.UI;

public class EdgeDetect : MonoBehaviour
{
    [SerializeField] private ScrollRect scroll = null;
    [SerializeField] private Image top = null;
    [SerializeField] private Image bottom = null;
    [SerializeField] private Image left = null;
    [SerializeField] private Image right = null;

    private void Update()
    {
        Bounds contentBound = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.viewport, scroll.content);
        Rect viewportRect = scroll.viewport.rect;

        top.enabled = viewportRect.max.y >= contentBound.max.y; // 上までスクロールされているか?
        bottom.enabled = viewportRect.min.y <= contentBound.min.y;
        left.enabled = viewportRect.min.x <= contentBound.min.x;
        right.enabled = viewportRect.max.x >= contentBound.max.x;
    }
}

一番重要な箇所は
RectTransformUtility.CalculateRelativeRectTransformBounds
で、viewportからみたcontentの領域を計算している点ですね。
あとは、viewportとcontentの最大値や最小値を比較してあげれば良いです。

もしかすると、Scrollbar.valueを使えば良いんじゃないかと思われる方もいらっしゃると思います。
ただこの場合だと、スクロール全体の領域が表示領域より小さい場合は4辺すべてが表示されることになりますが、それを検出することができないので、このような方法を用いています。

基本的なロジックは以上の通りなのですが、このままの条件で使用すると、しばしば端にあるのに正しく判定されないことがあります。その場合は以下のように小さい値を加えて条件を緩くすると良いです。

using UnityEngine;
using UnityEngine.UI;

public class EdgeDetect : MonoBehaviour
{
    [SerializeField] private ScrollRect scroll = null;
    [SerializeField] private Image top = null;
    [SerializeField] private Image bottom = null;
    [SerializeField] private Image left = null;
    [SerializeField] private Image right = null;

    private const float eps = 0.1f;

    private void Update()
    {
        Bounds contentBound = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.viewport, scroll.content);
        Rect viewportRect = scroll.viewport.rect;

        top.enabled = viewportRect.max.y >= contentBound.max.y - eps;
        bottom.enabled = viewportRect.min.y <= contentBound.min.y + eps;
        left.enabled = viewportRect.min.x <= contentBound.min.x + eps;
        right.enabled = viewportRect.max.x >= contentBound.max.x - eps;
    }
}

スワイプによる画像切り替えなどを実装するときに役に立つと思います。