今までアプリで何度となく作ってきたページ単位でスクロールするいわゆるスナップスクロールというもの。
少しずつ改善を重ね、ようやく良い感じのものが出来たのでAsset化しました☆
試しにiPhoneのホーム画面を作ってみた(笑)
SnapScrollとは
一定の表示領域(ページ)単位でピタっと止まるスクロールをするUI。
uGUIにはScrollViewはあるけど、スナップスクロールの機能はないので、独自に実装する必要がある。uGUIのScrollViewを実現させているScrollRectを拡張して作っているので、ScrollViewと同じ使用感で使えます♪
動作環境
Unity2017.1.0+
iOS 7.0+
Android 4.1+
使用方法
通常の手順でScrollViewを追加する。
ScrollRectを削除してSnapScrollViewを追加する。ContentやViewport等、外れた参照は再度設定しておく。
インスペクターオプション(▼三)からDebugを選択し、スナップスクロールに関するプロパティを編集。もしくはスクリプトからプロパティを編集してもOK。
※本当はDebugモードにせずにプロパティを編集したかったが、どうやらScrollRect自体がエディタ拡張を行ってるようで、直接プロパティを表示できなかった
Demo.cs
using UnityEngine;
using UnityEngine.UI;
using SnapScroll;
public class Demo : MonoBehaviour
{
[SerializeField] SnapScrollView scrollView;
[SerializeField] Image[] indicators;
void Start()
{
scrollView.MaxPage = 1;
scrollView.PageSize = 1080;
scrollView.OnPageChanged += OnIndicatorUpdate;
scrollView.RefreshPage();
}
void OnIndicatorUpdate()
{
for(var i = 0; i < indicators.Length; i++)
{
var a = (i == scrollView.Page) ? 1 : 0.5f;
indicators[i].color = new Color(1, 1, 1, a);
}
}
}
パラメーター | 説明 |
---|---|
Page | 現在ページ(0〜) |
MaxPage | 最大ページ(0〜) |
PageSize | ページサイズ |
ScrollableDistance | スクロール可能な判定距離(フリックのしやすさ) |
Tween | アンカーポジション移動Tween。基本、編集の必要はないが、Tweenの設定をいじれば動き方を変えることも出来る。 |
OnPageChanged | ページ変化イベント。このイベントでインジケーターの更新等を行う。 |
RefreshPage(bool isPlayAnimation = true) | ページリフレッシュメソッド。isPlayAnimationをfalseにすればアニメーションを再生せずにポジション移動が可能(初期ページが0以外で初期化する時とか)。 |
プログラム
SnapScrollView.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
namespace SnapScroll
{
/// <summary>
/// スナップスクロールビュー
/// </summary>
[AddComponentMenu("UI/SnapScrollView", 100)]
public class SnapScrollView : ScrollRect
{
/// <summary>
/// 現在ページ(0〜)
/// </summary>
[SerializeField] int page;
/// <summary>
/// 最大ページ(0〜)
/// </summary>
[SerializeField] int maxPage;
/// <summary>
/// ページサイズ
/// </summary>
[SerializeField] float pageSize;
/// <summary>
/// スクロール可能な判定距離(フリックのしやすさ)
/// </summary>
[SerializeField] float scrollableDistance = 2;
/// <summary>
/// アンカーポジション移動Tween
/// </summary>
[SerializeField] TweenAnchorPosition tween;
/// <summary>
/// 現在のドラッグポジション
/// </summary>
Vector3 dragPos;
/// <summary>
/// 前回のドラッグポジション
/// </summary>
Vector3 prevDragPos;
/// <summary>
/// 現在ページ(0〜)
/// </summary>
public int Page { get { return page; } set { page = value; } }
/// <summary>
/// 最大ページ(0〜)
/// </summary>
public int MaxPage { get { return maxPage; } set { maxPage = value; } }
/// <summary>
/// ページサイズ
/// </summary>
public float PageSize { get { return pageSize; } set { pageSize = value; } }
/// <summary>
/// スクロール可能な判定距離(フリックのしやすさ)
/// </summary>
public float ScrollableDistance { get { return scrollableDistance; } set { scrollableDistance = value; } }
/// <summary>
/// アンカーポジション移動Tween
/// </summary>
public TweenAnchorPosition Tween { get { return tween; } }
/// <summary>
/// ページ変化イベント
/// </summary>
public event Action OnPageChanged;
void Update()
{
if(tween.IsRunning) tween.Update();
}
public override void OnBeginDrag(PointerEventData eventData)
{
base.OnBeginDrag(eventData);
tween.Stop();
dragPos = content.position;
prevDragPos = Vector3.zero;
}
public override void OnDrag(PointerEventData eventData)
{
base.OnDrag(eventData);
prevDragPos = dragPos;
dragPos = content.position;
}
public override void OnEndDrag(PointerEventData eventData)
{
base.OnEndDrag(eventData);
StopMovement();
// 「ページ内移動量」「最終フレームドラッグ量(フリック量)」でスクロール可能か判定
var pageDx = -pageSize * page - content.localPosition.x;
var dragDx = prevDragPos.x - content.position.x;
var pageDistance = Mathf.Abs(pageDx);
var dragDistance = Math.Abs(dragDx);
var isScrollable = false;
var isRight = false;
if(pageDistance >= pageSize / 2)
{
// ページ半分以上動かしていたら強制的に次ページへ
isScrollable = true;
isRight = (pageDx >= 0);
}
else if(dragDistance >= scrollableDistance)
{
// スクロール可能な距離以上動かしていたら次ページへ
isScrollable = true;
isRight = (dragDx >= 0);
}
if(isScrollable)
{
// 最大・最小ページを超えないようにページ更新
if((isRight && page < maxPage) || (!isRight && page >= 1))
{
page += isRight ? 1 : -1;
}
}
RefreshPage();
}
/// <summary>
/// ページリフレッシュ
/// </summary>
/// <param name="isPlayAnimation">アニメーションを再生させるか</param>
public void RefreshPage(bool isPlayAnimation = true)
{
var movePos = content.anchoredPosition;
movePos.x = -pageSize * page;
if(isPlayAnimation)
{
tween.Run(content, movePos);
}
else
{
content.anchoredPosition = movePos;
}
if(OnPageChanged != null) OnPageChanged();
}
}
}
ScrollRectのドラッグイベントでドラッグポジションを取って、ドラッグ終了時に最終フレームドラッグ量(フリック量)でスクロール可能か判定してる。
何気にTweenがお手製w(外部公開するに当たって他のAssetを含めたくなかったので自作した)
最後に
結構こういうちょっとしたUIやツールとか作るの好きなんですよね〜☆以前、まんまRPGツクールのようなエディタ作ったこともあったり♪
UnityとC#でのモノづくりはホント楽しいなぁ。
https://github.com/okamura0510/SnapScroll
〜オセロを作りながらゲームのプログラムを学ぼう〜