Анимированные переходы в Android

При программировании под Android можно использовать уже ряд готовых анимаций переходов между экранами. Но что если дизайнер придумал более изощренные переходы? Рассмотрим, какие есть возможности реализовать нестандартные смены экранов.

Активити и фрагменты

От того, используются ли переходы между Activity или все происходит в рамках разных фрагментов, зависит и то, какие инструменты предлагает система для переходов между экранами.

Приложение с несколькими Activity

Если вы работаете с несколькими Activity, то можно использовать простые анимации переходов или создать красивые эффекты с shared element.

Простой переход между Activity

Можно использовать либо готовые переходы, которые предоставляет Android, либо определить свой. Например, такой:

slide_in.xml

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromXDelta="100%p"
    android:toXDelta="0%p"/>

Можно либо переопределить переходы для всех Activity в теме приложения:

<!--Base application theme-->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorPrimary</item>
    <item name="android:windowAnimationStyle">@style/CustomActivityAnimation</item>
    ...
</style>

<style name="CustomActivityAnimation" parent="@android:style/Animation.Activity">
    <item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
    <item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
    <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
    <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
    ...
</style>

Либо же переопределить переход в рамках отдельной Activity, вызвав

overridePendingTransition(R.anim.slide_in, R.anim.slide_out)

в onCreate вызываемой Activity.

Shared Element Transition

Суть shared element transition в том, что общий элемент перетекает между двумя экранами, создавая видимость целостности двух Activity между собой.

Берете View с одного экрана. Говорите системе, что view с тем же id есть на следующем. Запускаете переход.

// получаем элемент, который получает событие нажатия
val imgContainerView = findViewById<View>(R.id.img_container)

// получаем общий элемент для перехода в этой activity
val androidRobotView = findViewById<View>(R.id.image_small)

// добавляем слушателя нажатия
imgContainerView.setOnClickListener {
    val intent = Intent(this, Activity2::class.java)

    // создаем анимацию перехода - у imageView в разметках
    // обеих activity указано android:transitionName="robot"
    val options = ActivityOptions
            .makeSceneTransitionAnimation(this, androidRobotView, "robot")

    // запускаем новую activity
    startActivity(intent, options.toBundle())
}

Остальное Android OS все сделает за вас.

Single activity application

Суть single activity application заключена в его названии. Ваше приложение будет состоять из одной активности, а экраны внутри будут реализованы как фрагменты. Такой подход придает гибкость и контролируемость переходам. В рамках View можно делать абсолютно что угодно.

Вы можете управлять любой View, применять любые transition и т.д. С Activity такое не прокатит, потому что слушать или влиять на скорость отработки закрытия и открытия активности напрямую не получится.

Архитектура переходов между фрагментами одной activity

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

interface AnimatedTransitionFragment {
    fun exit(): Single<Boolean>
}

Не обязательно использовать Single из RxJava, можно установить любой тип callback. Метод exit() должен вернуть нечто, что можно “слушать” на предмет исполнения. Fragment, который должен поддерживать “выход”, будет реализовывать интерфейс, приведенный выше.

Далее необходимо при переходе между фрагментами вызвать exit(). Подождать, когда Fragment исполнит необходимую анимацию, а затем перейти на новый Fragment, который может запустить анимацию “старта”.

Если вы используете Cicerone, то можно переопределить свой AnimatedSupportAppNavigator:

override fun applyCommands(commands: Array<out Command>?) {
    val topFragment = _fragmentManager?.fragments?.lastOrNull()
    val command = commands?.let {
         if (it.isNotEmpty()) it[it.size - 1] else null
    }
    val frag = topFragment as? AnimatedTransitionFragment
    if (frag != null) {
        if (shouldAnimatedTransition) {
            frag.exit().subscribe {
                super.applyCommands(commands)
            }
        } else {
            super.applyCommands(commands)
        }
    } else {
        super.applyCommands(commands)
    }
}

Происходит все то же самое, что и в Cicerone, только теперь, если Fragment поддерживает анимацию выхода, сначала проиграется она.

Анимация выхода из Fragment

Сам Fragment, реализуя exit(), проигрывает анимацию выхода и отдает управление дальше.

override fun exit(): Single<Boolean> {
    exitAnimResult = SingleSubject.create()
    val views = getViewsToAnimate().filterNotNull()
    //Crashlytics не согласится с вами...
    AdditiveAnimator()
        .setDuration(ANIMATION_DURATION)
        .targets(views)
        .rotationY(ROTATION_FACTOR)
        .scaleX(SCALEX_FACTOR)
        .scaleY(SCALEY_FACTOR)
        .setInterpolator(AccelerateInterpolator(2f))
        .addListener(object : SimpleAnimatorListener() {
            override fun onAnimationEnd(animation: Animator?) {
                exitAnimResult?.onSuccess(true)
                exitAnimResult = null
            }
        })
        .start()
    return exitAnimResult!!
}

Для простоты используется AdditiveAnimation. Он позволяет легко управлять анимацией разных элементов и слушать окончание проигрывания.

Анимация входа во Fragment

При необходимости в onViewCreated можно запустить анимацию “входа”.

private fun entryScreenAnimation() {
    var shift = 500f
    for (view in listOf(tvTitle, etFirstName, etLastName, btnNext)) {
        view.alpha = 0f
        view.translationY = shift
        shift += 100f
    }
    AdditiveAnimator()
        .setDuration(600)
        .targets(tvTitle, etFirstName, etLastName, btnNext)
        .translationY(0f)
        .alpha(1f)
        .start()
}

Это лишь база, необходимая для того, чтобы понимать, как можно осуществлять навигацию между Activity и Fragment. Более сложные примеры зависят уже от конкретной задачи и прихоти дизайнера. Имея доступ к анимации на уровне View, можно сделать любую мыслимую и не очень анимацию. Например, можно запустить полноэкранную Lottie анимацию, сделанную в Adobe After Effects.

Источник: Анимированные переходы в Android