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

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

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

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

В этом документе объясняется, как обеспечить совместимость вашего приложения с различными версиями библиотеки Tiles и рендерера ProtoLayout. Также объясняется, как перейти на более новые версии библиотеки 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 для работы с плитками. Учитывая высокоструктурированный и шаблонный характер плиток, используйте существующие примеры дизайна в качестве отправной точки для собственных разработок.
  • Проведите тестирование на экранах разных размеров и с разными размерами шрифтов. Плитки часто содержат много информации, поэтому текст (особенно на кнопках) подвержен переполнению и обрезке. Чтобы свести это к минимуму, используйте готовые компоненты и избегайте сложной настройки. Протестируйте с помощью функции предварительного просмотра плиток в 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"
val protoLayoutVersion = "1.3.0"

 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 , описывающий пользовательский интерфейс плитки.

With ProtoLayout 1.3 (M3), while the overall data structure and flow have not changed, the LayoutElement is now constructed using a Compose-inspired approach with a layout based on defined slots which are (from top to bottom) the titleSlot (optional; typically for a primary title or header), mainSlot (mandatory; for the core content), and bottomSlot (optional; often for actions like an edge button or supplemental information like short text). This layout is constructed by the primaryLayout() function.

Расположение элементов на плитке: 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)
                .build()
        )
        .build()

М3

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

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

  1. Устранение построителей . Предыдущий шаблон проектирования «построитель» для компонентов Material UI заменен более декларативным синтаксисом, вдохновленным Compose. (Не-UI компоненты, такие как String/Color/Modifiers, также получают новые обертки Kotlin.)
  2. Стандартизированные функции инициализации и компоновки . Компоновки M3 используют стандартизированные функции инициализации и структуры: materialScope() и primaryLayout() . Эти обязательные функции инициализируют среду M3 (тематику, область видимости компонентов с помощью materialScope ) и определяют основную компоновку на основе слотов (с помощью primaryLayout ). Обе функции должны быть вызваны ровно один раз для каждой компоновки.

Тематическое оформление

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

Цвет

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

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

Типография

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

Для получения более подробной информации обратитесь к документации по типографике .

Форма

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

textButtonmainSlot ) с формой full :

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

Та же самая текстовая кнопка в 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; при необходимости вы можете использовать модификаторы в стиле Builder из ProtoLayout 1.2 с компонентами пользовательского интерфейса M3, и наоборот.)

М2.5

// Uses Builder-style modifier to set opacity
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

М3

// Uses Compose-like modifiers to set opacity
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

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

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

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

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

primaryLayout(
    mainSlot = {
        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 для работы с макетами Tiles находятся в пространстве имен androidx.wear.protolayout . Чтобы использовать новейшие API, выполните следующие шаги миграции в своем коде.

Обновить зависимости

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

Классный

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

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

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

Котлин

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

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

  // Update
  implementation("androidx.wear.tiles:tiles:1.6.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() to onTileResourcesRequest()
    3. RequestBuilders.TileRequest : getDeviceParameters() to getDeviceConfiguration() , setDeviceParameters() to setDeviceConfiguration() , getState() to getCurrentState() , and setState() to setCurrentState()