From 8ed876086db7f7242dfc690ec13367e786d21002 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Oct 2021 23:44:29 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/common/leapfrogWidget/Animations.kt | 25 ++-- .../tap/common/leapfrogWidget/LeapView.kt | 113 ++++++++++++++- .../common/leapfrogWidget/LeapfrogWidget.kt | 131 +++++++++--------- 3 files changed, 191 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt index b89618ee5f..77c9f1fcbb 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt @@ -25,24 +25,29 @@ import android.view.animation.LinearInterpolator typealias ProgressListener = (Int) -> Unit -fun AnimatorSet.createProgressListener(duration: Long, listener: ProgressListener?): Animator { +fun createProgressListener(duration: Long, listener: ProgressListener?): Animator { val valueAnimator = ValueAnimator.ofInt(0, 100) valueAnimator.duration = duration valueAnimator.addUpdateListener { listener?.invoke((it.animatedValue as? Int) ?: 0) } return valueAnimator } -fun View.initAnimation(animDuration: Long, properties: Properties): AnimatorSet { - elevation = properties.elevationEnd - scaleX = properties.scaleEnd - scaleY = properties.scaleEnd - +fun View.unfoldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) translate.duration = animDuration return AnimatorSet().apply { playTogether(translate) } } -fun View.leapAnimation(animDuration: Long, properties: Properties, overLift: Float): AnimatorSet { +fun View.foldAnimation(animDuration: Long, properties: AnimationProperties): AnimatorSet { + val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslationStart, properties.yTranslationEnd) + translate.duration = animDuration + + return AnimatorSet().apply { + playSequentially(translate) + } +} + +fun View.leapAnimation(animDuration: Long, properties: AnimationProperties, overLift: Float): AnimatorSet { val halfDuration = animDuration / 2 val upTo = (height.toFloat() + properties.yTranslationStart + overLift) * -1 @@ -80,7 +85,7 @@ fun View.leapAnimation(animDuration: Long, properties: Properties, overLift: Flo } } -fun View.leapBackAnimation(animDuration: Long, properties: Properties, calculator: PropertyCalculator): AnimatorSet { +fun View.leapBackAnimation(animDuration: Long, properties: AnimationProperties, calculator: PropertyCalculator): AnimatorSet { val halfDuration = animDuration / 2 val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1 @@ -117,7 +122,7 @@ fun View.leapBackAnimation(animDuration: Long, properties: Properties, calculato } } -fun View.pullUpAnimation(leapDuration: Long, properties: Properties): AnimatorSet { +fun View.pullUpAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 2 @@ -135,7 +140,7 @@ fun View.pullUpAnimation(leapDuration: Long, properties: Properties): AnimatorSe } } -fun View.pullDownAnimation(leapDuration: Long, properties: Properties): AnimatorSet { +fun View.pullDownAnimation(leapDuration: Long, properties: AnimationProperties): AnimatorSet { val pullDuration = leapDuration / 2 val delayDuration = leapDuration / 4 diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt index b3a115f964..a9d5212f3e 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt @@ -9,36 +9,124 @@ class LeapView( val view: View, val index: Int, position: Int, - val maximalPosition: Int, - val initialProperties: Properties + private val maximalPosition: Int, + private val calculator: PropertyCalculator, ) { + var currentProperties: AnimationProperties + private set + var currentPosition: Int = position private set var previousPosition: Int = position private set - fun changePositionByLeap() { + init { + val initialProperties = createInitialProperty(position) + currentProperties = AnimationProperties.from(initialProperties, initialProperties) + + view.elevation = initialProperties.elevation + view.translationY = initialProperties.yTranslation + view.scaleX = initialProperties.scale + view.scaleY = initialProperties.scale +// initialProperties.hasForeground + } + + fun leap(): LeapFrogAnimation { previousPosition = currentPosition currentPosition = when (previousPosition) { 0 -> maximalPosition else -> currentPosition - 1 } + updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + + return when { + previousPosition == 0 && currentPosition == maximalPosition -> LeapFrogAnimation.LEAP + else -> LeapFrogAnimation.PULL + } } - fun changePositionByLeapBack() { + fun leapBack(): LeapFrogAnimation { previousPosition = currentPosition currentPosition = when (previousPosition) { maximalPosition -> 0 else -> currentPosition + 1 } + updateProperties(createLeapAnimationProperty(previousPosition, currentPosition)) + + return when { + previousPosition == maximalPosition && currentPosition == 0 -> LeapFrogAnimation.LEAP + else -> LeapFrogAnimation.PULL + } + } + + fun fold() { + updateProperties(currentProperties.toFold()) + } + + fun unfold() { + updateProperties(currentProperties.toUnfold(calculator)) + } + + private fun updateProperties(properties: AnimationProperties) { + currentProperties = properties + } + + private fun createInitialProperty(endPosition: Int): Properties { + return Properties( + endPosition, + calculator.scale(endPosition), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(0), + calculator.hasForeground(endPosition), + ) + } + + private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): AnimationProperties { + return AnimationProperties( + startPosition, + endPosition, + calculator.scale(startPosition), + calculator.scale(endPosition), + calculator.elevation(startPosition, maximalPosition + 1), + calculator.elevation(endPosition, maximalPosition + 1), + calculator.yTranslation(startPosition), + calculator.yTranslation(endPosition), + calculator.hasForeground(startPosition), + calculator.hasForeground(endPosition), + ) + } + + private fun AnimationProperties.toUnfold(calculator: PropertyCalculator): AnimationProperties { + val start = this.yTranslationEnd + val end = calculator.yTranslation(this.positionEnd) + return this.copy(yTranslationStart = start, yTranslationEnd = end) + } + + private fun AnimationProperties.toFold(): AnimationProperties { + val start = this.yTranslationEnd + val end = 0f + return this.copy(yTranslationStart = start, yTranslationEnd = end) } override fun toString(): String = "index: $index, position: $currentPosition, code: ${view.hashCode()}" + +} + +enum class LeapFrogAnimation { + LEAP, PULL } data class Properties( - val startPosition: Int, - val endPosition: Int, + val position: Int, + val scale: Float, + val elevation: Float, + val yTranslation: Float, + val hasForeground: Boolean, +) + +data class AnimationProperties( + val positionStart: Int, + val positionEnd: Int, val scaleStart: Float, val scaleEnd: Float, val elevationStart: Float, @@ -48,6 +136,7 @@ data class Properties( val hasForegroundStart: Boolean, val hasForegroundEnd: Boolean, ) { + override fun toString(): String { return """ scaleStart: $scaleStart, scaleEnd: $scaleEnd, @@ -56,4 +145,16 @@ data class Properties( hasForegroundStart : $hasForegroundStart, hasForegroundEnd: $hasForegroundEnd, """.trimIndent() } + + companion object { + fun from(start: Properties, end: Properties): AnimationProperties { + return AnimationProperties( + start.position, end.position, + start.scale, end.scale, + start.elevation, end.elevation, + start.yTranslation, end.yTranslation, + start.hasForeground, end.hasForeground + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt index e88e1cf49a..423be3b97b 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt @@ -15,15 +15,18 @@ class LeapfrogWidget( private val parentContainer: FrameLayout, private val calculator: PropertyCalculator = PropertyCalculator(), ) { - private val initAnimationDuration = 500L + private val foldAnimationDuration = 500L private val leapAnimationDuration = 800L private val leapBackAnimationDuration = 800L private val leapViews = mutableListOf() + private var foldUnfoldProgress: Int = 100 private var leapProgress: Int = 100 private var leapBackProgress: Int = 100 + private var isFolded: Boolean = true + init { calculator.setTranslationConverter { parentContainer.dpToPx(it) } init() @@ -35,54 +38,65 @@ class LeapfrogWidget( val maxPosition = parentContainer.childCount - 1 parentContainer.children.forEachIndexed { index, view -> val initialPosition = maxPosition - index - val initialProperties = createViewProperty(0, initialPosition) - leapViews.add(LeapView(view, index, initialPosition, maxPosition, initialProperties)) + leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator)) } - resetToInitialState() } - private fun resetToInitialState(withAnimation: Boolean = true, duration: Long = initAnimationDuration) { - if (!viewIsFullFledged()) return + fun fold(listener: ProgressListener? = null) { + if (!canFoldUnfold() || isFolded) return - if (withAnimation) { - val animatorsList = mutableListOf() - leapViews.forEach { animatorsList.add(it.view.initAnimation(duration, it.initialProperties)) } - val animator = AnimatorSet() - animator.interpolator = AccelerateDecelerateInterpolator() - animator.playTogether(animatorsList.toList()) - animator.start() - } else { - leapViews.forEach { - it.view.scaleX = it.initialProperties.scaleEnd - it.view.scaleY = it.initialProperties.scaleEnd - it.view.translationY = it.initialProperties.yTranslationEnd - it.view.elevation = it.initialProperties.elevationEnd + createFoldUnfoldAnimation(true, foldAnimationDuration, listener) + } + + fun unfold(listener: ProgressListener? = null) { + if (!canFoldUnfold() || !isFolded) return + + createFoldUnfoldAnimation(false, foldAnimationDuration, listener) + } + + private fun createFoldUnfoldAnimation(isFoldAnimation: Boolean, duration: Long, listener: ProgressListener?) { + val animatorsList = mutableListOf() + leapViews.forEach { + if (isFoldAnimation) { + it.fold() + animatorsList.add(it.view.foldAnimation(duration, it.currentProperties)) + } else { + it.unfold() + animatorsList.add(it.view.unfoldAnimation(duration, it.currentProperties)) } } + animatorsList.add(createProgressListener(duration) { + foldUnfoldProgress = it + if (it == 100) isFolded = isFoldAnimation + }) + animatorsList.add(createProgressListener(duration, listener)) + + val animator = AnimatorSet() + animator.interpolator = AccelerateDecelerateInterpolator() + animator.playTogether(animatorsList.toList()) + animator.start() } fun leap(listener: ProgressListener? = null) { if (!canLeap()) return - // if not - then it's ready for pullUp - fun isReadyForLeap(leapView: LeapView): Boolean { - return leapView.previousPosition == 0 && leapView.currentPosition == leapView.maximalPosition - } - + val duration = leapAnimationDuration val animatorsList = mutableListOf() leapViews.forEach { - it.changePositionByLeap() - val properties = createViewProperty(it.previousPosition, it.currentPosition) - if (isReadyForLeap(it)) { - val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapAnimation(leapAnimationDuration, properties, overLift)) - } else { - animatorsList.add(it.view.pullUpAnimation(leapAnimationDuration, properties)) + when (it.leap()) { + LeapFrogAnimation.LEAP -> { + val overLift = calculator.overLift(leapViews.size) + animatorsList.add(it.view.leapAnimation(duration, it.currentProperties, overLift)) + } + LeapFrogAnimation.PULL -> { + animatorsList.add(it.view.pullUpAnimation(duration, it.currentProperties)) + } } } + animatorsList.add(createProgressListener(duration) { leapProgress = it }) + animatorsList.add(createProgressListener(duration, listener)) + val animator = AnimatorSet() - animatorsList.add(animator.createProgressListener(leapAnimationDuration) { leapProgress = it }) - animatorsList.add(animator.createProgressListener(leapAnimationDuration, listener)) animator.playTogether(animatorsList.toList()) animator.start() } @@ -90,25 +104,21 @@ class LeapfrogWidget( fun leapBack(listener: ProgressListener? = null) { if (!canLeapBack()) return - // if not - then it's ready for pullDown - fun isReadyForLeapBack(leapView: LeapView): Boolean { - return leapView.previousPosition == leapView.maximalPosition && leapView.currentPosition == 0 - } - + val duration = leapBackAnimationDuration val animatorsList = mutableListOf() leapViews.forEach { - it.changePositionByLeapBack() - val properties = createViewProperty(it.previousPosition, it.currentPosition) - if (isReadyForLeapBack(it)) { - val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapBackAnimation(leapBackAnimationDuration, properties, calculator)) - } else { - animatorsList.add(it.view.pullDownAnimation(leapBackAnimationDuration, properties)) + when (it.leapBack()) { + LeapFrogAnimation.LEAP -> { + animatorsList.add(it.view.leapBackAnimation(duration, it.currentProperties, calculator)) + } + LeapFrogAnimation.PULL -> { + animatorsList.add(it.view.pullDownAnimation(duration, it.currentProperties)) + } } } + animatorsList.add(createProgressListener(duration) { leapBackProgress = it }) + animatorsList.add(createProgressListener(duration, listener)) val animator = AnimatorSet() - animatorsList.add(animator.createProgressListener(leapBackAnimationDuration) { leapBackProgress = it }) - animatorsList.add(animator.createProgressListener(leapBackAnimationDuration, listener)) animator.playTogether(animatorsList.toList()) animator.start() } @@ -119,9 +129,19 @@ class LeapfrogWidget( private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1 + private fun canFoldUnfold(): Boolean { + return when { + !viewIsFullFledged() -> false + foldUnfoldInProgress() -> false + leapInProgress() || leapBackInProgress() -> false + else -> true + } + } + private fun canLeap(): Boolean { return when { !viewIsFullFledged() -> false + isFolded -> false leapBackInProgress() -> false leapProgress < 70 -> false else -> true @@ -131,29 +151,16 @@ class LeapfrogWidget( private fun canLeapBack(): Boolean { return when { !viewIsFullFledged() -> false + isFolded -> false leapInProgress() -> false leapBackProgress < 70 -> false else -> true } } + private fun foldUnfoldInProgress(): Boolean = foldUnfoldProgress != 100 private fun leapInProgress(): Boolean = leapProgress != 100 private fun leapBackInProgress(): Boolean = leapBackProgress != 100 - - private fun createViewProperty(startPosition: Int, endPosition: Int): Properties { - return Properties( - startPosition, - endPosition, - calculator.scale(startPosition), - calculator.scale(endPosition), - calculator.elevation(startPosition, parentContainer.childCount), - calculator.elevation(endPosition, parentContainer.childCount), - calculator.yTranslation(startPosition), - calculator.yTranslation(endPosition), - calculator.hasForeground(startPosition), - calculator.hasForeground(endPosition), - ) - } } class PropertyCalculator(