andkulikov / Transitions-Everywhere

Set of extra Transitions on top of Jetpack Transitions Library
Apache License 2.0
4.82k stars 487 forks source link

Custom transitions #93

Open GVFiQst opened 5 years ago

GVFiQst commented 5 years ago

Привет! На одном из моих проектов мне надо было анимировать смену TextView::textSize и я написал вот такой Transition. Может еще кому-то понадобится и поэтому решил создать pull request с ним (Он был написан на котлине. Но для ПР я его переписал на java)

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ViewGroup
import android.widget.TextView
import androidx.transition.Transition
import androidx.transition.TransitionValues

class ChangeTextSize : Transition {

    constructor() : super()
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun captureStartValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun captureEndValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    private fun captureValues(transitionValues: TransitionValues) {
        if (transitionValues.view is TextView) {
            transitionValues.values[PROP_NAME] = (transitionValues.view as TextView).textSize
        }
    }

    override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
        if (startValues == null || endValues == null || startValues.view !is TextView || endValues.view !is TextView)
            return null

        val start = startValues.values[PROP_NAME] as? Float ?: return null
        val end = endValues.values[PROP_NAME] as? Float ?: return null
        if (start == end) return null

        val view = endValues.view as TextView

        return ValueAnimator.ofFloat(start, end)
                .apply { addUpdateListener { view.setTextSize(TypedValue.COMPLEX_UNIT_PX, it.animatedValue as Float) } }
    }

    companion object {
        private const val PROP_NAME = "ChangeTextSize.textSize"
    }
}

Неплохая идея собрать библиотеку с разными кастомными Transition! Жду Вашего фид-бэка!

andkulikov commented 5 years ago

Привет! Спасибо за желание поделиться своим кастомным транзишном!

К сожалению конкретно про этот у меня есть сомнения и я не хотел бы иметь его частью библиотеки. Причина простая: не стоит анимировать вызов setTextSize, потому что он в том числе изменяет размер вьюхи и влечет за собой вызов requestLayout и весь парент лейаут и все следующие зависящие от его размера лейауты будут обязаны пройти через measure/layout цикл на каждом фрейме анимации и это не может работать оптимально и давать плавную анимацию. На быстрых флагманах это может быть незаментно и если конкретно под требования вашего проекта такая реализация подходит то все равно не стоит её продвигать как рекомендованную. Как бы я заимплементил подобное: создал бы кастомную вьюху где рисовал нужный текст прям на канвасе и на каждый шаг анимации только вызывал invalidate, где в onDraw применял новый размер, но это не меняло бы размер View.

Еще раз спасибо!

GVFiQst commented 5 years ago

Привет еще раз! Не хотел создавать отдельный ишю ведь тема та же. Я написал еще один простенький транзишн.

Как я писал вверху идея собрать либку с кастомными транзишнами очень хороша. Таким образом, если каждый хто писал транзишины кинет свои кастомные версии, другие разработчики не будут тратить время, а будуть иметь большой выбор транзишнов под рукой.

Вот он:

import android.animation.Animator
import android.animation.ValueAnimator
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.transition.Transition
import androidx.transition.TransitionValues

class ChangeProgress : Transition() {

    override fun captureStartValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun captureEndValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    fun captureValues(values: TransitionValues) {
        (values.view as? ProgressBar)?.apply {
            values.values[PROP_NAME] = progress
        }
    }

    override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
        if (startValues == null || endValues == null || startValues.view !is ProgressBar || endValues.view !is ProgressBar)
            return null

        val start = startValues.values[PROP_NAME] as? Int ?: return null
        val end = endValues.values[PROP_NAME] as? Int ?: return null
        if (start == end) return null

        val view = endValues.view as ProgressBar

        return ValueAnimator.ofInt(start, end)
                .apply { addUpdateListener { view.progress = it.animatedValue as Int } }
    }

    companion object {
        const val PROP_NAME = "android:progress:progress"
    }
}

Создавать пулл реквест не тороплюсь, а буду ждать от Вас фидбека!

P.S. Я знаю о том что есть метод ProgressBar.setProgress(progress: Int, animated: Boolean). Но он доступен только с 24 API.

andkulikov commented 5 years ago

Привет! Я думаю в этом случае правильнее было бы использовать этот транзишн для версий до 24 Api, а начиная с него официальную функциональность. Причина простая: setProgress(animated = true) будет работать гораздо плавнее, он внутри анимирует прогресс как float, то есть не просто скачет по целым числам и прогресс выглядит плавнее. особенно это будет заметно когда прогресс меняется не слишком сильно, например с 10 до 15, если duration 300, то анимация будет состоять из примерно 18 фреймов, а реальных значений только 5. Поэтому может не стоит делать и этот транзишн частью библиотеки, потому что я хочу в ней иметь только подходы, которые могу назвать рекомендованными. Здесь я бы или использовал этот транзишн до 24 апи, или вообще не имел никакой анимации до 24 апи, возможно это не критично для бизнеса, в виду малого количества пользователей на этих версиях. Спасибо!