触ると表示され放置すると消えるスクロールバー

概要

アプリによくあるような、普段は見えないが、スクロール領域を動かしたらフェードインで表示され、スクロールが終わるとフェードアウトで消えていくようなスクロールバーを作った。
ステートパターンで非表示/フェードイン/表示/フェードアウトを切り替えている。
ステートが変わるたびにオブジェクトが生成されるのを避けるため、各ステートは初回で生成してしまっている。
フェード処理、遅延処理にはDoTweenを使用している。 

パラメータ

  • AutoFadeScrollbar.handle:表示・非表示となる操作用ハンドル画像。Sliding Areaは最初から非表示にしておく
  • FadeInState.fadeDuration:フェードインにかかる時間
  • ShowState.showDuration:表示し続ける時間
  • FadeOutState.fadeDuration:フェードアウトにかかる時間

コード

以下を作成しScrollbarコンポーネントとともにアタッチする。
#region以下はステートの実装部。

using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;


/// <summary>
/// 動かしたときに表示され、自動で消えるスクロールバー
/// </summary>
[RequireComponent(typeof(Scrollbar))]
public class AutoFadeScrollbar : MonoBehaviour
{
    [SerializeField] private Scrollbar scrollbar;
    [SerializeField] private Image handle;

    /// <summary>
    /// 初期処理
    /// </summary>
    private void Awake()
    {
        var context = new Context(handle);
        scrollbar.onValueChanged.AddListener(_ => context.OnScroll());
    }
    
    
    #region ハンドルコンテキストとステートのクラス定義
    private enum HandleState
    {
        Hide,
        FadeIn,
        Show,
        FadeOut
    }
    
    /// <summary>
    /// コンテキストクラス
    /// </summary>
    private class Context
    {
        /// <summary>
        /// ハンドル画像
        /// </summary>
        public readonly Image Image;
        
        private IHandleState currentState;

        /// <summary>
        /// ステートリスト
        /// 毎回newしたくないので状態は保持しておく
        /// </summary>
        private readonly IHandleState[] stateList = {
            new HideState(),
            new FadeInState(), 
            new ShowState(), 
            new FadeOutState()
        };

        /// <summary>
        /// コンストラクタ
        /// ハンドル画像の取得と初期Stateのセット
        /// </summary>
        /// <param name="image"></param>
        public Context(Image image)
        {
            Image = image;
            ChangeState(HandleState.Hide);
        }

        /// <summary>
        /// ハンドル状態の更新
        /// </summary>
        /// <param name="newState"></param>
        public void ChangeState(HandleState newState)
        {
            currentState = stateList[(int)newState];
            currentState.Initialize(this);
        }

        /// <summary>
        /// 動かしたとき各ステートの実行
        /// </summary>
        public void OnScroll()
        {
            currentState.OnScroll(this);
        }
    }

    
    /// <summary>
    /// ハンドルの状態を表すインターフェース
    /// </summary>
    private interface IHandleState
    {
        /// <summary>
        /// ステート遷移時の初期化
        /// </summary>
        /// <param name="context"></param>
        void Initialize(Context context);

        /// <summary>
        /// 動かしたとき
        /// </summary>
        /// <param name="context"></param>
        void OnScroll(Context context);
    }

    /// <summary>
    /// 非表示状態
    /// </summary>
    private class HideState : IHandleState
    {
        private readonly Color clearColor;

        /// <summary>
        /// コンストラクタ
        /// 透明色のキャッシュ
        /// </summary>
        public HideState()
        {
            clearColor = Color.white;
            clearColor.a = 0;
        }
        
        /// <summary>
        /// 完全に非表示化
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(Context context)
        {
            context.Image.color = clearColor;
        }
        
        /// <summary>
        /// 表示中に切り替え
        /// </summary>
        /// <param name="context"></param>
        public void OnScroll(Context context)
        {
            context.ChangeState(HandleState.FadeIn);
        }
    }

    /// <summary>
    /// フェード表示中
    /// </summary>
    private class FadeInState : IHandleState
    {
        // フェード時間
        private float fadeDuration = 0.2f;

        /// <summary>
        /// フェード表示の実行
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(Context context)
        {
            DOTween.ToAlpha(() => context.Image.color, color => context.Image.color = color, 1, fadeDuration)
                .OnComplete(() => context.ChangeState(HandleState.Show));
        }

        public void OnScroll(Context context)
        {
        }
    }

    /// <summary>
    /// 表示中
    /// </summary>
    private class ShowState : IHandleState
    {
        // 表示し続ける時間
        private float showDuration = 3f;

        private Tween _tween;
        
        private readonly Color clearColor;

        /// <summary>
        /// コンストラクタ
        /// 表示色のキャッシュ
        /// </summary>
        public ShowState()
        {
            clearColor = Color.white;
        }

        /// <summary>
        /// 表示後、徐々にフェードアウトさせる
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(Context context)
        {
            context.Image.color = clearColor;
            _tween = DOVirtual.DelayedCall(showDuration, () => context.ChangeState(HandleState.FadeOut));
        }

        /// <summary>
        /// スクロールされると表示時間をリセット
        /// </summary>
        /// <param name="context"></param>
        public void OnScroll(Context context)
        {
            _tween.Restart();
        }
    }

    /// <summary>
    /// フェードアウト中
    /// </summary>
    private class FadeOutState : IHandleState
    {
        private float fadeDuration = 0.4f;

        /// <summary>
        /// フェードアウトの実行
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(Context context)
        {
            DOTween.ToAlpha(() => context.Image.color, color => context.Image.color = color, 0, fadeDuration)
                .OnComplete(() => context.ChangeState(HandleState.Hide));
        }

        /// <summary>
        /// スクロールすると再表示
        /// </summary>
        /// <param name="context"></param>
        public void OnScroll(Context context)
        {
            context.ChangeState(HandleState.FadeIn);
        }
    }
    
    #endregion
}