Анимация плиток

Плитки поддерживают несколько типов анимации, в том числе:

Как добавить переход "Панорама"

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

private var startValue = 15f
private var endValue = 105f
private val animationDurationInMillis = 2000L // 2 seconds

override fun onTileRequest(requestParams: RequestBuilders.TileRequest): ListenableFuture<Tile> {
    val circularProgressIndicator =
        CircularProgressIndicator.Builder()
            .setProgress(
                FloatProp.Builder(/* static value */ 0.25f)
                    .setDynamicValue(
                        // Or you can use some other dynamic object, for example
                        // from the platform and then at the end of expression
                        // add animate().
                        DynamicFloat.animate(
                            startValue,
                            endValue,
                            AnimationSpec.Builder()
                                .setAnimationParameters(
                                    AnimationParameters.Builder()
                                        .setDurationMillis(animationDurationInMillis)
                                        .build()
                                )
                                .build(),
                        )
                    )
                    .build()
            )
            .build()

    return Futures.immediateFuture(
        Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(Timeline.fromLayoutElement(circularProgressIndicator))
            .build()
    )
}

Как задать направление дуги

Если на вашей плитке есть дуга, вы можете не захотеть, чтобы линия дуги или текст всегда увеличивались в направлении текста по умолчанию для выбранного пользователем языка. Чтобы задать направление роста дуги, используйте API ArcDirection:

public override fun onTileRequest(
    requestParams: RequestBuilders.TileRequest
): ListenableFuture<Tile> {
    return Futures.immediateFuture(
        Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    EdgeContentLayout.Builder(deviceParameters)
                        .setResponsiveContentInsetEnabled(true)
                        .setEdgeContent(
                            Arc.Builder()
                                // Arc should always grow clockwise.
                                .setArcDirection(LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE)
                                .addContent(
                                    ArcLine.Builder()
                                        // Set color, length, thickness, and more.
                                        // Arc should always grow clockwise.
                                        .setArcDirection(
                                            LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE
                                        )
                                        .build()
                                )
                                .build()
                        )
                        .build()
                )
            )
            .build()
    )
}

Как добавить плавный переход

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

Если макет плитки содержит элемент, значение которого меняется, плитка показывает анимацию выхода элемента, затем обновляет макет и показывает анимацию входа элемента.

Переходы с затуханием

В приведенном ниже фрагменте кода показано, как выполнить переход с эффектом появления и исчезновения с помощью вспомогательных методов из DefaultContentTransitions. Чтобы задать специальные объекты FadeInTransition и FadeOutTransition, вызовите методы setFadeIn() и setFadeOut() соответственно в методах установки перехода.

public override fun onTileRequest(
    requestParams: RequestBuilders.TileRequest
): ListenableFuture<Tile> {
    // Assumes that you've defined a custom helper method called
    // getTileTextToShow().
    val tileText = getTileTextToShow()
    return Futures.immediateFuture(
        Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    Text.Builder(this, tileText)
                        .setModifiers(
                            Modifiers.Builder()
                                .setContentUpdateAnimation(
                                    AnimatedVisibility.Builder()
                                        .setEnterTransition(DefaultContentTransitions.fadeIn())
                                        .setExitTransition(DefaultContentTransitions.fadeOut())
                                        .build()
                                )
                                .build()
                        )
                        .build()
                )
            )
            .build()
    )
}

Переходы между слайдами

В этом фрагменте кода показано, как реализовать переходы с помощью вспомогательных методов из DefaultContentTransitions. Вы также можете определить собственные объекты SlideInTransition и SlideOutTransition, вызвав setSlideIn() и setSlideOut() соответственно в методах установки перехода.

public override fun onTileRequest(
    requestParams: RequestBuilders.TileRequest
): ListenableFuture<Tile> {
    // Assumes that you've defined a custom helper method called
    // getTileTextToShow().
    val tileText = getTileTextToShow()
    return Futures.immediateFuture(
        Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    Text.Builder(this, tileText)
                        .setModifiers(
                            Modifiers.Builder()
                                .setContentUpdateAnimation(
                                    AnimatedVisibility.Builder()
                                        .setEnterTransition(
                                            DefaultContentTransitions.slideIn(
                                                ModifiersBuilders.SLIDE_DIRECTION_LEFT_TO_RIGHT
                                            )
                                        )
                                        .setExitTransition(
                                            DefaultContentTransitions.slideOut(
                                                ModifiersBuilders.SLIDE_DIRECTION_LEFT_TO_RIGHT
                                            )
                                        )
                                        .build()
                                )
                                .build()
                        )
                        .build()
                )
            )
            .build()
    )
}

Как посмотреть преобразование

Чтобы привлечь внимание к определенному элементу или области на фрагменте, можно применить к нему несколько типов преобразований, в том числе поворот, масштабирование и перенос.

Многие значения с плавающей запятой, связанные с преобразованиями, принимают динамические выражения, которые позволяют анимировать эти преобразования.

Вращение

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

return Futures.immediateFuture(
    Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTileTimeline(
            Timeline.fromLayoutElement(
                Text.Builder(this, someTileText)
                    .setModifiers(
                        Modifiers.Builder()
                            .setTransformation(
                                ModifiersBuilders.Transformation.Builder()
                                    // Set the pivot point 50 dp from the left edge
                                    // and 100 dp from the top edge of the screen.
                                    .setPivotX(dp(50f))
                                    .setPivotY(dp(100f))
                                    // Rotate the element 45 degrees clockwise.
                                    .setRotation(degrees(45f))
                                    .build()
                            )
                            .build()
                    )
                    .build()
            )
        )
        .build()
)

Масштабирование

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

return Futures.immediateFuture(
    Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTileTimeline(
            Timeline.fromLayoutElement(
                Text.Builder(this, someTileText)
                    .setModifiers(
                        Modifiers.Builder()
                            .setTransformation(
                                ModifiersBuilders.Transformation.Builder()
                                    // Set the pivot point 50 dp from the left edge
                                    // and 100 dp from the top edge of the screen.
                                    .setPivotX(dp(50f))
                                    .setPivotY(dp(100f))
                                    // Shrink the element by a scale factor
                                    // of 0.5 horizontally and 0.75 vertically.
                                    .setScaleX(FloatProp.Builder(0.5f).build())
                                    .setScaleY(FloatProp.Builder(0.75f).build())
                                    .build()
                            )
                            .build()
                    )
                    .build()
            )
        )
        .build()
)

Геометрический перевод

Чтобы переместить элемент на определенное количество пикселей плотности (dp) по горизонтали или вертикали, используйте код, похожий на следующий:

return Futures.immediateFuture(
    Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTileTimeline(
            Timeline.fromLayoutElement(
                Text.Builder(this, someTileText)
                    .setModifiers(
                        Modifiers.Builder()
                            .setTransformation(
                                ModifiersBuilders.Transformation.Builder()
                                    // Translate (move) the element 60 dp to the right
                                    // and 80 dp down.
                                    .setTranslationX(dp(60f))
                                    .setTranslationY(dp(80f))
                                    .build()
                            )
                            .build()
                    )
                    .build()
            )
        )
        .build()
)

Анимации Lottie

Мозаика поддерживает воспроизведение анимаций Lottie с использованием синтаксиса, похожего на синтаксис изображений:

class LottieAnimation : TileService() {

    val lottieResourceId = "lottie_animation"

    override fun onTileRequest(requestParams: RequestBuilders.TileRequest): ListenableFuture<Tile> {

        val layout =
            LayoutElementBuilders.Image.Builder()
                .setWidth(dp(150f))
                .setHeight(dp(150f))
                .setResourceId(lottieResourceId)
                .build()

        return Futures.immediateFuture(
            Tile.Builder()
                .setResourcesVersion(RESOURCES_VERSION)
                .setTileTimeline(Timeline.fromLayoutElement(layout))
                .build()
        )
    }

    override fun onTileResourcesRequest(
        requestParams: ResourcesRequest
    ): ListenableFuture<Resources> {

        val lottieImage =
            ResourceBuilders.ImageResource.Builder()
                .setAndroidLottieResourceByResId(
                    ResourceBuilders.AndroidLottieResourceByResId.Builder(R.raw.lottie)
                        .setStartTrigger(createOnVisibleTrigger())
                        .build()
                )
                .build()

        return Futures.immediateFuture(
            Resources.Builder()
                .setVersion(requestParams.version)
                .addIdToImageMapping(lottieResourceId, lottieImage)
                .build()
        )
    }
}

Обратите внимание на следующее:

  • Поддерживаются не все файлы Lottie. Проверьте совместимость с помощью одного из следующих валидаторов:
    • Онлайн-валидатор: https://skottie.skia.org/. В разделе "Отчет о совместимости" файл должен пройти проверки на наличие ошибок в спецификации, предупреждений о спецификации (без учета общих свойств) и ошибок в профиле низкого энергопотребления.
    • Библиотека для проверки файлов Lottie на языке Rust: https://github.com/google/lottie-tools.
  • Воспроизведение Lottie поддерживается рендерами плиток с основной версией не ниже 1 и промежуточной версией не ниже 500. Если анимация не поддерживается, она не будет показываться, но остальная часть элемента будет отображаться корректно. При необходимости вы можете добавить резервный вариант, например статическое изображение.

Не показывайте важную информацию в середине анимации

Анимация отключается в следующих случаях:

  • Система может отключить анимацию для всех элементов.
  • В одном фрагменте можно анимировать не более четырех элементов. Если вы попытаетесь анимировать более четырех элементов одновременно, анимация будет показана не для всех из них.

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