Управление версиями плиток

На устройствах Wear OS плитки визуализируются двумя ключевыми компонентами с независимым управлением версиями. Чтобы плитки приложений корректно работали на всех устройствах, важно понимать эту базовую архитектуру.

  • Библиотеки, связанные с плитками Jetpack : эти библиотеки (включая Wear Tiles и Wear ProtoLayout) встроены в ваше приложение, и вы, как разработчик, управляете их версиями. Ваше приложение использует эти библиотеки для создания объекта TileBuilder.Tile (структуры данных, представляющей вашу плитку) в ответ на системный вызов onTileRequest() .
  • Рендерер ProtoLayout: этот системный компонент отвечает за рендеринг объекта Tile на дисплее и обработку взаимодействий пользователя. Версия рендерера не контролируется разработчиком приложения и может различаться на разных устройствах, даже на устройствах с идентичным аппаратным обеспечением.

Внешний вид и поведение плитки могут различаться в зависимости от версии библиотеки Jetpack Tiles вашего приложения и версии рендерера ProtoLayout на устройстве пользователя. Например, одно устройство может поддерживать вращение или отображение данных о частоте сердечных сокращений, а другое — нет.

В этом документе объясняется, как обеспечить совместимость вашего приложения с различными версиями библиотеки Tiles и ProtoLayout Renderer, а также как перейти на более поздние версии библиотеки Jetpack.

Учитывайте совместимость

Чтобы создать плитку, корректно работающую на различных устройствах, учтите возможность поддержки различных функций. Это можно сделать двумя основными способами: определить возможности рендерера во время выполнения и предоставить встроенные резервные варианты.

Определить возможности рендеринга

Вы можете динамически изменять макет плитки в зависимости от функций, доступных на конкретном устройстве.

Определить версию рендерера

  • Используйте метод getRendererSchemaVersion() объекта DeviceParameters , переданного методу onTileRequest() . Этот метод возвращает основной и дополнительный номера версии рендерера ProtoLayout на устройстве.
  • Затем вы можете использовать условную логику в реализации onTileRequest() чтобы адаптировать дизайн или поведение вашего Tile на основе обнаруженной версии рендерера.

Аннотация @RequiresSchemaVersion

  • Аннотация @RequiresSchemaVersion в методах ProtoLayout указывает минимальную версию схемы рендерера, необходимую для того, чтобы этот метод вел себя так, как описано в документации ( пример ).
    • Хотя вызов метода, требующего более высокой версии рендерера, чем доступна на устройстве, не приведет к сбою приложения, это может привести к тому, что контент не будет отображаться или функция будет проигнорирована.

Пример определения версии

val rendererVersion = requestParams.deviceConfiguration.rendererSchemaVersion

val arcElement =
    // DashedArcLine has the annotation @RequiresSchemaVersion(major = 1, minor = 500)
    // and so is supported by renderer versions 1.500 and greater
    if (
        rendererVersion.major > 1 ||
        (rendererVersion.major == 1 && rendererVersion.minor >= 500)
    ) {
        // Use DashedArcLine if the renderer supports it …
        DashedArcLine.Builder()
            .setLength(degrees(270f))
            .setThickness(8f)
            .setLinePattern(
                LayoutElementBuilders.DashedLinePattern.Builder()
                    .setGapSize(8f)
                    .setGapInterval(10f)
                    .build()
            )
            .build()
    } else {
        // … otherwise use ArcLine.
        ArcLine.Builder().setLength(degrees(270f)).setThickness(dp(8f)).build()
    }

Предоставить запасные варианты

Некоторые ресурсы позволяют определить резервный вариант непосредственно в конструкторе. Это часто проще, чем проверять версию рендерера, и является предпочтительным подходом, если он доступен.

Распространенный вариант использования — предоставление статического изображения в качестве резервного варианта для анимации Lottie. Если устройство не поддерживает анимацию Lottie, оно отобразит статическое изображение.

val lottieImage =
    ResourceBuilders.ImageResource.Builder()
        .setAndroidLottieResourceByResId(
            ResourceBuilders.AndroidLottieResourceByResId.Builder(R.raw.lottie)
                .setStartTrigger(createOnVisibleTrigger())
                .build()
        )
        // Fallback if lottie is not supported
        .setAndroidResourceByResId(
            ResourceBuilders.AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.lottie_fallback)
                .build()
        )
        .build()

Тестирование с различными версиями рендерера

Чтобы протестировать плитки на разных версиях рендерера, разверните их на разных версиях эмулятора Wear OS. (На физических устройствах обновления рендерера ProtoLayout устанавливаются через Play Store или системные обновления. Принудительно установить определённую версию рендерера невозможно.)

Функция предварительного просмотра плиток в Android Studio использует рендерер, встроенный в библиотеку Jetpack ProtoLayout, от которой зависит ваш код, поэтому другой подход — использовать разные версии библиотеки Jetpack при тестировании плиток.

Переход на Tiles 1.5 / ProtoLayout 1.3 (Material 3 Expressive)

Обновите библиотеки плиток Jetpack, чтобы воспользоваться последними усовершенствованиями, включая изменения пользовательского интерфейса, которые позволят вашим плиткам органично интегрироваться с системой.

Jetpack Tiles 1.5 и Jetpack ProtoLayout 1.3 представляют несколько заметных улучшений и изменений. Среди них:

  • API-интерфейс в стиле Compose для описания пользовательского интерфейса.
  • Компоненты Material 3 Expressive , включая кнопку с прилегающим снизу краем и поддержку улучшенных визуальных эффектов: анимация Lottie, больше типов градиентов и новые стили дуговых линий. - Примечание: некоторые из этих функций можно использовать и без перехода на новый API.

Рекомендации

  • Переносите все плитки одновременно. Избегайте смешивания версий плиток в вашем приложении. Хотя компоненты Material 3 находятся в отдельном артефакте ( androidx.wear.protolayout:protolayout-material3 ), что технически позволяет использовать плитки M2.5 и M3 в одном приложении, мы настоятельно рекомендуем не использовать этот подход без крайней необходимости (например, если в вашем приложении много плиток, которые невозможно перенести все одновременно).
  • Воспользуйтесь рекомендациями по UX-дизайну Tiles. Учитывая высокую степень структурированности и шаблонности плиток, используйте существующие примеры дизайна в качестве отправной точки для собственных проектов.
  • Тестируйте на разных экранах и с разными размерами шрифтов. Плитки часто содержат много информации, из-за чего текст (особенно на кнопках) может переполняться и обрезаться. Чтобы минимизировать это, используйте готовые компоненты и избегайте чрезмерной настройки. Тестируйте, используя функцию предварительного просмотра плиток в Android Studio , а также на нескольких реальных устройствах.

Процесс миграции

Обновление зависимостей

Сначала обновите файл build.gradle.kts . Обновите версии и измените зависимость protolayout-material на protolayout-material3 , как показано ниже:

// In build.gradle.kts

//val tilesVersion = "1.4.1"
//val protoLayoutVersion = "1.2.1"

// Use these versions for M3.
val tilesVersion = "1.5.0-rc01"
val protoLayoutVersion = "1.3.0-rc01"

 dependencies {
     // Use to implement support for wear tiles
     implementation("androidx.wear.tiles:tiles:$tilesVersion")

     // Use to utilize standard components and layouts in your tiles
     implementation("androidx.wear.protolayout:protolayout:$protoLayoutVersion")

     // Use to utilize components and layouts with Material Design in your tiles
     // implementation("androidx.wear.protolayout:protolayout-material:$protoLayoutVersion")
     implementation("androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion")

     // Use to include dynamic expressions in your tiles
     implementation("androidx.wear.protolayout:protolayout-expression:$protoLayoutVersion")

     // Use to preview wear tiles in your own app
     debugImplementation("androidx.wear.tiles:tiles-renderer:$tilesVersion")

     // Use to fetch tiles from a tile provider in your tests
     testImplementation("androidx.wear.tiles:tiles-testing:$tilesVersion")
 }

TileService остался практически неизменным

Основные изменения в ходе этой миграции затрагивают компоненты пользовательского интерфейса. Следовательно, ваша реализация TileService , включая любые механизмы загрузки ресурсов, должна потребовать минимальных изменений или вообще не требовать их.

Основное исключение касается отслеживания активности плиток: если ваше приложение использует onTileEnterEvent() или onTileLeaveEvent() , вам следует перейти на onRecentInteractionEventsAsync() . Начиная с API 36, эти события будут объединяться в пакеты.

Адаптируйте свой код генерации макета

В ProtoLayout 1.2 (M2.5) метод onTileRequest() возвращает объект TileBuilders.Tile . Этот объект содержит различные элементы, включая TimelineBuilders.Timeline , который, в свою очередь, содержит LayoutElement , описывающий пользовательский интерфейс плитки.

В ProtoLayout 1.3 (M3), хотя общая структура данных и поток не изменились, элемент LayoutElement теперь строится с использованием подхода, вдохновлённого Compose, с макетом, основанным на определённых слотах , которые (сверху вниз) включают в себя titleSlot (необязательный; обычно для основного заголовка), mainSlot (обязательный; для основного контента) и bottomSlot (необязательный; часто для действий, таких как кнопка на краю или дополнительная информация, например, короткий текст). Этот макет создаётся функцией primaryLayout() .

Макет плитки, на котором показаны mainSlot, titleSlot, bottomSlot
Рисунок 1. : Ячейки плитки.
Сравнение функций макетов M2.5 и M3

М2.5

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters
) =
    PrimaryLayout.Builder(deviceConfiguration)
        .setResponsiveContentInsetEnabled(true)
        .setContent(
            Text.Builder(context, "Hello World!")
                .setTypography(Typography.TYPOGRAPHY_BODY1)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .build()
        )
        .build()

М3

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
) =
    materialScope(context, deviceConfiguration) {
        primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
    }

Чтобы подчеркнуть ключевые различия:

  1. Устранение конструкторов . Традиционный шаблон конструктора для компонентов пользовательского интерфейса Material3 заменяется более декларативным синтаксисом, вдохновлённым Compose. (Компоненты, не относящиеся к пользовательскому интерфейсу, такие как строки, цвета и модификаторы, также получают новые обёртки Kotlin.)
  2. Стандартизированные функции инициализации и компоновки . Макеты M3 используют стандартизированные функции инициализации и структурирования: materialScope() и primaryLayout() . Эти обязательные функции инициализируют среду M3 (темизацию, область действия компонента через materialScope ) и определяют основной макет на основе слотов (через primaryLayout ). Обе функции должны быть вызваны ровно один раз для каждого макета.

Тематика

Цвет

Отличительной особенностью Material 3 Expressive является «динамическое тематизм»: плитки, включающие эту функцию (включены по умолчанию), будут отображаться в предоставленной системой теме (доступность зависит от устройства и конфигурации пользователя).

Еще одним изменением в M3 является расширение количества цветовых токенов, которое увеличилось с 4 до 29. Новые цветовые токены можно найти в классе ColorScheme .

Типографика

Подобно M2.5, M3 в значительной степени опирается на предопределённые константы размера шрифта — прямое указание размера шрифта не рекомендуется. Эти константы находятся в классе Typography и предлагают несколько расширенный набор более выразительных возможностей.

Полную информацию см. в документации по типографике .

Форма

Большинство компонентов M3 могут различаться по форме, размеру и цвету.

textButtonmainSlot ) с full формой:

Плитка с «полной» формой (более закругленные углы)
Рисунок 2. Плитка с «полной» формой

Тот же textButton с small формой:

Плитка «маленькой» формы (менее закругленные углы)
Рисунок 3. Плитка с «маленькой» формой

Компоненты

Компоненты M3 значительно более гибкие и настраиваемые, чем их аналоги M2.5. В то время как в M2.5 часто требовались отдельные компоненты для различных визуальных эффектов, в M3 часто используется обобщенный, но легко настраиваемый «базовый» компонент с удобными настройками по умолчанию.

Этот принцип применим к «корневому» макету. В M2.5 это был либо PrimaryLayout , либо EdgeContentLayout . В M3 после установки одного MaterialScope верхнего уровня вызывается функция primaryLayout() . Она напрямую возвращает корневой макет (без необходимости в сборщиках) и принимает LayoutElements для нескольких «слотов», таких как titleSlot , mainSlot и bottomSlot . Эти слоты могут быть заполнены конкретными компонентами пользовательского интерфейса, например, возвращаемыми text() , button() или card() , или структурами макета, такими как Row или Column из LayoutElementBuilders .

Темы — ещё одно ключевое улучшение M3. По умолчанию элементы пользовательского интерфейса автоматически соответствуют спецификациям стиля M3 и поддерживают динамическую настройку тем.

М2.5 М3
Интерактивные элементы
Button или Chip
Текст
Text text()
Индикаторы прогресса
CircularProgressIndicator circularProgressIndicator() или segmentedCircularProgressIndicator()
Макет
PrimaryLayout или EdgeContentLayout primaryLayout()
buttonGroup()
Изображения
icon() , avatarImage() или backgroundImage()

Модификаторы

В M3 Modifiers , которые используются для оформления или дополнения компонента, больше похожи на Compose. Это изменение позволяет сократить объём шаблонного кода за счёт автоматического создания соответствующих внутренних типов. (Это изменение ортогонально использованию компонентов пользовательского интерфейса M3; при необходимости можно использовать модификаторы в стиле конструктора из ProtoLayout 1.2 с компонентами пользовательского интерфейса M3, и наоборот.)

М2.5

// A Builder-style modifier to set the opacity of an element to 0.5
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

М3

// The equivalent Compose-like modifier is much simpler
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

Вы можете создавать модификаторы, используя любой из стилей API, а также можете использовать функцию расширения toProtoLayoutModifiers() для преобразования LayoutModifier в ModifiersBuilders.Modifier .

Вспомогательные функции

Хотя ProtoLayout 1.3 позволяет реализовать многие компоненты пользовательского интерфейса с помощью API, вдохновлённого Compose, базовые элементы макета, такие как строки и столбцы из LayoutElementBuilders по-прежнему используют шаблон конструктора. Чтобы преодолеть этот стилистический пробел и обеспечить согласованность с новыми API компонентов M3, рассмотрите возможность использования вспомогательных функций.

Без помощников

primaryLayout(
    mainSlot = {
        LayoutElementBuilders.Column.Builder()
            .setWidth(expand())
            .setHeight(expand())
            .addContent(text("A".layoutString))
            .addContent(text("B".layoutString))
            .addContent(text("C".layoutString))
            .build()
    }
)

С помощниками

// Function literal with receiver helper function
fun column(builder: Column.Builder.() -> Unit) =
    Column.Builder().apply(builder).build()

primaryLayout(
    mainSlot = {
        column {
            setWidth(expand())
            setHeight(expand())
            addContent(text("A".layoutString))
            addContent(text("B".layoutString))
            addContent(text("C".layoutString))
        }
    }
)

Переход на Tiles 1.2 / ProtoLayout 1.0

Начиная с версии 1.2, большинство API для раскладки плиток находятся в пространстве имён androidx.wear.protolayout . Чтобы использовать последние версии API, выполните следующие шаги по миграции в своём коде.

Обновление зависимостей

В файле сборки модуля вашего приложения внесите следующие изменения:

Круто

  // Remove
  implementation 'androidx.wear.tiles:tiles-material:version'

  // Include additional dependencies
  implementation "androidx.wear.protolayout:protolayout:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-material:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-expression:1.3.0"

  // Update
  implementation "androidx.wear.tiles:tiles:1.5.0"

Котлин

  // Remove
  implementation("androidx.wear.tiles:tiles-material:version")

  // Include additional dependencies
  implementation("androidx.wear.protolayout:protolayout:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-material:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-expression:1.3.0")

  // Update
  implementation("androidx.wear.tiles:tiles:1.5.0")

Обновление пространств имен

Внесите следующие изменения в файлы кода вашего приложения на Kotlin и Java. Также можно выполнить этот скрипт переименования пространства имён .

  1. Замените все импортируемые файлы androidx.wear.tiles.material.* на androidx.wear.protolayout.material.* . Выполните этот шаг также для библиотеки androidx.wear.tiles.material.layouts .
  2. Замените большинство других импортов androidx.wear.tiles.* на androidx.wear.protolayout.* .

    Импорт для androidx.wear.tiles.EventBuilders , androidx.wear.tiles.RequestBuilders , androidx.wear.tiles.TileBuilders и androidx.wear.tiles.TileService должен остаться прежним.

  3. Переименуйте несколько устаревших методов из классов TileService и TileBuilder:

    1. TileBuilders : getTimeline() для getTileTimeline() и setTimeline() для setTileTimeline()
    2. TileService : onResourcesRequest() в onTileResourcesRequest()
    3. RequestBuilders.TileRequest : getDeviceParameters() для getDeviceConfiguration() , setDeviceParameters() для setDeviceConfiguration() , getState() для getCurrentState() и setState() для setCurrentState()