整合包

gameplay bundle has been imported together
This commit is contained in:
2025-08-09 18:29:32 +08:00
parent e7962bf492
commit 1d818fa4eb
1654 changed files with 431813 additions and 103 deletions
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 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.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 572727be5c765482ca314fb2581d9392
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
# FancyScrollView
It is a general-purpose ScrollView component that can implement scroll animation freely. It also supports infinite scrolling.
WebGL Demo: https://setchi.jp/FancyScrollView/demo
API Documentation: https://setchi.jp/FancyScrollView/api/FancyScrollView.html
This library is free and opensource on GitHub. More info, supports and sourcecodes, see https://github.com/setchi/FancyScrollView.
## Author
[setchi](https://github.com/setchi)
## License
[MIT](https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 96981b0d2ee8645cc94d70563bda2999
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: d19ac89da996d1843aafe828e87e81ac
folderAsset: yes
timeCreated: 1487434893
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3b7adbc21b737494d970fcbd5dbe1175
folderAsset: yes
timeCreated: 1508699670
licenseType: Pro
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
{
"name": "FancyScrollView.Editor",
"references": [
"FancyScrollView"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 310eb4609be1c41459e5ebb1bde5ac5a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,124 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEditor;
using UnityEditor.AnimatedValues;
// For manteinance, every new [SerializeField] variable in Scroller must be declared here
namespace FancyScrollView
{
[CustomEditor(typeof(Scroller))]
[CanEditMultipleObjects]
public class ScrollerEditor : Editor
{
SerializedProperty viewport;
SerializedProperty scrollDirection;
SerializedProperty movementType;
SerializedProperty elasticity;
SerializedProperty scrollSensitivity;
SerializedProperty inertia;
SerializedProperty decelerationRate;
SerializedProperty snap;
SerializedProperty draggable;
SerializedProperty scrollbar;
AnimBool showElasticity;
AnimBool showInertiaRelatedValues;
void OnEnable()
{
viewport = serializedObject.FindProperty("viewport");
scrollDirection = serializedObject.FindProperty("scrollDirection");
movementType = serializedObject.FindProperty("movementType");
elasticity = serializedObject.FindProperty("elasticity");
scrollSensitivity = serializedObject.FindProperty("scrollSensitivity");
inertia = serializedObject.FindProperty("inertia");
decelerationRate = serializedObject.FindProperty("decelerationRate");
snap = serializedObject.FindProperty("snap");
draggable = serializedObject.FindProperty("draggable");
scrollbar = serializedObject.FindProperty("scrollbar");
showElasticity = new AnimBool(Repaint);
showInertiaRelatedValues = new AnimBool(Repaint);
SetAnimBools(true);
}
void OnDisable()
{
showElasticity.valueChanged.RemoveListener(Repaint);
showInertiaRelatedValues.valueChanged.RemoveListener(Repaint);
}
void SetAnimBools(bool instant)
{
SetAnimBool(showElasticity, !movementType.hasMultipleDifferentValues && movementType.enumValueIndex == (int)MovementType.Elastic, instant);
SetAnimBool(showInertiaRelatedValues, !inertia.hasMultipleDifferentValues && inertia.boolValue, instant);
}
void SetAnimBool(AnimBool a, bool value, bool instant)
{
if (instant)
{
a.value = value;
}
else
{
a.target = value;
}
}
public override void OnInspectorGUI()
{
SetAnimBools(false);
serializedObject.Update();
EditorGUILayout.PropertyField(viewport);
EditorGUILayout.PropertyField(scrollDirection);
EditorGUILayout.PropertyField(movementType);
DrawMovementTypeRelatedValue();
EditorGUILayout.PropertyField(scrollSensitivity);
EditorGUILayout.PropertyField(inertia);
DrawInertiaRelatedValues();
EditorGUILayout.PropertyField(draggable);
EditorGUILayout.PropertyField(scrollbar);
serializedObject.ApplyModifiedProperties();
}
void DrawMovementTypeRelatedValue()
{
using (var group = new EditorGUILayout.FadeGroupScope(showElasticity.faded))
{
if (!group.visible)
{
return;
}
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(elasticity);
}
}
}
void DrawInertiaRelatedValues()
{
using (var group = new EditorGUILayout.FadeGroupScope(showInertiaRelatedValues.faded))
{
if (!group.visible)
{
return;
}
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(decelerationRate);
EditorGUILayout.PropertyField(snap);
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 986fda6476737da458576709b7f59ea3
timeCreated: 1508699683
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd41a20ffdea64364905efac7fb4aa9b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aca07f0cadcbc467ca910639faf19bd9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,76 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEngine;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyScrollView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <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 FancyCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
{
/// <summary>
/// このセルで表示しているデータのインデックス.
/// </summary>
public int Index { get; set; } = -1;
/// <summary>
/// このセルの可視状態.
/// </summary>
public virtual bool IsVisible => gameObject.activeSelf;
/// <summary>
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> の参照.
/// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
/// </summary>
protected TContext Context { get; private set; }
/// <summary>
/// <see cref="Context"/> をセットします.
/// </summary>
/// <param name="context">コンテキスト.</param>
public virtual void SetContext(TContext context) => Context = context;
/// <summary>
/// 初期化を行います.
/// </summary>
public virtual void Initialize() { }
/// <summary>
/// このセルの可視状態を設定します.
/// </summary>
/// <param name="visible">可視状態なら <c>true</c>, 非可視状態なら <c>false</c>.</param>
public virtual void SetVisible(bool visible) => gameObject.SetActive(visible);
/// <summary>
/// アイテムデータに基づいてこのセルの表示内容を更新します.
/// </summary>
/// <param name="itemData">アイテムデータ.</param>
public abstract void UpdateContent(TItemData itemData);
/// <summary>
/// <c>0.0f</c> ~ <c>1.0f</c> の値に基づいてこのセルのスクロール位置を更新します.
/// </summary>
/// <param name="position">ビューポート範囲の正規化されたスクロール位置.</param>
public abstract void UpdatePosition(float position);
}
/// <summary>
/// <see cref="FancyScrollView{TItemData}"/> のセルを実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyCell{TItemData, TContext}"/>
public abstract class FancyCell<TItemData> : FancyCell<TItemData, NullContext>
{
/// <inheritdoc/>
public sealed override void SetContext(NullContext context) => base.SetContext(context);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 23e073f093082c6449696d1199adc881
timeCreated: 1487183338
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,220 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System.Collections.Generic;
using UnityEngine;
namespace FancyScrollView
{
/// <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<FancyCell<TItemData, TContext>> pool = new List<FancyCell<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>
/// 初期化を行います.
/// </summary>
/// <remarks>
/// 最初にセルが生成される直前に呼び出されます.
/// </remarks>
protected virtual void Initialize() { }
/// <summary>
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
/// </summary>
/// <param name="itemsSource">アイテム一覧.</param>
protected virtual void UpdateContents(IList<TItemData> itemsSource)
{
ItemsSource = itemsSource;
Refresh();
}
/// <summary>
/// セルのレイアウトを強制的に更新します.
/// </summary>
protected virtual void Relayout() => UpdatePosition(currentPosition, false);
/// <summary>
/// セルのレイアウトと表示内容を強制的に更新します.
/// </summary>
protected virtual void Refresh() => UpdatePosition(currentPosition, true);
/// <summary>
/// スクロール位置を更新します.
/// </summary>
/// <param name="position">スクロール位置.</param>
protected virtual void UpdatePosition(float position) => UpdatePosition(position, false);
void UpdatePosition(float position, bool forceRefresh)
{
if (!initialized)
{
Initialize();
initialized = true;
}
currentPosition = position;
var p = position - scrollOffset / cellInterval;
var firstIndex = Mathf.CeilToInt(p);
var firstPosition = (Mathf.Ceil(p) - p) * cellInterval;
if (firstPosition + pool.Count * cellInterval < 1f)
{
ResizePool(firstPosition);
}
UpdateCells(firstPosition, firstIndex, forceRefresh);
}
void ResizePool(float firstPosition)
{
Debug.Assert(CellPrefab != null);
Debug.Assert(cellContainer != null);
var addCount = Mathf.CeilToInt((1f - firstPosition) / cellInterval) - pool.Count;
for (var i = 0; i < addCount; i++)
{
var cell = Instantiate(CellPrefab, cellContainer).GetComponent<FancyCell<TItemData, TContext>>();
if (cell == null)
{
throw new MissingComponentException(string.Format(
"FancyCell<{0}, {1}> component not found in {2}.",
typeof(TItemData).FullName, typeof(TContext).FullName, CellPrefab.name));
}
cell.SetContext(Context);
cell.Initialize();
cell.SetVisible(false);
pool.Add(cell);
}
}
void UpdateCells(float firstPosition, int firstIndex, bool forceRefresh)
{
for (var i = 0; i < pool.Count; i++)
{
var index = firstIndex + i;
var position = firstPosition + i * cellInterval;
var cell = pool[CircularIndex(index, pool.Count)];
if (loop)
{
index = CircularIndex(index, ItemsSource.Count);
}
if (index < 0 || index >= ItemsSource.Count || position > 1f)
{
cell.SetVisible(false);
continue;
}
if (forceRefresh || cell.Index != index || !cell.IsVisible)
{
cell.Index = index;
cell.SetVisible(true);
cell.UpdateContent(ItemsSource[index]);
}
cell.UpdatePosition(position);
}
}
int CircularIndex(int i, int size) => size < 1 ? 0 : i < 0 ? size - 1 + (i + 1) % size : i % size;
#if UNITY_EDITOR
bool cachedLoop;
float cachedCellInterval, cachedScrollOffset;
void LateUpdate()
{
if (cachedLoop != loop ||
cachedCellInterval != cellInterval ||
cachedScrollOffset != scrollOffset)
{
cachedLoop = loop;
cachedCellInterval = cellInterval;
cachedScrollOffset = scrollOffset;
UpdatePosition(currentPosition);
}
}
#endif
}
/// <summary>
/// <see cref="FancyScrollView{TItemData}"/> のコンテキストクラス.
/// </summary>
public sealed class NullContext { }
/// <summary>
/// スクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップに対応しています.
/// </summary>
/// <typeparam name="TItemData"></typeparam>
/// <seealso cref="FancyScrollView{TItemData, TContext}"/>
public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, NullContext> { }
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e5a78e3955a0bca4593487fdd78cf2d4
timeCreated: 1487183318
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
{
"name": "FancyScrollView"
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 59f6770f7492c42ff827a64ac010ac49
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c7768f2982b0142ab876d2bb4b597646
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,76 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEngine;
using System.Linq;
namespace FancyScrollView
{
/// <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);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84300901ad8704c11b39587ed6d87468
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,185 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using EasingCore;
namespace FancyScrollView
{
/// <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, IFancyGridViewContext, new()
{
/// <summary>
/// デフォルトのセルグループクラス.
/// </summary>
protected abstract class DefaultCellGroup : FancyCellGroup<TItemData, TContext> { }
/// <summary>
/// 最初にセルを配置する軸方向のセル同士の余白.
/// </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"/> を最初にセルを配置する軸方向のセルコンテナとして使用します.
/// </remarks>
protected sealed override GameObject CellPrefab => cellGroupTemplate;
/// <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(startAxisCellCount > 0);
Context.ScrollDirection = Scroller.ScrollDirection;
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>
/// <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>
{
Context.CellTemplate = cellTemplate.gameObject;
cellGroupTemplate = new GameObject("Group").AddComponent<TGroup>().gameObject;
cellGroupTemplate.transform.SetParent(cellContainer, false);
cellGroupTemplate.SetActive(false);
}
/// <summary>
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
/// </summary>
/// <param name="items">アイテム一覧.</param>
public virtual void UpdateContents(IList<TItemData> items)
{
DataCount = items.Count;
var itemGroups = items
.Select((item, index) => (item, index))
.GroupBy(
x => x.index / startAxisCellCount,
x => x.item)
.Select(group => group.ToArray())
.ToArray();
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>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
protected override void ScrollTo(int itemIndex, float duration, float alignment = 0.5f, Action onComplete = null)
{
var groupIndex = itemIndex / startAxisCellCount;
base.ScrollTo(groupIndex, duration, alignment, onComplete);
}
/// <summary>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
protected override void ScrollTo(int itemIndex, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
{
var groupIndex = itemIndex / startAxisCellCount;
base.ScrollTo(groupIndex, duration, easing, alignment, onComplete);
}
}
/// <summary>
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyGridView{TItemData, TContext}"/>
public abstract class FancyGridView<TItemData> : FancyGridView<TItemData, FancyGridViewContext> { }
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e806c8d18b1ff45bb87e9a5b87ec85e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEngine;
namespace FancyScrollView
{
/// <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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 129bb13f5b2b9435a93d53ca93471334
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
using UnityEngine;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキスト基底クラス.
/// </summary>
public class FancyGridViewContext : IFancyGridViewContext
{
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { 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; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 954a17398bfb54ee7baac3d7ab7e822c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
using UnityEngine;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyCellGroup{TItemData, TContext}"/> のコンテキストインターフェース.
/// </summary>
public interface IFancyCellGroupContext
{
GameObject CellTemplate { get; set; }
Func<int> GetGroupCount { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 213da42f7c9a347beaa56c5886c582a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキストインターフェース.
/// </summary>
public interface IFancyGridViewContext : IFancyScrollRectContext, IFancyCellGroupContext
{
Func<float> GetStartAxisSpacing { get; set; }
Func<float> GetCellSize { get; set ; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cf2d53d9c81945c28f7c558a7c40ba3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e2c5beedd885c4490a86bb4973f965bf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,305 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
using System.Collections.Generic;
using UnityEngine;
using EasingCore;
namespace FancyScrollView
{
/// <summary>
/// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollRect{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
[RequireComponent(typeof(Scroller))]
public abstract class FancyScrollRect<TItemData, TContext> : FancyScrollView<TItemData, TContext>
where TContext : class, IFancyScrollRectContext, new()
{
/// <summary>
/// スクロール中にセルが再利用されるまでの余白のセル数.
/// </summary>
/// <remarks>
/// <c>0</c> を指定するとセルが完全に隠れた直後に再利用されます.
/// <c>1</c> 以上を指定すると, そのセル数だけ余分にスクロールしてから再利用されます.
/// </remarks>
[SerializeField] protected float reuseCellMarginCount = 0f;
/// <summary>
/// コンテンツ先頭の余白.
/// </summary>
[SerializeField] protected float paddingHead = 0f;
/// <summary>
/// コンテンツ末尾の余白.
/// </summary>
[SerializeField] protected float paddingTail = 0f;
/// <summary>
/// スクロール軸方向のセル同士の余白.
/// </summary>
[SerializeField] protected float spacing = 0f;
/// <summary>
/// セルのサイズ.
/// </summary>
protected abstract float CellSize { get; }
/// <summary>
/// スクロール可能かどうか.
/// </summary>
/// <remarks>
/// アイテム数が十分少なくビューポート内に全てのセルが収まっている場合は <c>false</c>, それ以外は <c>true</c> になります.
/// </remarks>
protected virtual bool Scrollable => MaxScrollPosition > 0f;
Scroller cachedScroller;
/// <summary>
/// スクロール位置を制御する <see cref="FancyScrollView.Scroller"/> のインスタンス.
/// </summary>
/// <remarks>
/// <see cref="Scroller"/> のスクロール位置を変更する際は必ず <see cref="ToScrollerPosition(float)"/> を使用して変換した位置を使用してください.
/// </remarks>
protected Scroller Scroller => cachedScroller ?? (cachedScroller = GetComponent<Scroller>());
float ScrollLength => 1f / Mathf.Max(cellInterval, 1e-2f) - 1f;
float ViewportLength => ScrollLength - reuseCellMarginCount * 2f;
float PaddingHeadLength => (paddingHead - spacing * 0.5f) / (CellSize + spacing);
float MaxScrollPosition => ItemsSource.Count
- ScrollLength
+ reuseCellMarginCount * 2f
+ (paddingHead + paddingTail - spacing) / (CellSize + spacing);
/// <inheritdoc/>
protected override void Initialize()
{
base.Initialize();
Context.ScrollDirection = Scroller.ScrollDirection;
Context.CalculateScrollSize = () =>
{
var interval = CellSize + spacing;
var reuseMargin = interval * reuseCellMarginCount;
var scrollSize = Scroller.ViewportSize + interval + reuseMargin * 2f;
return (scrollSize, reuseMargin);
};
AdjustCellIntervalAndScrollOffset();
Scroller.OnValueChanged(OnScrollerValueChanged);
}
/// <summary>
/// <see cref="Scroller"/> のスクロール位置が変更された際の処理.
/// </summary>
/// <param name="p"><see cref="Scroller"/> のスクロール位置.</param>
void OnScrollerValueChanged(float p)
{
base.UpdatePosition(ToFancyScrollViewPosition(Scrollable ? p : 0f));
if (Scroller.Scrollbar)
{
if (p > ItemsSource.Count - 1)
{
ShrinkScrollbar(p - (ItemsSource.Count - 1));
}
else if (p < 0f)
{
ShrinkScrollbar(-p);
}
}
}
/// <summary>
/// スクロール範囲を超えてスクロールされた量に基づいて, スクロールバーのサイズを縮小します.
/// </summary>
/// <param name="offset">スクロール範囲を超えてスクロールされた量.</param>
void ShrinkScrollbar(float offset)
{
var scale = 1f - ToFancyScrollViewPosition(offset) / (ViewportLength - PaddingHeadLength);
UpdateScrollbarSize((ViewportLength - PaddingHeadLength) * scale);
}
/// <inheritdoc/>
protected override void Refresh()
{
AdjustCellIntervalAndScrollOffset();
RefreshScroller();
base.Refresh();
}
/// <inheritdoc/>
protected override void Relayout()
{
AdjustCellIntervalAndScrollOffset();
RefreshScroller();
base.Relayout();
}
/// <summary>
/// <see cref="Scroller"/> の各種状態を更新します.
/// </summary>
protected void RefreshScroller()
{
Scroller.Draggable = Scrollable;
Scroller.ScrollSensitivity = ToScrollerPosition(ViewportLength - PaddingHeadLength);
Scroller.Position = ToScrollerPosition(currentPosition);
if (Scroller.Scrollbar)
{
Scroller.Scrollbar.gameObject.SetActive(Scrollable);
UpdateScrollbarSize(ViewportLength);
}
}
/// <inheritdoc/>
protected override void UpdateContents(IList<TItemData> items)
{
AdjustCellIntervalAndScrollOffset();
base.UpdateContents(items);
Scroller.SetTotalCount(items.Count);
RefreshScroller();
}
/// <summary>
/// スクロール位置を更新します.
/// </summary>
/// <param name="position">スクロール位置.</param>
protected new void UpdatePosition(float position)
{
Scroller.Position = ToScrollerPosition(position, 0.5f);
}
/// <summary>
/// 指定したアイテムの位置までジャンプします.
/// </summary>
/// <param name="itemIndex">アイテムのインデックス.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
protected virtual void JumpTo(int itemIndex, float alignment = 0.5f)
{
Scroller.Position = ToScrollerPosition(itemIndex, alignment);
}
/// <summary>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="index">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
protected virtual void ScrollTo(int index, float duration, float alignment = 0.5f, Action onComplete = null)
{
Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, onComplete);
}
/// <summary>
/// 指定したアイテムの位置まで移動します.
/// </summary>
/// <param name="index">アイテムのインデックス.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easing">移動に使用するイージング.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
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);
}
/// <summary>
/// ビューポートとコンテンツの長さに基づいてスクロールバーのサイズを更新します.
/// </summary>
/// <param name="viewportLength">ビューポートのサイズ.</param>
protected void UpdateScrollbarSize(float viewportLength)
{
var contentLength = Mathf.Max(ItemsSource.Count + (paddingHead + paddingTail - spacing) / (CellSize + spacing), 1);
Scroller.Scrollbar.size = Scrollable ? Mathf.Clamp01(viewportLength / contentLength) : 1f;
}
/// <summary>
/// <see cref="Scroller"/> が扱うスクロール位置を <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置に変換します.
/// </summary>
/// <param name="position"><see cref="Scroller"/> が扱うスクロール位置.</param>
/// <returns><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</returns>
protected float ToFancyScrollViewPosition(float position)
{
return position / Mathf.Max(ItemsSource.Count - 1, 1) * MaxScrollPosition - PaddingHeadLength;
}
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
/// </summary>
/// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
/// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
protected float ToScrollerPosition(float position)
{
return (position + PaddingHeadLength) / MaxScrollPosition * Mathf.Max(ItemsSource.Count - 1, 1);
}
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
/// </summary>
/// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
/// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
protected float ToScrollerPosition(float position, float alignment = 0.5f)
{
var offset = alignment * (ScrollLength - (1f + reuseCellMarginCount * 2f))
+ (1f - alignment - 0.5f) * spacing / (CellSize + spacing);
return ToScrollerPosition(Mathf.Clamp(position - offset, 0f, MaxScrollPosition));
}
/// <summary>
/// 指定された設定を実現するための
/// <see cref="FancyScrollView{TItemData,TContext}.cellInterval"/> と
/// <see cref="FancyScrollView{TItemData,TContext}.scrollOffset"/> を計算して適用します.
/// </summary>
protected void AdjustCellIntervalAndScrollOffset()
{
var totalSize = Scroller.ViewportSize + (CellSize + spacing) * (1f + reuseCellMarginCount * 2f);
cellInterval = (CellSize + spacing) / totalSize;
scrollOffset = cellInterval * (1f + reuseCellMarginCount);
}
protected virtual void OnValidate()
{
AdjustCellIntervalAndScrollOffset();
if (loop)
{
loop = false;
Debug.LogError("Loop is currently not supported in FancyScrollRect.");
}
if (Scroller.SnapEnabled)
{
Scroller.SnapEnabled = false;
Debug.LogError("Snap is currently not supported in FancyScrollRect.");
}
if (Scroller.MovementType == MovementType.Unrestricted)
{
Scroller.MovementType = MovementType.Elastic;
Debug.LogError("MovementType.Unrestricted is currently not supported in FancyScrollRect.");
}
}
}
/// <summary>
/// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
/// 無限スクロールおよびスナップには対応していません.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyScrollRect{TItemData, TContext}"/>
public abstract class FancyScrollRect<TItemData> : FancyScrollRect<TItemData, FancyScrollRectContext> { }
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66c8eb84fdbde4a4a8273b98227a282d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,61 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEngine;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
/// 代わりに <see cref="FancyScrollRectCell{TItemData}"/> を使用します.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <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/>
public override void UpdatePosition(float position)
{
var (scrollSize, reuseMargin) = Context.CalculateScrollSize();
var normalizedPosition = (Mathf.Lerp(0f, scrollSize, position) - reuseMargin) / (scrollSize - reuseMargin * 2f);
var start = 0.5f * scrollSize;
var end = -start;
UpdatePosition(normalizedPosition, Mathf.Lerp(start, end, position));
}
/// <summary>
/// このセルの位置を更新します.
/// </summary>
/// <param name="normalizedPosition">
/// ビューポートの範囲で正規化されたスクロール位置.
/// <see cref="FancyScrollRect{TItemData, TContext}.reuseCellMarginCount"/> の値に基づいて
/// <c>0.0</c> ~ <c>1.0</c> の範囲を超えた値が渡されることがあります.
/// </param>
/// <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>
/// <see cref="FancyScrollRect{TItemData}"/> のセルを実装するための抽象基底クラス.
/// </summary>
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
/// <seealso cref="FancyScrollRectCell{TItemData, TContext}"/>
public abstract class FancyScrollRectCell<TItemData> : FancyScrollRectCell<TItemData, FancyScrollRectContext>
{
/// <inheritdoc/>
public sealed override void SetContext(FancyScrollRectContext context) => base.SetContext(context);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09f137a55810740eab42e24ef242dcfa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキスト基底クラス.
/// </summary>
public class FancyScrollRectContext : IFancyScrollRectContext
{
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91383bd46cee541a7a03e08cfaa47c16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
namespace FancyScrollView
{
/// <summary>
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキストインターフェース.
/// </summary>
public interface IFancyScrollRectContext
{
ScrollDirection ScrollDirection { get; set; }
Func<(float ScrollSize, float ReuseMargin)> CalculateScrollSize { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e425de6354b6946c7a3b9f2c807b60fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 691a9f5ee4aec4112a01b9a4332cac51
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,196 @@
/*
* EasingCore (https://github.com/setchi/EasingCore)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/EasingCore/blob/master/LICENSE)
*/
using UnityEngine;
namespace EasingCore
{
public enum Ease
{
Linear,
InBack,
InBounce,
InCirc,
InCubic,
InElastic,
InExpo,
InQuad,
InQuart,
InQuint,
InSine,
OutBack,
OutBounce,
OutCirc,
OutCubic,
OutElastic,
OutExpo,
OutQuad,
OutQuart,
OutQuint,
OutSine,
InOutBack,
InOutBounce,
InOutCirc,
InOutCubic,
InOutElastic,
InOutExpo,
InOutQuad,
InOutQuart,
InOutQuint,
InOutSine,
}
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 EasingFunction Get(Ease type)
{
switch (type)
{
case Ease.Linear: return linear;
case Ease.InBack: return inBack;
case Ease.InBounce: return inBounce;
case Ease.InCirc: return inCirc;
case Ease.InCubic: return inCubic;
case Ease.InElastic: return inElastic;
case Ease.InExpo: return inExpo;
case Ease.InQuad: return inQuad;
case Ease.InQuart: return inQuart;
case Ease.InQuint: return inQuint;
case Ease.InSine: return inSine;
case Ease.OutBack: return outBack;
case Ease.OutBounce: return outBounce;
case Ease.OutCirc: return outCirc;
case Ease.OutCubic: return outCubic;
case Ease.OutElastic: return outElastic;
case Ease.OutExpo: return outExpo;
case Ease.OutQuad: return outQuad;
case Ease.OutQuart: return outQuart;
case Ease.OutQuint: return outQuint;
case Ease.OutSine: return outSine;
case Ease.InOutBack: return inOutBack;
case Ease.InOutBounce: return inOutBounce;
case Ease.InOutCirc: return inOutCirc;
case Ease.InOutCubic: return inOutCubic;
case Ease.InOutElastic: return inOutElastic;
case Ease.InOutExpo: return inOutExpo;
case Ease.InOutQuad: return inOutQuad;
case Ease.InOutQuart: return inOutQuart;
case Ease.InOutQuint: return inOutQuint;
case Ease.InOutSine: return inOutSine;
default: return linear;
}
float linear(float t) => t;
float inBack(float t) => t * t * t - t * Mathf.Sin(t * Mathf.PI);
float outBack(float t) => 1f - inBack(1f - t);
float inOutBack(float t) =>
t < 0.5f
? 0.5f * inBack(2f * t)
: 0.5f * outBack(2f * t - 1f) + 0.5f;
float inBounce(float t) => 1f - outBounce(1f - t);
float outBounce(float t) =>
t < 4f / 11.0f ?
(121f * t * t) / 16.0f :
t < 8f / 11.0f ?
(363f / 40.0f * t * t) - (99f / 10.0f * t) + 17f / 5.0f :
t < 9f / 10.0f ?
(4356f / 361.0f * t * t) - (35442f / 1805.0f * t) + 16061f / 1805.0f :
(54f / 5.0f * t * t) - (513f / 25.0f * t) + 268f / 25.0f;
float inOutBounce(float t) =>
t < 0.5f
? 0.5f * inBounce(2f * t)
: 0.5f * outBounce(2f * t - 1f) + 0.5f;
float inCirc(float t) => 1f - Mathf.Sqrt(1f - (t * t));
float outCirc(float t) => Mathf.Sqrt((2f - t) * t);
float inOutCirc(float t) =>
t < 0.5f
? 0.5f * (1 - Mathf.Sqrt(1f - 4f * (t * t)))
: 0.5f * (Mathf.Sqrt(-((2f * t) - 3f) * ((2f * t) - 1f)) + 1f);
float inCubic(float t) => t * t * t;
float outCubic(float t) => inCubic(t - 1f) + 1f;
float inOutCubic(float t) =>
t < 0.5f
? 4f * t * t * t
: 0.5f * inCubic(2f * t - 2f) + 1f;
float inElastic(float t) => Mathf.Sin(13f * (Mathf.PI * 0.5f) * t) * Mathf.Pow(2f, 10f * (t - 1f));
float outElastic(float t) => Mathf.Sin(-13f * (Mathf.PI * 0.5f) * (t + 1)) * Mathf.Pow(2f, -10f * t) + 1f;
float inOutElastic(float t) =>
t < 0.5f
? 0.5f * Mathf.Sin(13f * (Mathf.PI * 0.5f) * (2f * t)) * Mathf.Pow(2f, 10f * ((2f * t) - 1f))
: 0.5f * (Mathf.Sin(-13f * (Mathf.PI * 0.5f) * ((2f * t - 1f) + 1f)) * Mathf.Pow(2f, -10f * (2f * t - 1f)) + 2f);
float inExpo(float t) => Mathf.Approximately(0.0f, t) ? t : Mathf.Pow(2f, 10f * (t - 1f));
float outExpo(float t) => Mathf.Approximately(1.0f, t) ? t : 1f - Mathf.Pow(2f, -10f * t);
float inOutExpo(float v) =>
Mathf.Approximately(0.0f, v) || Mathf.Approximately(1.0f, v)
? v
: v < 0.5f
? 0.5f * Mathf.Pow(2f, (20f * v) - 10f)
: -0.5f * Mathf.Pow(2f, (-20f * v) + 10f) + 1f;
float inQuad(float t) => t * t;
float outQuad(float t) => -t * (t - 2f);
float inOutQuad(float t) =>
t < 0.5f
? 2f * t * t
: -2f * t * t + 4f * t - 1f;
float inQuart(float t) => t * t * t * t;
float outQuart(float t)
{
var u = t - 1f;
return u * u * u * (1f - t) + 1f;
}
float inOutQuart(float t) =>
t < 0.5f
? 8f * inQuart(t)
: -8f * inQuart(t - 1f) + 1f;
float inQuint(float t) => t * t * t * t * t;
float outQuint(float t) => inQuint(t - 1f) + 1f;
float inOutQuint(float t) =>
t < 0.5f
? 16f * inQuint(t)
: 0.5f * inQuint(2f * t - 2f) + 1f;
float inSine(float t) => Mathf.Sin((t - 1f) * (Mathf.PI * 0.5f)) + 1f;
float outSine(float t) => Mathf.Sin(t * (Mathf.PI * 0.5f));
float inOutSine(float t) => 0.5f * (1f - Mathf.Cos(t * Mathf.PI));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3b2a35f9ff1c8487582b74620e380486
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
namespace FancyScrollView
{
public enum MovementDirection
{
Left,
Right,
Up,
Down,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11769de5e972543c7a4132ceff94a5c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using UnityEngine.UI;
namespace FancyScrollView
{
public enum MovementType
{
Unrestricted = ScrollRect.MovementType.Unrestricted,
Elastic = ScrollRect.MovementType.Elastic,
Clamped = ScrollRect.MovementType.Clamped
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 585de8f4f272547c89041f94f2ad92b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
namespace FancyScrollView
{
public enum ScrollDirection
{
Vertical,
Horizontal,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3392a30c085c642139c92e94ec0d6309
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,602 @@
/*
* FancyScrollView (https://github.com/setchi/FancyScrollView)
* Copyright (c) 2020 setchi
* Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
*/
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using EasingCore;
namespace FancyScrollView
{
/// <summary>
/// スクロール位置の制御を行うコンポーネント.
/// </summary>
public class Scroller : UIBehaviour, IPointerUpHandler, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
[SerializeField] RectTransform viewport = default;
/// <summary>
/// ビューポートのサイズ.
/// </summary>
public float ViewportSize => scrollDirection == ScrollDirection.Horizontal
? viewport.rect.size.x
: viewport.rect.size.y;
[SerializeField] ScrollDirection scrollDirection = ScrollDirection.Vertical;
/// <summary>
/// スクロール方向.
/// </summary>
public ScrollDirection ScrollDirection => scrollDirection;
[SerializeField] MovementType movementType = MovementType.Elastic;
/// <summary>
/// コンテンツがスクロール範囲を越えて移動するときに使用する挙動.
/// </summary>
public MovementType MovementType
{
get => movementType;
set => movementType = value;
}
[SerializeField] float elasticity = 0.1f;
/// <summary>
/// コンテンツがスクロール範囲を越えて移動するときに使用する弾力性の量.
/// </summary>
public float Elasticity
{
get => elasticity;
set => elasticity = value;
}
[SerializeField] float scrollSensitivity = 1f;
/// <summary>
/// <see cref="ViewportSize"/> の端から端まで Drag したときのスクロール位置の変化量.
/// </summary>
public float ScrollSensitivity
{
get => scrollSensitivity;
set => scrollSensitivity = value;
}
[SerializeField] bool inertia = true;
/// <summary>
/// 慣性を使用するかどうか. <c>true</c> を指定すると慣性が有効に, <c>false</c> を指定すると慣性が無効になります.
/// </summary>
public bool Inertia
{
get => inertia;
set => inertia = value;
}
[SerializeField] float decelerationRate = 0.03f;
/// <summary>
/// スクロールの減速率. <see cref="Inertia"/> が <c>true</c> の場合のみ有効です.
/// </summary>
public float DecelerationRate
{
get => decelerationRate;
set => decelerationRate = value;
}
[SerializeField] Snap snap = new Snap {
Enable = true,
VelocityThreshold = 0.5f,
Duration = 0.3f,
Easing = Ease.InOutCubic
};
/// <summary>
/// <c>true</c> ならスナップし, <c>false</c>ならスナップしません.
/// </summary>
/// <remarks>
/// スナップを有効にすると, 慣性でスクロールが止まる直前に最寄りのセルへ移動します.
/// </remarks>
public bool SnapEnabled
{
get => snap.Enable;
set => snap.Enable = value;
}
[SerializeField] bool draggable = true;
/// <summary>
/// Drag 入力を受付けるかどうか.
/// </summary>
public bool Draggable
{
get => draggable;
set => draggable = value;
}
[SerializeField] Scrollbar scrollbar = default;
/// <summary>
/// スクロールバーのオブジェクト.
/// </summary>
public Scrollbar Scrollbar => scrollbar;
/// <summary>
/// 現在のスクロール位置.
/// </summary>
/// <value></value>
public float Position
{
get => currentPosition;
set
{
autoScrollState.Reset();
velocity = 0f;
dragging = false;
UpdatePosition(value);
}
}
readonly AutoScrollState autoScrollState = new AutoScrollState();
Action<float> onValueChanged;
Action<int> onSelectionChanged;
Vector2 beginDragPointerPosition;
float scrollStartPosition;
float prevPosition;
float currentPosition;
int totalCount;
bool hold;
bool scrolling;
bool dragging;
float velocity;
[Serializable]
class Snap
{
public bool Enable;
public float VelocityThreshold;
public float Duration;
public Ease Easing;
}
static readonly EasingFunction DefaultEasingFunction = Easing.Get(Ease.OutCubic);
class AutoScrollState
{
public bool Enable;
public bool Elastic;
public float Duration;
public EasingFunction EasingFunction;
public float StartTime;
public float EndPosition;
public Action OnComplete;
public void Reset()
{
Enable = false;
Elastic = false;
Duration = 0f;
StartTime = 0f;
EasingFunction = DefaultEasingFunction;
EndPosition = 0f;
OnComplete = null;
}
public void Complete()
{
OnComplete?.Invoke();
Reset();
}
}
protected override void Start()
{
base.Start();
if (scrollbar)
{
scrollbar.onValueChanged.AddListener(x => UpdatePosition(x * (totalCount - 1f), false));
}
}
/// <summary>
/// スクロール位置が変化したときのコールバックを設定します.
/// </summary>
/// <param name="callback">スクロール位置が変化したときのコールバック.</param>
public void OnValueChanged(Action<float> callback) => onValueChanged = callback;
/// <summary>
/// 選択位置が変化したときのコールバックを設定します.
/// </summary>
/// <param name="callback">選択位置が変化したときのコールバック.</param>
public void OnSelectionChanged(Action<int> callback) => onSelectionChanged = callback;
/// <summary>
/// アイテムの総数を設定します.
/// </summary>
/// <remarks>
/// <paramref name="totalCount"/> を元に最大スクロール位置を計算します.
/// </remarks>
/// <param name="totalCount">アイテムの総数.</param>
public void SetTotalCount(int totalCount) => this.totalCount = totalCount;
/// <summary>
/// 指定した位置まで移動します.
/// </summary>
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public void ScrollTo(float position, float duration, Action onComplete = null) => ScrollTo(position, duration, Ease.OutCubic, onComplete);
/// <summary>
/// 指定した位置まで移動します.
/// </summary>
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
/// <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, Easing.Get(easing), onComplete);
/// <summary>
/// 指定した位置まで移動します.
/// </summary>
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
/// <param name="duration">移動にかける秒数.</param>
/// <param name="easingFunction">移動に使用するイージング関数.</param>
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
public void ScrollTo(float position, float duration, EasingFunction easingFunction, Action onComplete = null)
{
if (duration <= 0f)
{
Position = CircularPosition(position, totalCount);
onComplete?.Invoke();
return;
}
autoScrollState.Reset();
autoScrollState.Enable = true;
autoScrollState.Duration = duration;
autoScrollState.EasingFunction = easingFunction ?? DefaultEasingFunction;
autoScrollState.StartTime = Time.unscaledTime;
autoScrollState.EndPosition = currentPosition + CalculateMovementAmount(currentPosition, position);
autoScrollState.OnComplete = onComplete;
velocity = 0f;
scrollStartPosition = currentPosition;
UpdateSelection(Mathf.RoundToInt(CircularPosition(autoScrollState.EndPosition, totalCount)));
}
/// <summary>
/// 指定したインデックスの位置までジャンプします.
/// </summary>
/// <param name="index">アイテムのインデックス.</param>
public void JumpTo(int index)
{
if (index < 0 || index > totalCount - 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
UpdateSelection(index);
Position = index;
}
/// <summary>
/// <paramref name="sourceIndex"/> から <paramref name="destIndex"/> に移動する際の移動方向を返します.
/// スクロール範囲が無制限に設定されている場合は, 最短距離の移動方向を返します.
/// </summary>
/// <param name="sourceIndex">移動元のインデックス.</param>
/// <param name="destIndex">移動先のインデックス.</param>
/// <returns></returns>
public MovementDirection GetMovementDirection(int sourceIndex, int destIndex)
{
var movementAmount = CalculateMovementAmount(sourceIndex, destIndex);
return scrollDirection == ScrollDirection.Horizontal
? movementAmount > 0
? MovementDirection.Left
: MovementDirection.Right
: movementAmount > 0
? MovementDirection.Up
: 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)
{
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
{
return;
}
hold = false;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
viewport,
eventData.position,
eventData.pressEventCamera,
out beginDragPointerPosition);
scrollStartPosition = currentPosition;
dragging = true;
autoScrollState.Reset();
}
/// <inheritdoc/>
void IDragHandler.OnDrag(PointerEventData eventData)
{
if (!draggable || eventData.button != PointerEventData.InputButton.Left || !dragging)
{
return;
}
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
viewport,
eventData.position,
eventData.pressEventCamera,
out var dragPointerPosition))
{
return;
}
var pointerDelta = dragPointerPosition - beginDragPointerPosition;
var position = (scrollDirection == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
/ ViewportSize
* scrollSensitivity
+ scrollStartPosition;
var offset = CalculateOffset(position);
position += offset;
if (movementType == MovementType.Elastic)
{
if (offset != 0f)
{
position -= RubberDelta(offset, scrollSensitivity);
}
}
UpdatePosition(position);
}
/// <inheritdoc/>
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
{
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
{
return;
}
dragging = false;
}
float CalculateOffset(float position)
{
if (movementType == MovementType.Unrestricted)
{
return 0f;
}
if (position < 0f)
{
return -position;
}
if (position > totalCount - 1)
{
return totalCount - 1 - position;
}
return 0f;
}
void UpdatePosition(float position, bool updateScrollbar = true)
{
onValueChanged?.Invoke(currentPosition = position);
if (scrollbar && updateScrollbar)
{
scrollbar.value = Mathf.Clamp01(position / Mathf.Max(totalCount - 1f, 1e-4f));
}
}
void UpdateSelection(int index) => onSelectionChanged?.Invoke(index);
float RubberDelta(float overStretching, float viewSize) =>
(1 - 1 / (Mathf.Abs(overStretching) * 0.55f / viewSize + 1)) * viewSize * Mathf.Sign(overStretching);
void Update()
{
var deltaTime = Time.unscaledDeltaTime;
var offset = CalculateOffset(currentPosition);
if (autoScrollState.Enable)
{
var position = 0f;
if (autoScrollState.Elastic)
{
position = Mathf.SmoothDamp(currentPosition, currentPosition + offset, ref velocity,
elasticity, Mathf.Infinity, deltaTime);
if (Mathf.Abs(velocity) < 0.01f)
{
position = Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1);
velocity = 0f;
autoScrollState.Complete();
}
}
else
{
var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) /
Mathf.Max(autoScrollState.Duration, float.Epsilon));
position = Mathf.LerpUnclamped(scrollStartPosition, autoScrollState.EndPosition,
autoScrollState.EasingFunction(alpha));
if (Mathf.Approximately(alpha, 1f))
{
autoScrollState.Complete();
}
}
UpdatePosition(position);
}
else if (!(dragging || scrolling) && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
{
var position = currentPosition;
if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
{
autoScrollState.Reset();
autoScrollState.Enable = true;
autoScrollState.Elastic = true;
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1));
}
else if (inertia)
{
velocity *= Mathf.Pow(decelerationRate, deltaTime);
if (Mathf.Abs(velocity) < 0.001f)
{
velocity = 0f;
}
position += velocity * deltaTime;
if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
{
ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
}
}
else
{
velocity = 0f;
}
if (!Mathf.Approximately(velocity, 0f))
{
if (movementType == MovementType.Clamped)
{
offset = CalculateOffset(position);
position += offset;
if (Mathf.Approximately(position, 0f) || Mathf.Approximately(position, totalCount - 1f))
{
velocity = 0f;
UpdateSelection(Mathf.RoundToInt(position));
}
}
UpdatePosition(position);
}
}
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)
{
if (movementType != MovementType.Unrestricted)
{
return Mathf.Clamp(destPosition, 0, totalCount - 1) - sourcePosition;
}
var amount = CircularPosition(destPosition, totalCount) - CircularPosition(sourcePosition, totalCount);
if (Mathf.Abs(amount) > totalCount * 0.5f)
{
amount = Mathf.Sign(-amount) * (totalCount - Mathf.Abs(amount));
}
return amount;
}
float CircularPosition(float p, int size) => size < 1 ? 0 : p < 0 ? size - 1 + (p + 1) % size : p % size;
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9f0e995f494626a4f878eedbded37c8d
timeCreated: 1487408581
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
{
"name": "jp.setchi.fancyscrollview",
"displayName": "FancyScrollView",
"description": "A scrollview component that can implement highly flexible animation.",
"version": "1.9.0",
"unity": "2019.4",
"license": "MIT",
"author": "setchi",
"dependencies": {},
"samples": [
{
"displayName": "Demo",
"path": "Samples~"
}
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1c75442fd24454d849623c0a0701b45c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: