2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】List Viewの中に複雑なUIを構築する(VisualElement.userData)【UI Toolkit】

Last updated at Posted at 2024-08-31

自己紹介

サマーインターンでウハウハな大学生

List View

UI ToolkitにはList ViewというVisual Elementが存在します。
基本的に、なにかの一覧を並べるものなのですが、少しだけ工夫しないとその一覧から選択するなど複雑なUIを正しく構築するのが難しいです。今回はそのTipsを紹介します。

List Viewについて

List Viewの詳細は下記の記事が良くまとまっているので、ご覧ください。

Toggle in List View

以下のようなUIがあったとしましょう。

image.png

ストレートに考えると、イベント発火させたいときは以下のようなコードを書くと思います。

間違ったコード
// なにかしらのList Viewの初期化処理

listView.makeItem += () => new Toggle();

listView.bindItem += (element, index) =>
{
    var toggle = (Toggle)element;
    toggle.text = strings[index];
    toggle.style.fontSize = slider.value;

    toggle.RegisterValueChangedCallback(e =>
    {
        Debug.Log(index);
    });
};

しかし、bindItemの発火タイミングを考えると、これではバグが生まれます。

bindItemの発火タイミング

List Viewは画面に表示される要素だけをmakeItemで生成し、そのVisual Elementを使いまわす形で、画面に表示していきます。これによって、パフォーマンスの最適化がされているのです。

bindItemはスクロールしていったときに要素が切り替わるときに呼び出されます。
そのため、要素が切り替わるタイミングでRegisterValueChangeCallbackRegisterなんちゃら全部)を動かしてしまうと、2重で登録されてしまうのです。
これによって、正しく発火しなかったり、使いまわす過程で表示が崩れたりします。

indexがない問題

しかし、makeItemではindexが取得できません。これはVisualElement.userDataというプロパティを使うことで解決できます。このプロパティはobject型です。つまり、なんでも入れられます。
makeItemでイベントを登録し、bindItemuserDataにindexを入れることで更新するという処理を行うことが良いでしょう。

正しいコード?
// なにかしらのList Viewの初期化処理

listView.makeItem += () =>
{
    var toggle = new Toggle();

    toggle.RegisterValueChangedCallback(e =>
    {
        var index = (int)toggle.userData;
        
        Debug.Log(index);
    });
    return toggle;
};

listView.bindItem += (element, index) =>
{
    var toggle = (Toggle)element;
    toggle.text = strings[index];
    toggle.style.fontSize = slider.value;

    toggle.userData = index;
};

まだ終わらない、バグ

このコードを実行すると正しくイベントは発火されるはずです。
しかし、スクロールしていくと選択してもいないTogglevaluetrueになる問題が発生します。
これはVisualElementを使いまわすからですね。

純粋に、状態を保存する配列やListを生成してあげるのが良いでしょう。

正しいコード?
// なにかしらのList Viewの初期化処理
// なにかしらの状態を保存する配列などの初期化処理

listView.makeItem += () =>
{
    var toggle = new Toggle();

    toggle.RegisterValueChangedCallback(e =>
    {
        var index = (int)toggle.userData;
        _listState[index] = e.newValue;
        Debug.Log(index);
    });
    return toggle;
};

listView.bindItem += (element, index) =>
{
    var toggle = (Toggle)element;
    toggle.text = strings[index];
    toggle.style.fontSize = slider.value;
    
    toggle.SetValueWithoutNotify(_listState[index]);

    toggle.userData = index;
};

おわり

癖がありますが、まぁ言われてみたらそうだよなって感じの内容でした。
ではでは、またお会いしましょう。いずれ、どこかで。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?