Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Commit

Permalink
Updated Fancy ScrollView and added Example 8
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonDarksideJ committed Dec 4, 2019
1 parent c41ad9a commit 0a5444b
Show file tree
Hide file tree
Showing 17 changed files with 760 additions and 90 deletions.
2 changes: 1 addition & 1 deletion Examples
Submodule Examples updated from a5914a to 4878f8
92 changes: 84 additions & 8 deletions Scripts/Layout/FancyScrollView/Core/FancyScrollView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,109 @@

namespace UnityEngine.UI.Extensions
{
/// <summary>
/// スクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップに対応しています.
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollView{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
public abstract class FancyScrollView<TItemData, TContext> : MonoBehaviour where TContext : class, new()
{
/// <summary>
/// セル同士の間隔.
/// </summary>
[SerializeField, Range(1e-2f, 1f)] protected float cellInterval = 0.2f;

/// <summary>
/// スクロール位置の基準.
/// </summary>
/// <remarks>
/// たとえば、 <c>0.5</c> を指定してスクロール位置が <c>0</c> の場合, 中央に最初のセルが配置されます.
/// </remarks>
[SerializeField, Range(0f, 1f)] protected float scrollOffset = 0.5f;

/// <summary>
/// セルを循環して配置させるどうか.
/// </summary>
/// <remarks>
/// <c>true</c> にすると最後のセルの後に最初のセル, 最初のセルの前に最後のセルが並ぶようになります.
/// 無限スクロールを実装する場合は <c>true</c> を指定します.
/// </remarks>
[SerializeField] protected bool loop = false;

/// <summary>
/// セルの親要素となる <c>Transform</c>.
/// </summary>
[SerializeField] protected Transform cellContainer = default;

readonly IList<FancyScrollViewCell<TItemData, TContext>> pool =
new List<FancyScrollViewCell<TItemData, TContext>>();

/// <summary>
/// 初期化済みかどうか.
/// </summary>
protected bool initialized;

/// <summary>
/// 現在のスクロール位置.
/// </summary>
protected float currentPosition;

/// <summary>
/// セルの Prefab.
/// </summary>
protected abstract GameObject CellPrefab { get; }

/// <summary>
/// アイテム一覧のデータ.
/// </summary>
protected IList<TItemData> ItemsSource { get; set; } = new List<TItemData>();

/// <summary>
/// <typeparamref name="TContext"/> のインスタンス.
/// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
/// </summary>
protected TContext Context { get; } = new TContext();

/// <summary>
/// Updates the contents.
/// 初期化を行います.
/// </summary>
/// <remarks>
/// 最初にセルが生成される直前に呼び出されます.
/// </remarks>
protected virtual void Initialize() { }

/// <summary>
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
/// </summary>
/// <param name="itemsSource">Items source.</param>
/// <param name="itemsSource">アイテム一覧.</param>
protected virtual void UpdateContents(IList<TItemData> itemsSource)
{
ItemsSource = itemsSource;
Refresh();
}

/// <summary>
/// Refreshes the cells.
/// セルの表示内容を更新します.
/// </summary>
protected virtual void Refresh() => UpdatePosition(currentPosition, true);

/// <summary>
/// Updates the scroll position.
/// スクロール位置を更新します.
/// </summary>
/// <param name="position">Position.</param>
/// <param name="position">スクロール位置.</param>
protected virtual void UpdatePosition(float position) => UpdatePosition(position, false);

protected void UpdatePosition(float position, bool forceRefresh)
void UpdatePosition(float position, bool forceRefresh)
{
if (!initialized)
{
Initialize();
initialized = true;
}

currentPosition = position;

var p = position - scrollOffset / cellInterval;
Expand All @@ -66,7 +130,8 @@ void ResizePool(float firstPosition)
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<FancyScrollViewCell<TItemData, TContext>>();
if (cell == null)
{
throw new MissingComponentException(
Expand Down Expand Up @@ -118,7 +183,9 @@ void UpdateCells(float firstPosition, int firstIndex, bool forceRefresh)

void LateUpdate()
{
if (cachedLoop != loop || cachedCellInterval != cellInterval || cachedScrollOffset != scrollOffset)
if (cachedLoop != loop ||
cachedCellInterval != cellInterval ||
cachedScrollOffset != scrollOffset)
{
cachedLoop = loop;
cachedCellInterval = cellInterval;
Expand All @@ -130,7 +197,16 @@ void LateUpdate()
#endif
}

/// <summary>
/// <see cref="FancyScrollView{TItemData}"/> のコンテキストクラス.
/// </summary>
public sealed class FancyScrollViewNullContext { }

/// <summary>
/// スクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップに対応しています.
/// </summary>
/// <typeparam name="TItemData"></typeparam>
/// <seealso cref="FancyScrollView{TItemData, TContext}"/>
public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, FancyScrollViewNullContext> { }
}
39 changes: 25 additions & 14 deletions Scripts/Layout/FancyScrollView/Core/FancyScrollViewCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,64 @@

namespace UnityEngine.UI.Extensions
{
/// <summary>
/// <see cref="FancyScrollView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <see cref="FancyScrollViewCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollViewCell{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
public abstract class FancyScrollViewCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
{
/// <summary>
/// Gets or sets the index of the data.
/// このセルで表示しているデータのインデックス.
/// </summary>
/// <value>The index of the data.</value>
public int Index { get; set; } = -1;

/// <summary>
/// Gets a value indicating whether this <see cref="T:FancyScrollView.FancyScrollViewCell`2"/> is visible.
/// このセルの可視状態.
/// </summary>
/// <value><c>true</c> if is visible; otherwise, <c>false</c>.</value>
public virtual bool IsVisible => gameObject.activeSelf;

/// <summary>
/// Gets the context.
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> の参照.
/// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
/// </summary>
/// <value>The context.</value>
protected TContext Context { get; private set; }

/// <summary>
/// Setup the context.
/// <see cref="Context"/> のセットアップを行います.
/// </summary>
/// <param name="context">Context.</param>
/// <param name="context">コンテキスト.</param>
public virtual void SetupContext(TContext context) => Context = context;

/// <summary>
/// Sets the visible.
/// このセルの可視状態を設定します.
/// </summary>
/// <param name="visible">If set to <c>true</c> visible.</param>
/// <param name="visible">可視状態なら <c>true</c>, 非可視状態なら <c>false</c>.</param>
public virtual void SetVisible(bool visible) => gameObject.SetActive(visible);

/// <summary>
/// Updates the content.
/// アイテムデータに基づいてこのセルの表示内容を更新します.
/// </summary>
/// <param name="itemData">Item data.</param>
/// <param name="itemData">アイテムデータ.</param>
public abstract void UpdateContent(TItemData itemData);

/// <summary>
/// Updates the position.
/// <c>0.0f</c> ~ <c>1.0f</c> の値に基づいてこのセルのスクロール位置を更新します.
/// </summary>
/// <param name="position">Position.</param>
/// <param name="position">ビューポート範囲の正規化されたスクロール位置.</param>
public abstract void UpdatePosition(float position);
}

/// <summary>
/// <see cref="FancyScrollView{TItemData}"/> のセルを実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyScrollViewCell{TItemData, TContext}"/>
public abstract class FancyScrollViewCell<TItemData> : FancyScrollViewCell<TItemData, FancyScrollViewNullContext>
{
/// <inheritdoc/>
public sealed override void SetupContext(FancyScrollViewNullContext context) => base.SetupContext(context);
}
}
8 changes: 8 additions & 0 deletions Scripts/Layout/FancyScrollView/GridView.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 144 additions & 0 deletions Scripts/Layout/FancyScrollView/GridView/FancyGridView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/// Credit setchi (https://github.com/setchi)
/// Sourced from - https://github.com/setchi/FancyScrollView

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UI.Extensions.EasingCore;

namespace UnityEngine.UI.Extensions
{
/// <summary>
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// </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()
{
/// <summary>
/// カラム同士の余白.
/// </summary>
[SerializeField] protected float columnSpacing = 0f;

GameObject cachedRowPrefab;

/// <summary>
/// 行の Prefab.
/// </summary>
/// <remarks>
/// <see cref="FancyGridView{TItemData, TContext}"/> では,
/// <see cref="FancyScrollView{TItemData, TContext}.CellPrefab"/> を行オブジェクトとして使用します.
/// </remarks>
protected sealed override GameObject CellPrefab => cachedRowPrefab ?? (cachedRowPrefab = SetupRowTemplate());

/// <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; }

/// <summary>
/// アイテムの総数.
/// </summary>
public int DataCount { get; private set; }

/// <inheritdoc/>
protected override void Initialize()
{
base.Initialize();

Debug.Assert(RowTemplate != null);
Debug.Assert(CellTemplate != null);
Debug.Assert(ColumnCount > 0);

Context.CellTemplate = CellTemplate.gameObject;
Context.ScrollDirection = Scroller.ScrollDirection;
Context.GetColumnCount = () => ColumnCount;
Context.GetColumnSpacing = () => columnSpacing;
}

/// <summary>
/// 行オブジェクトのセットアップを行います.
/// </summary>
/// <returns>行を構成する <c>GameObject</c>.</returns>
protected virtual GameObject SetupRowTemplate()
{
var cell = CellTemplate.GetComponent<RectTransform>();
var row = RowTemplate.GetComponent<RectTransform>();

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;
}

/// <summary>
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
/// </summary>
/// <param name="items">アイテム一覧.</param>
public virtual void UpdateContents(IList<TItemData> items)
{
DataCount = items.Count;

var rows = items
.Select((item, index) => (item, index))
.GroupBy(
x => x.index / ColumnCount,
x => x.item)
.Select(group => group.ToArray())
.ToArray();

UpdateContents(rows);
}

/// <summary>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public override void ScrollTo(int itemIndex, float duration, Alignment alignment = Alignment.Center, Action onComplete = null)
{
var rowIndex = itemIndex / Context.GetColumnCount();
base.ScrollTo(rowIndex, duration, alignment, onComplete);
}

/// <summary>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="alignment"><see cref="Alignment"/>.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public override void ScrollTo(int itemIndex, float duration, Ease easing, Alignment alignment = Alignment.Center, Action onComplete = null)
{
var rowIndex = itemIndex / Context.GetColumnCount();
base.ScrollTo(rowIndex, 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);
}
}
}
Loading

0 comments on commit 0a5444b

Please sign in to comment.