Привязать к списку без ListView
Версия: 2023.2+
Вы можете привязать к списку без ListView. Чтобы сделать это, привязайте каждый элемент к элементу в массиве серийного объекта и отслеживайте значение размера массива. Размер массива может изменяться в некоторых ситуациях, таких как отмена или сброс операции.
Этот пример показывает, как привязать к списку без ListView.
Примерный обзор
Этот пример создает список TexturePreviewElements и связывает этот список с базовым списком объектов Texture2D.
Вы можете найти завершенные файлы, созданные этим примером, в хранилище GitHub.
Предварительные условия
Это руководство предназначено для разработчиков, знакомых со скриптами Unity Editor, UI Toolkit и C#. Перед началом работы ознакомьтесь со следующим:
Создание объекта, содержащего список
Создайте класс C#, содержащий список. Именно этот список будет целью привязки.
- Создайте проект в Unity с любым шаблоном.
- В окне Project создайте папку
bind-to-list-without-ListViewдля хранения всех ваших файлов. - Создайте скрипт C# с именем
TexturePackAsset.csи замените его содержимое следующим:
using System.Collections.Generic;
using UnityEngine;
namespace UIToolkitExamples
{
[CreateAssetMenu(menuName = "UIToolkitExamples/TexturePackAsset")]
public class TexturePackAsset : ScriptableObject
{
public List<Texture2D> textures;
public void Reset()
{
textures = new() { null, null, null, null };
}
}
}
Создание и настройка стиля пользовательского элемента управления
Создайте пользовательский контроллер с C#, который представляет собой ссылку на текстуру 2D, и стилизируйте его с USS.
- В этой папке создайте папку с именем
Editor. - В течение Редактор папка, создать C# скрипт назван
TexturePreviewElement.cs. - Заменить содержимое
TexturePreviewElement.csследующим текстом:
using System;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace UIToolkitExamples
{
[UxmlElement]
public partial class TexturePreviewElement : BindableElement, INotifyValueChanged<Object>
{
public static readonly string ussClassName = "texture-preview-element";
Image m_Preview;
ObjectField m_ObjectField;
Texture2D m_Value;
public TexturePreviewElement()
{
AddToClassList(ussClassName);
// Create a preview image.
m_Preview = new Image();
Add(m_Preview);
// Create an ObjectField, set its object type, and register a callback when its value changes.
m_ObjectField = new ObjectField();
m_ObjectField.objectType = typeof(Texture2D);
m_ObjectField.RegisterValueChangedCallback(OnObjectFieldValueChanged);
Add(m_ObjectField);
styleSheets.Add(Resources.Load<StyleSheet>("texture_preview_element"));
}
void OnObjectFieldValueChanged(ChangeEvent<Object> evt)
{
value = evt.newValue;
}
public void SetValueWithoutNotify(Object newValue)
{
if (newValue == null || newValue is Texture2D)
{
// Update the preview Image and update the ObjectField.
m_Value = newValue as Texture2D;
m_Preview.image = m_Value;
// Notice that this line calls the ObjectField's SetValueWithoutNotify() method instead of just setting
// m_ObjectField.value. This is very important; you don't want m_ObjectField to send a ChangeEvent.
m_ObjectField.SetValueWithoutNotify(m_Value);
}
else throw new ArgumentException($"Expected object of type {typeof(Texture2D)}");
}
public Object value
{
get => m_Value;
// The setter is called when the user changes the value of the ObjectField, which calls
// OnObjectFieldValueChanged(), which calls this.
set
{
if (value == this.value)
return;
var previous = this.value;
SetValueWithoutNotify(value);
using (var evt = ChangeEvent<Object>.GetPooled(previous, value))
{
evt.target = this;
SendEvent(evt);
}
}
}
}
}
- В течение Редактор папка, создать папку с названием
Resources. - В течение Ресурсы папка, создать StyleSheet названный
texture_preview_element.ussи заменить его содержание следующим:
.texture-preview-element {
width: 200px;
height: 200px;
}
.texture-preview-element > .unity-image {
flex-grow: 1;
}
Создание пользовательского редактора и установка привязки
Создайте пользовательский редактор с помощью скрипта C#, который создает ассет.
Чтобы изменить размер списка текстур при изменении числа TexturePreviewElements в UI, вызовите метод SetupList() и пройдите по списку записей в сериализированном списке.
Чтобы привязать каждый TexturePreviewElement к списку текстур, вызовите BindProperty() с именем свойства TexturePackAsset.textures.
- В течение Редактор папка, создать C# скрипт назван
TexturePackEditor.csи заменить его содержание следующим:
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UIToolkitExamples
{
[CustomEditor(typeof(TexturePackAsset))]
public class TexturePackEditor : Editor
{
[SerializeField]
VisualTreeAsset m_VisualTreeAsset;
public override VisualElement CreateInspectorGUI()
{
var editor = m_VisualTreeAsset.CloneTree();
var container = editor.Q(className: "preview-container");
SetupList(container);
// Watch the array size to handle the list being changed
var propertyForSize = serializedObject.FindProperty(nameof(TexturePackAsset.textures) + ".Array");
propertyForSize.Next(true); // Expand to obtain array size
editor.TrackPropertyValue(propertyForSize, prop => SetupList(container));
editor.Q<Button>("add-button").RegisterCallback<ClickEvent>(OnClick);
return editor;
}
void SetupList(VisualElement container)
{
var property = serializedObject.FindProperty(nameof(TexturePackAsset.textures) + ".Array");
var endProperty = property.GetEndProperty();
property.NextVisible(true); // Expand the first child.
var childIndex = 0;
// Iterate each property under the array and populate the container with preview elements
do
{
// Stop if we've reached the end of the array
if (SerializedProperty.EqualContents(property, endProperty))
break;
// Skip the array size property
if (property.propertyType == SerializedPropertyType.ArraySize)
continue;
TexturePreviewElement element;
// Find an existing element or create one
if (childIndex < container.childCount)
{
element = (TexturePreviewElement)container[childIndex];
}
else
{
element = new TexturePreviewElement();
container.Add(element);
}
element.BindProperty(property);
++childIndex;
}
while (property.NextVisible(false)); // Never expand children.
// Remove excess elements if the array is now smaller
while (childIndex < container.childCount)
{
container.RemoveAt(container.childCount - 1);
}
}
void OnClick(ClickEvent evt)
{
var property = serializedObject.FindProperty(nameof(TexturePackAsset.textures));
property.arraySize += 1;
serializedObject.ApplyModifiedProperties();
}
}
}
- Создайте документ UI с названием
texture_pack_editor.uxmlи замените его содержимое следующим:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns="UnityEngine.UIElements" example="UIToolkitExamples" editor-extension-mode="True">
<ui:ScrollView>
<ui:VisualElement class="preview-container" style="flex-wrap: wrap; flex-direction: row; justify-content: space-around;" />
</ui:ScrollView>
<ui:Button name="add-button" text="Add" />
</ui:UXML>
- В окне Project выберите TexturePackEditor.cs.
- Перетащите texture_pack_editor.uxml в Визуальное дерево актива в Inspector.
Проверка привязки
- В меню выберите Ассеты > Создать > UIToolkitExamples > TexturePackAsset. Это создаст ассет с именем New Texture Pack Asset.
- В окне Project выберите New Texture Pack Asset. Это отобразит четыре элемента TexturePreviewElement в Inspector.
- Назначьте 2D изображения этим элементам или используйте кнопку Добавить для добавления новых элементов. Если вы измените Inspector UI, свойство объекта
TexturePackAsset.texturesизменится.
Тип: Чтобы импортировать некоторые текстуры и назначить их различным записям в списке, попробуйте это бесплатно Плагин магазина ассетов Playground.