Merged in dev/fancyscrollviewupdate (pull request #77)

Updated Fancy Scroll View code to the latest from repo
pull/413/head
Simon Jackson 2020-06-30 15:14:24 +00:00
commit 9a66b92de2
20 changed files with 418 additions and 284 deletions

View File

@ -1,16 +1,17 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyScrollView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <see cref="FancyScrollViewCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollViewCell{TItemData}"/> を使用します.
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyCell{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
public abstract class FancyScrollViewCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
public abstract class FancyCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
{
/// <summary>
/// このセルで表示しているデータのインデックス.
@ -29,10 +30,15 @@ namespace UnityEngine.UI.Extensions
protected TContext Context { get; private set; }
/// <summary>
/// <see cref="Context"/> のセットアップを行います.
/// <see cref="Context"/> をセットします.
/// </summary>
/// <param name="context">コンテキスト.</param>
public virtual void SetupContext(TContext context) => Context = context;
public virtual void SetContext(TContext context) => Context = context;
/// <summary>
/// 初期化を行います.
/// </summary>
public virtual void Initialize() { }
/// <summary>
/// このセルの可視状態を設定します.
@ -57,10 +63,10 @@ namespace UnityEngine.UI.Extensions
/// <see cref="FancyScrollView{TItemData}"/> のセルを実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyScrollViewCell{TItemData, TContext}"/>
public abstract class FancyScrollViewCell<TItemData> : FancyScrollViewCell<TItemData, FancyScrollViewNullContext>
/// <seealso cref="FancyCell{TItemData, TContext}"/>
public abstract class FancyCell<TItemData> : FancyCell<TItemData, NullContext>
{
/// <inheritdoc/>
public sealed override void SetupContext(FancyScrollViewNullContext context) => base.SetupContext(context);
public sealed override void SetContext(NullContext context) => base.SetContext(context);
}
}

View File

@ -42,8 +42,7 @@ namespace UnityEngine.UI.Extensions
/// </summary>
[SerializeField] protected Transform cellContainer = default;
readonly IList<FancyScrollViewCell<TItemData, TContext>> pool =
new List<FancyScrollViewCell<TItemData, TContext>>();
readonly IList<FancyCell<TItemData, TContext>> pool = new List<FancyCell<TItemData, TContext>>();
/// <summary>
/// 初期化済みかどうか.
@ -90,7 +89,12 @@ namespace UnityEngine.UI.Extensions
}
/// <summary>
/// セルの表示内容を更新します.
/// セルのレイアウトを強制的に更新します.
/// </summary>
protected virtual void Relayout() => UpdatePosition(currentPosition, false);
/// <summary>
/// セルのレイアウトと表示内容を強制的に更新します.
/// </summary>
protected virtual void Refresh() => UpdatePosition(currentPosition, true);
@ -130,16 +134,16 @@ namespace UnityEngine.UI.Extensions
var addCount = Mathf.CeilToInt((1f - firstPosition) / cellInterval) - pool.Count;
for (var i = 0; i < addCount; i++)
{
var cell = Instantiate(CellPrefab, cellContainer)
.GetComponent<FancyScrollViewCell<TItemData, TContext>>();
var cell = Instantiate(CellPrefab, cellContainer).GetComponent<FancyCell<TItemData, TContext>>();
if (cell == null)
{
throw new MissingComponentException(
$"FancyScrollViewCell<{typeof(TItemData).FullName}, {typeof(TContext).FullName}> " +
$"component not found in {CellPrefab.name}.");
throw new MissingComponentException(string.Format(
"FancyCell<{0}, {1}> component not found in {2}.",
typeof(TItemData).FullName, typeof(TContext).FullName, CellPrefab.name));
}
cell.SetupContext(Context);
cell.SetContext(Context);
cell.Initialize();
cell.SetVisible(false);
pool.Add(cell);
}
@ -200,7 +204,7 @@ namespace UnityEngine.UI.Extensions
/// <summary>
/// <see cref="FancyScrollView{TItemData}"/> のコンテキストクラス.
/// </summary>
public sealed class FancyScrollViewNullContext { }
public sealed class NullContext { }
/// <summary>
/// スクロールビューを実装するための抽象基底クラス.
@ -208,5 +212,5 @@ namespace UnityEngine.UI.Extensions
/// </summary>
/// <typeparam name="TItemData"></typeparam>
/// <seealso cref="FancyScrollView{TItemData, TContext}"/>
public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, FancyScrollViewNullContext> { }
public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, NullContext> { }
}

View File

@ -0,0 +1,72 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
using System.Linq;
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// 複数の <see cref="FancyCell{TItemData, TContext}"/> を持つセルグループ実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyCellGroup<TItemData, TContext> : FancyCell<TItemData[], TContext>
where TContext : class, IFancyCellGroupContext, new()
{
/// <summary>
/// このグループで表示するセルの配列.
/// </summary>
protected virtual FancyCell<TItemData, TContext>[] Cells { get; private set; }
/// <summary>
/// このグループで表示するセルの配列をインスタンス化します.
/// </summary>
/// <returns>このグループで表示するセルの配列.</returns>
protected virtual FancyCell<TItemData, TContext>[] InstantiateCells()
{
return Enumerable.Range(0, Context.GetGroupCount())
.Select(_ => Instantiate(Context.CellTemplate, transform))
.Select(x => x.GetComponent<FancyCell<TItemData, TContext>>())
.ToArray();
}
/// <inheritdoc/>
public override void Initialize()
{
Cells = InstantiateCells();
Debug.Assert(Cells.Length == Context.GetGroupCount());
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].SetContext(Context);
Cells[i].Initialize();
}
}
/// <inheritdoc/>
public override void UpdateContent(TItemData[] contents)
{
var firstCellIndex = Index * Context.GetGroupCount();
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].Index = i + firstCellIndex;
Cells[i].SetVisible(i < contents.Length);
if (Cells[i].IsVisible)
{
Cells[i].UpdateContent(contents[i]);
}
}
}
/// <inheritdoc/>
public override void UpdatePosition(float position)
{
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].UpdatePosition(position);
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3e786463ba934403cacf7e8c0d5822d7
guid: 5d97e25c7748b8d44acd2298e509c8f1
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -11,77 +11,106 @@ namespace UnityEngine.UI.Extensions
/// <summary>
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyGridView{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyGridView<TItemData, TContext> : FancyScrollRect<TItemData[], TContext>
where TContext : class, IFancyScrollRectContext, IFancyGridViewContext, new()
where TContext : class, IFancyGridViewContext, new()
{
/// <summary>
/// カラム同士の余白.
/// デフォルトのセルグループクラス.
/// </summary>
[SerializeField] protected float columnSpacing = 0f;
GameObject cachedRowPrefab;
protected abstract class DefaultCellGroup : FancyCellGroup<TItemData, TContext> { }
/// <summary>
/// 行の Prefab.
/// 最初にセルを配置する軸方向のセル同士の余白.
/// </summary>
[SerializeField] protected float startAxisSpacing = 0f;
/// <summary>
/// 最初にセルを配置する軸方向のセル数.
/// </summary>
[SerializeField] protected int startAxisCellCount = 4;
/// <summary>
/// セルのサイズ.
/// </summary>
[SerializeField] protected Vector2 cellSize = new Vector2(100f, 100f);
/// <summary>
/// セルのグループ Prefab.
/// </summary>
/// <remarks>
/// <see cref="FancyGridView{TItemData, TContext}"/> では,
/// <see cref="FancyScrollView{TItemData, TContext}.CellPrefab"/> を行オブジェクトとして使用します.
/// <see cref="FancyScrollView{TItemData, TContext}.CellPrefab"/> を最初にセルを配置する軸方向のセルコンテナとして使用します.
/// </remarks>
protected sealed override GameObject CellPrefab => cachedRowPrefab ?? (cachedRowPrefab = SetupRowTemplate());
protected sealed override GameObject CellPrefab => cellGroupTemplate;
/// <summary>
/// 一行あたりの要素数.
/// </summary>
protected abstract int ColumnCount { get; }
/// <summary>
/// セルのテンプレート.
/// </summary>
protected abstract FancyScrollViewCell<TItemData, TContext> CellTemplate { get; }
/// <summary>
/// 行オブジェクトのテンプレート.
/// </summary>
protected abstract FancyGridViewRow<TItemData, TContext> RowTemplate { get; }
/// <inheritdoc/>
protected override float CellSize => Scroller.ScrollDirection == ScrollDirection.Horizontal
? cellSize.x
: cellSize.y;
/// <summary>
/// アイテムの総数.
/// </summary>
public int DataCount { get; private set; }
GameObject cellGroupTemplate;
/// <inheritdoc/>
protected override void Initialize()
{
base.Initialize();
Debug.Assert(RowTemplate != null);
Debug.Assert(CellTemplate != null);
Debug.Assert(ColumnCount > 0);
Debug.Assert(startAxisCellCount > 0);
Context.CellTemplate = CellTemplate.gameObject;
Context.ScrollDirection = Scroller.ScrollDirection;
Context.GetColumnCount = () => ColumnCount;
Context.GetColumnSpacing = () => columnSpacing;
Context.GetGroupCount = () => startAxisCellCount;
Context.GetStartAxisSpacing = () => startAxisSpacing;
Context.GetCellSize = () => Scroller.ScrollDirection == ScrollDirection.Horizontal
? cellSize.y
: cellSize.x;
SetupCellTemplate();
}
/// <summary>
/// 行オブジェクトのセットアップを行います.
/// 最初にセルが生成される直前に呼び出されます.
/// <see cref="Setup{TGroup}(FancyCell{TItemData, TContext})"/> メソッドを使用してセルテンプレートのセットアップを行ってください.
/// </summary>
/// <returns>行を構成する <c>GameObject</c>.</returns>
protected virtual GameObject SetupRowTemplate()
/// <example>
/// <code><![CDATA[
/// using UnityEngine;
/// using FancyScrollView;
///
/// public class MyGridView : FancyGridView<ItemData, Context>
/// {
/// class CellGroup : DefaultCellGroup { }
///
/// [SerializeField] Cell cellPrefab = default;
///
/// protected override void SetupCellTemplate() => Setup<CellGroup>(cellPrefab);
/// }
/// ]]></code>
/// </example>
protected abstract void SetupCellTemplate();
/// <summary>
/// セルテンプレートのセットアップを行います.
/// </summary>
/// <param name="cellTemplate">セルのテンプレート.</param>
/// <typeparam name="TGroup">セルグループの型.</typeparam>
protected virtual void Setup<TGroup>(FancyCell<TItemData, TContext> cellTemplate)
where TGroup : FancyCell<TItemData[], TContext>
{
var cell = CellTemplate.GetComponent<RectTransform>();
var row = RowTemplate.GetComponent<RectTransform>();
Context.CellTemplate = cellTemplate.gameObject;
row.sizeDelta = Scroller.ScrollDirection == ScrollDirection.Horizontal
? new Vector2(cell.rect.width, row.sizeDelta.y)
: new Vector2(row.sizeDelta.x, cell.rect.height);
return row.gameObject;
cellGroupTemplate = new GameObject("Group").AddComponent<TGroup>().gameObject;
cellGroupTemplate.transform.SetParent(cellContainer, false);
cellGroupTemplate.SetActive(false);
}
/// <summary>
@ -92,15 +121,26 @@ namespace UnityEngine.UI.Extensions
{
DataCount = items.Count;
var rows = items
var itemGroups = items
.Select((item, index) => (item, index))
.GroupBy(
x => x.index / ColumnCount,
x => x.index / startAxisCellCount,
x => x.item)
.Select(group => group.ToArray())
.ToArray();
UpdateContents(rows);
UpdateContents(itemGroups);
}
/// <summary>
/// 指定したアイテムの位置までジャンプします.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
protected override void JumpTo(int itemIndex, float alignment = 0.5f)
{
var groupIndex = itemIndex / startAxisCellCount;
base.JumpTo(groupIndex, alignment);
}
/// <summary>
@ -108,12 +148,12 @@ namespace UnityEngine.UI.Extensions
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public override void ScrollTo(int itemIndex, float duration, Alignment alignment = Alignment.Center, Action onComplete = null)
protected override void ScrollTo(int itemIndex, float duration, float alignment = 0.5f, Action onComplete = null)
{
var rowIndex = itemIndex / Context.GetColumnCount();
base.ScrollTo(rowIndex, duration, alignment, onComplete);
var groupIndex = itemIndex / startAxisCellCount;
base.ScrollTo(groupIndex, duration, alignment, onComplete);
}
/// <summary>
@ -122,23 +162,20 @@ namespace UnityEngine.UI.Extensions
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public override void ScrollTo(int itemIndex, float duration, Ease easing, Alignment alignment = Alignment.Center, Action onComplete = null)
protected override void ScrollTo(int itemIndex, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
{
var rowIndex = itemIndex / Context.GetColumnCount();
base.ScrollTo(rowIndex, duration, easing, alignment, onComplete);
var groupIndex = itemIndex / startAxisCellCount;
base.ScrollTo(groupIndex, duration, easing, alignment, onComplete);
}
}
/// <summary>
/// 指定したアイテムの位置までジャンプします.
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
public virtual void JumpTo(int itemIndex, Alignment alignment = Alignment.Center)
{
var rowIndex = itemIndex / Context.GetColumnCount();
UpdatePosition(rowIndex, alignment);
}
}
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyGridView{TItemData, TContext}"/>
public abstract class FancyGridView<TItemData> : FancyGridView<TItemData, FancyGridViewContext> { }
}

View File

@ -0,0 +1,42 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyGridViewCell{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyGridViewCell<TItemData, TContext> : FancyScrollRectCell<TItemData, TContext>
where TContext : class, IFancyGridViewContext, new()
{
/// <inheritdoc/>
protected override void UpdatePosition(float normalizedPosition, float localPosition)
{
var cellSize = Context.GetCellSize();
var spacing = Context.GetStartAxisSpacing();
var groupCount = Context.GetGroupCount();
var indexInGroup = Index % groupCount;
var positionInGroup = (cellSize + spacing) * (indexInGroup - (groupCount - 1) * 0.5f);
transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
? new Vector2(-localPosition, -positionInGroup)
: new Vector2(positionInGroup, localPosition);
}
}
/// <summary>
/// <see cref="FancyGridView{TItemData}"/> のセルを実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyGridViewCell{TItemData, TContext}"/>
public abstract class FancyGridViewCell<TItemData> : FancyGridViewCell<TItemData, FancyGridViewContext>
{
/// <inheritdoc/>
public sealed override void SetContext(FancyGridViewContext context) => base.SetContext(context);
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 84300901ad8704c11b39587ed6d87468
guid: ab8a59bbf5118824ab084e32342ad86b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -8,16 +8,13 @@ namespace UnityEngine.UI.Extensions
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキスト基底クラス.
/// </summary>
public class FancyGridViewContext : IFancyGridViewContext, IFancyScrollRectContext
public class FancyGridViewContext : IFancyGridViewContext
{
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
GameObject IFancyGridViewContext.CellTemplate { get; set; }
ScrollDirection IFancyGridViewContext.ScrollDirection { get; set; }
public Func<int> GetColumnCount { get; set; }
public Func<float> GetColumnSpacing { get; set; }
GameObject IFancyCellGroupContext.CellTemplate { get; set; }
Func<int> IFancyCellGroupContext.GetGroupCount { get; set; }
Func<float> IFancyGridViewContext.GetStartAxisSpacing { get; set; }
Func<float> IFancyGridViewContext.GetCellSize { get; set; }
}
}

View File

@ -1,81 +0,0 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
using System.Linq;
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> の行を実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyScrollViewCell{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyGridViewRow<TItemData, TContext> : FancyScrollRectCell<TItemData[], TContext>
where TContext : class, IFancyScrollRectContext, IFancyGridViewContext, new()
{
/// <summary>
/// この行で表示するセルの配列.
/// </summary>
protected virtual FancyScrollViewCell<TItemData, TContext>[] Cells { get; private set; }
/// <summary>
/// この行で表示するセルの配列をインスタンス化します.
/// </summary>
/// <returns>この行で表示するセルの配列.</returns>
protected virtual FancyScrollViewCell<TItemData, TContext>[] InstantiateCells()
{
return Enumerable.Range(0, Context.GetColumnCount())
.Select(_ => Instantiate(Context.CellTemplate, transform))
.Select(x => x.GetComponent<FancyScrollViewCell<TItemData, TContext>>())
.ToArray();
}
/// <inheritdoc/>
public override void SetupContext(TContext context)
{
base.SetupContext(context);
Cells = InstantiateCells();
Debug.Assert(Cells.Length == Context.GetColumnCount());
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].SetupContext(context);
}
}
/// <inheritdoc/>
public override void UpdateContent(TItemData[] rowContents)
{
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].Index = i + Index * Context.GetColumnCount();
Cells[i].SetVisible(i < rowContents.Length);
if (Cells[i].IsVisible)
{
Cells[i].UpdateContent(rowContents[i]);
}
}
}
/// <inheritdoc/>
public override void UpdatePosition(float position)
{
base.UpdatePosition(position);
for (var i = 0; i < Cells.Length; i++)
{
Cells[i].UpdatePosition(position);
}
}
/// <inheritdoc/>
protected override void UpdatePosition(float position, float viewportPosition)
{
transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
? new Vector2(viewportPosition, transform.localPosition.y)
: new Vector2(transform.localPosition.x, viewportPosition);
}
}
}

View File

@ -0,0 +1,16 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
using System;
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyCellGroup{TItemData, TContext}"/> のコンテキストインターフェース.
/// </summary>
public interface IFancyCellGroupContext
{
GameObject CellTemplate { get; set; }
Func<int> GetGroupCount { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1dc086f250206754aa8449e252d50388
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -8,11 +8,9 @@ namespace UnityEngine.UI.Extensions
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキストインターフェース.
/// </summary>
public interface IFancyGridViewContext
public interface IFancyGridViewContext : IFancyScrollRectContext, IFancyCellGroupContext
{
GameObject CellTemplate { get; set; }
ScrollDirection ScrollDirection { get; set; }
Func<int> GetColumnCount { get; set; }
Func<float> GetColumnSpacing { get; set; }
Func<float> GetStartAxisSpacing { get; set; }
Func<float> GetCellSize { get; set ; }
}
}

View File

@ -1,12 +0,0 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView
namespace UnityEngine.UI.Extensions
{
public enum Alignment
{
Head,
Center,
Tail,
}
}

View File

@ -39,10 +39,15 @@ namespace UnityEngine.UI.Extensions
[SerializeField] protected float paddingTail = 0f;
/// <summary>
/// セル同士の余白.
/// スクロール軸方向のセル同士の余白.
/// </summary>
[SerializeField] protected float spacing = 0f;
/// <summary>
/// セルのサイズ.
/// </summary>
protected abstract float CellSize { get; }
/// <summary>
/// スクロール可能かどうか.
/// </summary>
@ -51,13 +56,6 @@ namespace UnityEngine.UI.Extensions
/// </remarks>
protected virtual bool Scrollable => MaxScrollPosition > 0f;
/// <summary>
/// セルのサイズ.
/// </summary>
protected virtual float CellSize => Scroller.ScrollDirection == ScrollDirection.Horizontal
? CellRectTransform.rect.width
: CellRectTransform.rect.height;
Scroller cachedScroller;
/// <summary>
@ -68,9 +66,6 @@ namespace UnityEngine.UI.Extensions
/// </remarks>
protected Scroller Scroller => cachedScroller ?? (cachedScroller = GetComponent<Scroller>());
RectTransform cachedCellRect;
RectTransform CellRectTransform => cachedCellRect ?? (cachedCellRect = CellPrefab.transform as RectTransform);
float ScrollLength => 1f / Mathf.Max(cellInterval, 1e-2f) - 1f;
float ViewportLength => ScrollLength - reuseCellMarginCount * 2f;
@ -87,6 +82,7 @@ namespace UnityEngine.UI.Extensions
{
base.Initialize();
Context.ScrollDirection = Scroller.ScrollDirection;
Context.CalculateScrollSize = () =>
{
var interval = CellSize + spacing;
@ -138,6 +134,14 @@ namespace UnityEngine.UI.Extensions
base.Refresh();
}
/// <inheritdoc/>
protected override void Relayout()
{
AdjustCellIntervalAndScrollOffset();
RefreshScroller();
base.Relayout();
}
/// <summary>
/// <see cref="Scroller"/> の各種状態を更新します.
/// </summary>
@ -172,17 +176,17 @@ namespace UnityEngine.UI.Extensions
/// <param name="position">スクロール位置.</param>
protected new void UpdatePosition(float position)
{
UpdatePosition(position, Alignment.Center);
Scroller.Position = ToScrollerPosition(position, 0.5f);
}
/// <summary>
/// スクロール位置を更新します.
/// 指定したアイテムの位置までジャンプします.
/// </summary>
/// <param name="position">スクロール位置.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
protected virtual void UpdatePosition(float position, Alignment alignment)
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
protected virtual void JumpTo(int itemIndex, float alignment = 0.5f)
{
Scroller.Position = ToScrollerPosition(position, alignment);
Scroller.Position = ToScrollerPosition(itemIndex, alignment);
}
/// <summary>
@ -190,9 +194,9 @@ namespace UnityEngine.UI.Extensions
/// </summary>
/// <param name="index">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public virtual void ScrollTo(int index, float duration, Alignment alignment = Alignment.Center, Action onComplete = null)
protected virtual void ScrollTo(int index, float duration, float alignment = 0.5f, Action onComplete = null)
{
Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, onComplete);
}
@ -203,9 +207,9 @@ namespace UnityEngine.UI.Extensions
/// <param name="index">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public virtual void ScrollTo(int index, float duration, Ease easing, Alignment alignment = Alignment.Center, Action onComplete = null)
protected virtual void ScrollTo(int index, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
{
Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, easing, onComplete);
}
@ -244,25 +248,15 @@ namespace UnityEngine.UI.Extensions
/// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
/// </summary>
/// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
protected float ToScrollerPosition(float position, Alignment alignment = Alignment.Center)
protected float ToScrollerPosition(float position, float alignment = 0.5f)
{
var offset = (ScrollLength - (1f + reuseCellMarginCount * 2f)) * GetAnchore(alignment);
var offset = alignment * (ScrollLength - (1f + reuseCellMarginCount * 2f))
+ (1f - alignment - 0.5f) * spacing / (CellSize + spacing);
return ToScrollerPosition(Mathf.Clamp(position - offset, 0f, MaxScrollPosition));
}
float GetAnchore(Alignment alignment)
{
switch (alignment)
{
case Alignment.Head: return 0.0f;
case Alignment.Center: return 0.5f;
case Alignment.Tail: return 1.0f;
default: return GetAnchore(Alignment.Center);
}
}
/// <summary>
/// 指定された設定を実現するための
/// <see cref="FancyScrollView{TItemData,TContext}.cellInterval"/> と
@ -276,11 +270,8 @@ namespace UnityEngine.UI.Extensions
}
protected virtual void OnValidate()
{
if (CellPrefab)
{
AdjustCellIntervalAndScrollOffset();
}
if (loop)
{

View File

@ -5,12 +5,12 @@ namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <see cref="FancyScrollViewCell{TItemData, TContext}.Context"/> が不要な場合は
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollRectCell{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyScrollViewCell{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyScrollRectCell<TItemData, TContext> : FancyScrollViewCell<TItemData, TContext>
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
public abstract class FancyScrollRectCell<TItemData, TContext> : FancyCell<TItemData, TContext>
where TContext : class, IFancyScrollRectContext, new()
{
/// <inheritdoc/>
@ -18,24 +18,29 @@ namespace UnityEngine.UI.Extensions
{
var (scrollSize, reuseMargin) = Context.CalculateScrollSize();
var unclampedPosition = (Mathf.Lerp(0f, scrollSize, position) - reuseMargin) / (scrollSize - reuseMargin * 2f);
var normalizedPosition = (Mathf.Lerp(0f, scrollSize, position) - reuseMargin) / (scrollSize - reuseMargin * 2f);
var start = 0.5f * scrollSize;
var end = -start;
UpdatePosition(unclampedPosition, Mathf.Lerp(start, end, position));
UpdatePosition(normalizedPosition, Mathf.Lerp(start, end, position));
}
/// <summary>
/// このセルの位置を更新します.
/// </summary>
/// <param name="position">
/// <param name="normalizedPosition">
/// ビューポートの範囲で正規化されたスクロール位置.
/// <see cref="FancyScrollRect{TItemData, TContext}.reuseCellMarginCount"/> の値に基づいて
/// <c>0.0</c> ~ <c>1.0</c> の範囲を超えた値が渡されることがあります.
/// </param>
/// <param name="viewportPosition">ローカル位置.</param>
protected virtual void UpdatePosition(float position, float viewportPosition) { }
/// <param name="localPosition">ローカル位置.</param>
protected virtual void UpdatePosition(float normalizedPosition, float localPosition)
{
transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
? new Vector2(-localPosition, 0)
: new Vector2(0, localPosition);
}
}
/// <summary>
@ -46,6 +51,6 @@ namespace UnityEngine.UI.Extensions
public abstract class FancyScrollRectCell<TItemData> : FancyScrollRectCell<TItemData, FancyScrollRectContext>
{
/// <inheritdoc/>
public sealed override void SetupContext(FancyScrollRectContext context) => base.SetupContext(context);
public sealed override void SetContext(FancyScrollRectContext context) => base.SetContext(context);
}
}

View File

@ -10,6 +10,7 @@ namespace UnityEngine.UI.Extensions
/// </summary>
public class FancyScrollRectContext : IFancyScrollRectContext
{
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
}
}

View File

@ -10,6 +10,7 @@ namespace UnityEngine.UI.Extensions
/// </summary>
public interface IFancyScrollRectContext
{
ScrollDirection ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> CalculateScrollSize { get; set; }
}
}

View File

@ -1,30 +1,8 @@
//
// EasingCore - https://github.com/setchi/EasingCore
//
// The MIT License (MIT)
//
// Copyright (c) 2019 setchi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using UnityEngine;
/*
* EasingCore (https://github.com/setchi/EasingCore)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/EasingCore/blob/master/LICENSE)
*/
namespace UnityEngine.UI.Extensions.EasingCore
{
@ -63,14 +41,16 @@ namespace UnityEngine.UI.Extensions.EasingCore
InOutSine,
}
public static class EasingFunction
public delegate float EasingFunction(float t);
public static class Easing
{
/// <summary>
/// Gets the easing function
/// </summary>
/// <param name="type">Ease type</param>
/// <returns>Easing function</returns>
public static Func<float, float> Get(Ease type)
public static EasingFunction Get(Ease type)
{
switch (type)
{

View File

@ -10,7 +10,7 @@ namespace UnityEngine.UI.Extensions
/// <summary>
/// スクロール位置の制御を行うコンポーネント.
/// </summary>
public class Scroller : UIBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
public class Scroller : UIBehaviour, IPointerUpHandler, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
[SerializeField] RectTransform viewport = default;
@ -83,9 +83,7 @@ namespace UnityEngine.UI.Extensions
set => decelerationRate = value;
}
[SerializeField]
Snap snap = new Snap
{
[SerializeField] Snap snap = new Snap {
Enable = true,
VelocityThreshold = 0.5f,
Duration = 0.3f,
@ -151,6 +149,8 @@ namespace UnityEngine.UI.Extensions
int totalCount;
bool hold;
bool scrolling;
bool dragging;
float velocity;
@ -163,14 +163,14 @@ namespace UnityEngine.UI.Extensions
public Ease Easing;
}
static readonly Func<float, float> DefaultEasingFunction = EasingFunction.Get(Ease.OutCubic);
static readonly EasingFunction DefaultEasingFunction = Easing.Get(Ease.OutCubic);
class AutoScrollState
{
public bool Enable;
public bool Elastic;
public float Duration;
public Func<float, float> EasingFunction;
public EasingFunction EasingFunction;
public float StartTime;
public float EndPosition;
@ -240,7 +240,7 @@ namespace UnityEngine.UI.Extensions
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public void ScrollTo(float position, float duration, Ease easing, Action onComplete = null) => ScrollTo(position, duration, EasingFunction.Get(easing), onComplete);
public void ScrollTo(float position, float duration, Ease easing, Action onComplete = null) => ScrollTo(position, duration, Easing.Get(easing), onComplete);
/// <summary>
/// 指定した位置まで移動します.
@ -249,7 +249,7 @@ namespace UnityEngine.UI.Extensions
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easingFunction">移動に使用するイージング関数.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public void ScrollTo(float position, float duration, Func<float, float> easingFunction, Action onComplete = null)
public void ScrollTo(float position, float duration, EasingFunction easingFunction, Action onComplete = null)
{
if (duration <= 0f)
{
@ -283,13 +283,8 @@ namespace UnityEngine.UI.Extensions
throw new ArgumentOutOfRangeException(nameof(index));
}
autoScrollState.Reset();
velocity = 0f;
dragging = false;
UpdateSelection(index);
UpdatePosition(index);
Position = index;
}
/// <summary>
@ -311,6 +306,75 @@ namespace UnityEngine.UI.Extensions
: MovementDirection.Down;
}
/// <inheritdoc/>
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
{
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
{
return;
}
hold = true;
velocity = 0f;
autoScrollState.Reset();
}
/// <inheritdoc/>
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
{
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
{
return;
}
if (hold && snap.Enable)
{
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(currentPosition), 0, totalCount - 1));
ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
}
hold = false;
}
/// <inheritdoc/>
void IScrollHandler.OnScroll(PointerEventData eventData)
{
if (!draggable)
{
return;
}
var delta = eventData.scrollDelta;
// Down is positive for scroll events, while in UI system up is positive.
delta.y *= -1;
var scrollDelta = scrollDirection == ScrollDirection.Horizontal
? Mathf.Abs(delta.y) > Mathf.Abs(delta.x)
? delta.y
: delta.x
: Mathf.Abs(delta.x) > Mathf.Abs(delta.y)
? delta.x
: delta.y;
if (eventData.IsScrolling())
{
scrolling = true;
}
var position = currentPosition + scrollDelta / ViewportSize * scrollSensitivity;
if (movementType == MovementType.Clamped)
{
position += CalculateOffset(position);
}
if (autoScrollState.Enable)
{
autoScrollState.Reset();
}
UpdatePosition(position);
}
/// <inheritdoc/>
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
{
@ -319,6 +383,7 @@ namespace UnityEngine.UI.Extensions
return;
}
hold = false;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
viewport,
eventData.position,
@ -449,7 +514,7 @@ namespace UnityEngine.UI.Extensions
UpdatePosition(position);
}
else if (!dragging && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
else if (!(dragging || scrolling) && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
{
var position = currentPosition;
@ -500,13 +565,14 @@ namespace UnityEngine.UI.Extensions
}
}
if (!autoScrollState.Enable && dragging && inertia)
if (!autoScrollState.Enable && (dragging || scrolling) && inertia)
{
var newVelocity = (currentPosition - prevPosition) / deltaTime;
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
}
prevPosition = currentPosition;
scrolling = false;
}
float CalculateMovementAmount(float sourcePosition, float destPosition)