🐡

Unity シンプルな仮想スクロール (uGUI)

2023/02/11に公開

おことわり

この記事のコードには、新しい版 (GitHub)が存在します。

概要

  • 特徴
    • UnityEngine.UI.ScrollRectを継承しています。
      • 通常のScrollRectで動的に項目生成する場合と同じようにアセットを用意できます。
    • 個別に表示サイズが異なる「可変サイズの項目」に対応しています。
    • 縦/横並び、正/逆順、整列側(左/右、上/下)が選択できます。
    • 項目リストを動的に操作(挿入、削除、置換など)可能です。
  • 次のような使い方を想定しています。
    • UnityEngine.UI.ScrollRect(uGUI)の代替としてContent内にGameObjectを動的に生成します。
    • スクロール方向に対して、リストの前方にこれから表示される項目を追加し、表示の済んだリストの後方の項目を削除するなど、リストを動的に増減します。
    • 有限でも長大なリストを扱う際に、可視範囲外のGameObjectを節約します。
  • 次のような使い方はできません。
    • リストの上端と下端をループさせて、有限長のリストで無限にスクロールする。
    • 動的に生成される不定長のリストを自動的にスクロールする。

テスト環境

  • Unity 2022.3.6f1
  • Windows 11

アセットパッケージ

機能

  • リスト(IEnumerable<IInfiniteScrollItem>)の登録と初期化
    • 縦スクロール、または、横スクロール
    • 項目に対するパディングとスペーシング
    • スクロールに直行する方向の項目制御(下/左寄せ、中央寄せ、上/右寄せ、拡縮Fit)
    • 項目の並び順の反転
  • スクロール方向に可変長の項目サイズ
  • 項目リスト(List<IInfiniteScrollItem>)への任意の操作
  • UnityEngine.UI.ScrollRectの主要な機能

概念

  • スクロールレクト
    • 矩形のスクロール領域です。
    • 縦、または、横に項目が並び、スクロールします。
    • 可変長の項目を許容します。
  • 項目
    • 物理項目
      • スクロールレクトに縦または横に並んだ表示体です。
      • 物理項目は見える範囲にしか作られず、範囲外になった項目は不活性にされ、必要に応じて再利用されます。
      • 活性中の物理項目には、紐付けられている論理項目の内容が反映されます。
      • 一般に、プレファブとその制御クラスとして実装されます。
    • 論理項目
      • 物理項目に表示される値を保持するクラスです。
  • リスト
    • 論理項目の集合で、並び順を持ちます。
    • 配列やList(コレクション)として実装されます。

導入

  • プロジェクトにアセットパッケージInfiniteScrollRect.unitypackageを導入してください。
  • アセットの本体はAssets/InfiniteScroll/にあります。
    • フォルダを移動しても支障ありません。
    • フォルダの中は不用意に触らないようにしてください。
  • UI/Scroll Viewの代わりにUI/InfiniteScroll Viewを、UI/ScrollRectの代わりにUI/InfiniteScrollRectを使用します。
    • 必要に応じて、ScrollRectと共通の設定に加えて、PaddingSpacingChild AlignmentReverse ArrangementControl Child SizeStandard Item Sizeを設定してください。
    • Contentには何も置かないでください。
    • スクロール方向は縦/横のどちらかしか選べません。
  • インターフェイスIInfiniteScrollItemを継承したクラスを用意してください。
    • 必須のメソッドとプロパティの他に、コンストラクタなどを実装してください。
  • クラスInfiniteScrollItemComponentBaseを継承したクラスを用意してください。
    • static Create ()を置き換える(new)メソッドを実装してください。
      • このメソッドで項目の実体を生成するために、必要に応じて、クラスをアタッチしたプレファブを用意してください。
    • Initialize ()Apply ()overrideするメソッドの他に、Itemプロパティを実装してください。

使い方

  • InfiniteScrollRectコンポーネントのInitializeに、IInfiniteScrollItemを継承したクラスの配列またはリストを渡します。
    • このサンプルは、「GUI Text (Legacy)」を使用していますが、InfiniteScrollRectがそれに依存するわけではありません。
    • より具体的な使い方は、サンプルをご参照ください。
  • サンプルを確認するには、アセットパッケージInfiniteScrollSample.unitypackageを導入して、シーンAssets/Sample/Scenes/InfiniteScrollTest.unityを開いてください。
    • Assets/Sample/
      • Resouces/
        • Prefabs/
          • Item.prefab: 物理項目のプレファブ
      • Scenes/
        • InfiniteScrollTest.unity: サンプルシーン
      • Scripts/
        • InfiniteScrollTest.cs: サンプルメイン
        • Item.cs: IInfiniteScrollItemを継承した論理項目クラス
        • ItemComponent.cs: InfiniteScrollItemComponentBaseを継承した物理項目クラス

主なAPI

class InfiniteScrollRect

  • UnityEngine.UI.ScrollRectを継承したコンポーネント・クラスです。

フィールド

RectOffset padding

  • Contentと項目の間の隙間のサイズです。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

float spacing

  • 項目間の隙間のサイズです。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

TextAnchor childAlignment

  • スクロールに直行する方向の項目の整列制御です。
    • 下/左寄せ、中央寄せ、上/右寄せ
    • controlChildSizeが真の時は効果がありません。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

bool reverseArrangement

  • 項目の並び順を逆にします。
    • 偽だと、上から下、左から右になります。
    • 真だと、下から上、右から左になります。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

bool controlChildSize

  • スクロールに直行する方向の項目の拡大制御です。
    • 真だと、項目が幅いっぱいに拡大されて、childAlignmentの設定は無効になります。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

float standardItemSize

  • アイテムのスクロール方向の標準的なサイズです。
  • 未だ物理項目が作られていない場合に、仮のサイズとして使用します。
    • 実体に近いサイズに設定することで、表示のガタツキを減らせます。
  • インスペクタで設定可能な項目で、初期化の際に使われます。
    • 動的な変更後には再初期化が必要になります。

プロパティ

bool Valid

  • スクロールレクトが有効に初期化されていれば真です。

IInfiniteScrollItem this [int index]

  • 論理項目リストにアクセスするインデクサです。

int Count

  • 論理項目の数です。

int FirstIndex

  • 表示中の最初の論理項目のインデックスです。

int LastIndex

  • 表示中の最後の論理項目のインデックスです。

メソッド

void Initialize (IEnumerable<IInfiniteScrollItem> items, int index = 0)

  • 論理項目のリストitemsを渡してスクロールレクトを初期化します。
    • indexは最初に表示する項目のインデックスです。
  • インスペクタで設定可能なシリアライズ・フィールドは、あらかじめ設定しておく必要があります。
    • シリアライズフィールドの変更後には再初期化が必要になります。

ReadOnlyCollection<IInfiniteScrollItem> AsReadOnly ()

  • 内部の論理項目リストにアクセスします。

List<IInfiniteScrollItem> FindAll (Predicate<IInfiniteScrollItem> match)

  • 条件に合う論理項目を抽出したリストを返します。

List<TOutput> ConvertAll<TOutput> (Converter<IInfiniteScrollItem, TOutput> converter)

  • 論理項目を変換したリストを返します。

void Clear ()

  • 全項目を抹消します。

void Modify (Action<InfiniteScrollRect, List<IInfiniteScrollItem>, int, int> modifier)

  • 項目リストの書き換えを行います。
  • modifierには、スクロールレクト、論理項目のリスト、表示中の最初の論理項目のインデックス、表示中の最後の論理項目のインデックスが渡されます。
  • 論理項目のリストを自在に更新することができますが、表示中の項目の削除など、変更の内容によってはスクロールが生じます。

Interface IInfiniteScrollItem

  • 論理アイテムのベースとなるインターフェイスです。
  • このクラスを継承したクラスを作成して、論理項目リストList<IInfiniteScrollItem>として使用します。
    • メソッドCreate ()といくつかのプロパティは必須で、他にコンストラクタなどを実装します。

プロパティ

float Position

  • Content上に配置される際のスクロール方向のオフセットです。

float Size

  • Content上に配置される際のスクロール方向のサイズです。

bool Dirty

  • 上記のオフセットとサイズを除く論理項目の内容に変更があって、物理項目に反映する必要が生じていることを表します。
  • 物理項目側で、このフラグを監視して内容の取得と反映を行います。

メソッド

InfiniteScrollItemComponentBase Create (InfiniteScrollRect scrollRect, int index)

  • 親のスクロールレクトと論理項目リストのインデックスを受け取って、この論理項目をスクロールレクトに表示するための物理項目を生成するメソッドで、overrideが必要です。
    • 実際の生成は、物理項目のクラスに委ねて、このメソッドは受け渡しをするだけです。
  • 物理項目は、渡されたインデックスを用いて論理項目にアクセスします。

class InfiniteScrollItemComponentBase

  • 物理アイテムのベースとなる抽象クラスです。
  • このクラスを継承したクラスを作成して、物理項目のGameObjectにアタッチして使用します。
    • static Create ()の置き換え(new)は必須です。置き換えが行われていない場合は、実行時に例外を投げます。
    • 独自の情報を扱うなら、Initialize ()Apply ()overrideは必須で、他にItemプロパティの実装も必要です。

プロパティ

Item

  • リンク中の論理項目にアクセスするためのプロパティで、置き換え(new)が必要です。

メソッド

static InfiniteScrollItemComponentBase Create (InfiniteScrollRect scrollRect, int index)

  • 論理項目から呼ばれて物理項目を生成するメソッドで、置き換え(new)が必要です。
  • このメソッドは継承したクラスでの置き換えを前提としたもので、参考のために実装されています。

void Initialize ()

  • 生成直後に初期化するためのメソッドでoverrideが必要です。

void Apply ()

  • 生成直後、および、論理項目の内容に変更があった(Dirty)ときに、内容を反映するためのメソッドで、overrideが必要です。
  • 基底クラスのメソッドでDirtyfalseにされます。

おわりに

説明が解りづらい、使いにくい、これがしたい、など何でも、コメントをお寄せいただけるとありがたいです。
最後までお読みいただきありがとうございました。

Discussion