From 8c101f0e63fcf2c4bb9d519ec93c37853b256f68 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 05:11:46 -0300 Subject: [PATCH 01/33] WIP - Add spring animation on collapse and expand animation --- .../animation/CollapseAnimationActivity.kt | 16 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- jerry/build.gradle | 1 + .../jerry/ExpandableAnimation.kt | 377 +++++++++++++++--- .../alexandregpereira/jerry/ViewAnimation.kt | 30 ++ jerry/src/main/res/values/strings.xml | 8 +- 7 files changed, 368 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 35425dc..7ab855a 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -7,8 +7,10 @@ import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.collapseHeight +import br.alexandregpereira.jerry.collapseHeightSpring import br.alexandregpereira.jerry.collapseWidth import br.alexandregpereira.jerry.expandHeight +import br.alexandregpereira.jerry.expandHeightSpring import br.alexandregpereira.jerry.expandWidth import kotlinx.android.synthetic.main.activity_collapse_animation.* import kotlinx.android.synthetic.main.container_seek_bar.* @@ -39,8 +41,7 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a }) collapseTextButton.setOnClickListener { - collapseTextView.collapseHeight( - duration = seekBar.progress.getSeekBarAnimationDuration(), + collapseTextView.collapseHeightSpring( onProgressChange = { interpolatedTime -> collapsePercentageTextView.text = (interpolatedTime * 100).toInt().toString() } @@ -50,8 +51,7 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseExpandTextButton.setOnClickListener { - collapseTextView.expandHeight( - duration = seekBar.progress.getSeekBarAnimationDuration(), + collapseTextView.expandHeightSpring( onProgressChange = { interpolatedTime -> collapsePercentageTextView.text = (interpolatedTime * 100).toInt().toString() } @@ -61,11 +61,15 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseFixedTextButton.setOnClickListener { - collapseFixedTextView.collapseHeight() + collapseFixedTextView.collapseHeight( + duration = seekBar.progress.getSeekBarAnimationDuration() + ) } collapseExpandFixedTextButton.setOnClickListener { - collapseFixedTextView.expandHeight() + collapseFixedTextView.expandHeight( + duration = seekBar.progress.getSeekBarAnimationDuration() + ) } collapseWidthTextButton.setOnClickListener { diff --git a/build.gradle b/build.gradle index a3c1539..251c446 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0-rc03' + classpath 'com.android.tools.build:gradle:4.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1056a3b..9fdd3c2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/jerry/build.gradle b/jerry/build.gradle index a05de2f..5c66764 100644 --- a/jerry/build.gradle +++ b/jerry/build.gradle @@ -25,4 +25,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' } \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index b117d54..a4efe99 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -1,9 +1,13 @@ package br.alexandregpereira.jerry +import android.util.Log import android.view.View import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.Transformation +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.SpringAnimation /** * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation @@ -133,62 +137,6 @@ fun View.expandWidth( onAnimationEnd = onAnimationEnd ) -private fun View.expand( - duration: Long = ANIMATION_SHORT_TIME, - isHeight: Boolean = true, - onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, - onAnimationStart: (() -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { - return - } - - val originalValue = getOriginalValue(isHeight) - val initialValue = (getLayoutParamSize(isHeight)).let { - if (it == originalValue || it < 0) 0 else it - } - val targetValue = getTargetValue(originalValue, isHeight) - - if (targetValue == null) { - finishExpandingCollapsingAnimation(onAnimationEnd) - return - } - startExpandingRunning() - - if (initialValue == 0) { - if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 - } - visible() - val animation = object : Animation() { - override fun applyTransformation(interpolatedTime: Float, t: Transformation) { - val value = initialValue + ((targetValue - initialValue) * interpolatedTime).toInt() - - val finalValue = if (value == 0) 1 else { - if (value == targetValue && originalValue == ViewGroup.LayoutParams.WRAP_CONTENT) { - ViewGroup.LayoutParams.WRAP_CONTENT - } else { - value - } - } - - setLayoutParamSize(finalValue, isHeight) - onProgressChange?.invoke(interpolatedTime) - requestLayout() - } - - override fun willChangeBounds(): Boolean { - return true - } - } - animation.setAnimationListener(onEnd = { - finishExpandingCollapsingAnimation(onAnimationEnd) - }, onStart = onAnimationStart) - - animation.duration = duration - startAnimation(animation) -} - /** * Animates collapsing the height and changes the visibility status to GONE. * This animation handles double click. This method can be reverted in the middle of the animation @@ -228,6 +176,252 @@ fun View.collapseWidth( onAnimationEnd = onAnimationEnd ) +private fun View.viewProperty( + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null +) = object : FloatPropertyCompat("viewProperty") { + + private val originalValue: Int + get() = getOriginalValue(isHeight) + + private val initialValue: Int + get() = if (isExpandingRunning()) { + (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it + } + } else { + getCollapsingInitialValue(isHeight) + } + + private val targetValue: Int? + get() = if (isExpandingRunning()) { + getTargetValue(originalValue, isHeight) + } else { + null + } + + override fun getValue(view: View?): Float { + return initialValue.toFloat().apply { + Log.d("heightViewProperty", "get: $this") + } + } + + override fun setValue(view: View?, value: Float) { + Log.d("heightViewProperty", "set: $value") + val targetValue = targetValue + if (targetValue != null) { + setExpandingValue(value, targetValue) + } else { + setCollapsingValue(value) + } + + onProgressChange?.invoke(progressFunction(value)) + requestLayout() + } + + private val progressFunction = createProgressFunction() + + private fun createProgressFunction(): (Float) -> Float { + val viewSize = targetValue ?: initialValue + return if (isExpandingRunning()) { + { value -> + (viewSize - (viewSize - value)) / viewSize + } + } else { + { value -> + (viewSize - value) / viewSize + } + } + } + + private fun setCollapsingValue(value: Float) { + if (value == 0f) { + setLayoutParamSize(originalValue, isHeight) + } else { + setLayoutParamSize(value.toInt(), isHeight) + } + } + + private fun setExpandingValue(value: Float, targetValue: Int) { + val finalValue = if (value == 0f) 1f else { + if (value == targetValue.toFloat() && + originalValue == ViewGroup.LayoutParams.WRAP_CONTENT + ) { + ViewGroup.LayoutParams.WRAP_CONTENT + } else { + value + } + } + + setLayoutParamSize(finalValue.toInt(), isHeight) + } +} + +fun View.collapseHeightSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +fun View.collapseWidthSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +private fun View.collapseSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isVisible().not() || isCollapsingRunning()) { + return + } + startCollapsingRunning() + + getOriginalValue(isHeight) + val initialValue = getCollapsingInitialValue(isHeight) + + startExpandCollapseAnimation( + stiffness = stiffness, + isHeight = isHeight, + targetValue = 0f, + onProgressChange = onProgressChange, + onAnimationEnd = { + gone() + onAnimationEnd?.invoke() + } + ) +} + +fun View.expandHeightSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +fun View.expandWidthSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +private fun View.expandSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { + return + } + + val originalValue = getOriginalValue(isHeight) + val initialValue = (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it + } + val targetValue = getTargetValue(originalValue, isHeight) + + if (targetValue == null) { + finishExpandingCollapsingAnimation(onAnimationEnd) + return + } + startExpandingRunning() + + if (initialValue == 0) { + if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 + } + visible() + + startExpandCollapseAnimation( + stiffness = stiffness, + isHeight = isHeight, + targetValue = targetValue.toFloat(), + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd + ) +} + +private fun View.startExpandCollapseAnimation( + stiffness: Float, + isHeight: Boolean = true, + targetValue: Float, + onProgressChange: ((progress: Float) -> Unit)?, + onAnimationEnd: (() -> Unit)? +) { + val springAnimation = this.spring( + getExpandingCollapsingSpringKey(isHeight), + viewProperty(isHeight, onProgressChange), + stiffness = stiffness + ) + + getExpandingCollapsingEndListener(isHeight)?.also { + springAnimation.removeEndListener(it) + } + springAnimation.addExpandingCollapsingEndListener(view = this, isHeight, onAnimationEnd) + + springAnimation.animateToFinalPosition(targetValue) +} + +private fun SpringAnimation.addExpandingCollapsingEndListener( + view: View, + isHeight: Boolean, + onAnimationEnd: (() -> Unit)? = null +) { + DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> + view.getExpandingCollapsingEndListener(isHeight)?.let { + this.removeEndListener(it) + } + view.finishExpandingCollapsingAnimation(onAnimationEnd) + }.let { + this.addEndListener(it) + view.setTag(getExpandingCollapsingEndListenerKey(isHeight), it) + } +} + +private fun View.getExpandingCollapsingEndListener(isHeight: Boolean): DynamicAnimation.OnAnimationEndListener? { + return getTag(getExpandingCollapsingEndListenerKey(isHeight)).run { + this as? DynamicAnimation.OnAnimationEndListener + } +} + +private fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_spring_key + } else { + R.string.expanding_collapsing_width_spring_key + } +} + +private fun getExpandingCollapsingEndListenerKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_end_listener_key + } else { + R.string.expanding_collapsing_width_end_listener_key + } +} + private fun View.collapse( duration: Long = ANIMATION_SHORT_TIME, isHeight: Boolean = true, @@ -246,7 +440,6 @@ private fun View.collapse( val animation = object : Animation() { override fun applyTransformation(interpolatedTime: Float, t: Transformation) { if (interpolatedTime == 1f) { - gone() setLayoutParamSize(originalValue, isHeight) } else { val value = initialValue - (initialValue * interpolatedTime).toInt() @@ -262,6 +455,63 @@ private fun View.collapse( return true } } + animation.setAnimationListener(onEnd = { + gone() + finishExpandingCollapsingAnimation(onAnimationEnd) + }, onStart = onAnimationStart) + + animation.duration = duration + startAnimation(animation) +} + +private fun View.expand( + duration: Long = ANIMATION_SHORT_TIME, + isHeight: Boolean = true, + onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, + onAnimationStart: (() -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { + return + } + + val originalValue = getOriginalValue(isHeight) + val initialValue = (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it + } + val targetValue = getTargetValue(originalValue, isHeight) + + if (targetValue == null) { + finishExpandingCollapsingAnimation(onAnimationEnd) + return + } + startExpandingRunning() + + if (initialValue == 0) { + if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 + } + visible() + val animation = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val value = initialValue + ((targetValue - initialValue) * interpolatedTime).toInt() + + val finalValue = if (value == 0) 1 else { + if (value == targetValue && originalValue == ViewGroup.LayoutParams.WRAP_CONTENT) { + ViewGroup.LayoutParams.WRAP_CONTENT + } else { + value + } + } + + setLayoutParamSize(finalValue, isHeight) + onProgressChange?.invoke(interpolatedTime) + requestLayout() + } + + override fun willChangeBounds(): Boolean { + return true + } + } animation.setAnimationListener(onEnd = { finishExpandingCollapsingAnimation(onAnimationEnd) }, onStart = onAnimationStart) @@ -333,15 +583,24 @@ private fun View.getCollapsingInitialValue(isHeight: Boolean): Int { } private fun View.getOriginalValue(isHeight: Boolean): Int { + val key = getOriginalValueKey(isHeight) return runCatching { - getTag(id) as Int + getTag(key) as Int }.getOrElse { (getLayoutParamSize(isHeight)).apply { - setTag(id, this) + setTag(key, this) } } } +private fun getOriginalValueKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_original_value_key + } else { + R.string.expanding_collapsing_width_original_value_key + } +} + private fun View.clearOriginalValue() { runCatching { setTag(id, null) } } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index e01218b..2625b81 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -2,11 +2,16 @@ package br.alexandregpereira.jerry import android.view.View import android.view.ViewPropertyAnimator +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.SpringAnimation +import androidx.dynamicanimation.animation.SpringForce const val ANIMATION_SHORT_TIME = 200L const val ANIMATION_MEDIUM_TIME = 400L const val ANIMATION_LONG_TIME = 600L +const val ANIMATION_FAST_STIFFNESS = 600f + /** * Used to clear the key to verify if a animation is running. */ @@ -22,6 +27,31 @@ internal const val ENTER_ANIMATION_MODE = 1 */ internal const val POP_ANIMATION_MODE = 2 +fun View.spring( + key: Int, + property: FloatPropertyCompat, + dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, + stiffness: Float = SpringForce.STIFFNESS_LOW, + onValueUpdated: ((progress: Float) -> Unit)? = null +): SpringAnimation { + var springAnimation = getTag(key) as? SpringAnimation + if (springAnimation == null) { + springAnimation = SpringAnimation(this, property).apply { + spring = SpringForce().apply { + this.dampingRatio = dampingRatio + this.stiffness = stiffness + } + onValueUpdated?.let { + addUpdateListener { _, value, _ -> + onValueUpdated(value) + } + } + } + setTag(key, springAnimation) + } + return springAnimation +} + /** * Check if is animation is running using the view tag system. * diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index a0fec95..77e2329 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -1,6 +1,12 @@ - + is_expanding_collapsing_key is_fade_in_fade_out_key + expanding_collapsing_height_original_value_key + expanding_collapsing_height_spring_key + expanding_collapsing_height_end_listener_key + expanding_collapsing_width_original_value_key + expanding_collapsing_width_spring_key + expanding_collapsing_width_end_listener_key \ No newline at end of file From d34721434814be182c4cc8f9cf2fa97c0be60596 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 16:31:23 -0300 Subject: [PATCH 02/33] Create spring animation on collapse and expand animation --- .../animation/CollapseAnimationActivity.kt | 17 +- .../jerry/app/widgets/Drawable.kt | 7 +- .../jerry/app/widgets/RoundTextView.kt | 1 - .../layout/activity_collapse_animation.xml | 28 +- .../res/layout/container_animation_info.xml | 38 ++ .../main/res/layout/container_seek_bar.xml | 2 +- app/src/main/res/values/dimens.xml | 5 +- app/src/main/res/values/strings.xml | 1 + .../jerry/ExpandableAnimation.kt | 573 +++++------------- .../jerry/ExpandableAnimationCommon.kt | 175 ++++++ .../jerry/ExpandableFadingAnimation.kt | 90 +++ .../alexandregpereira/jerry/ViewAnimation.kt | 11 +- .../jerry/WidthHeightViewProperty.kt | 83 +++ 13 files changed, 566 insertions(+), 465 deletions(-) create mode 100644 app/src/main/res/layout/container_animation_info.xml create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 7ab855a..2dd048f 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.collapseHeight import br.alexandregpereira.jerry.collapseHeightSpring @@ -13,11 +14,12 @@ import br.alexandregpereira.jerry.expandHeight import br.alexandregpereira.jerry.expandHeightSpring import br.alexandregpereira.jerry.expandWidth import kotlinx.android.synthetic.main.activity_collapse_animation.* +import kotlinx.android.synthetic.main.container_animation_info.view.* import kotlinx.android.synthetic.main.container_seek_bar.* class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_animation) { - var collapseTextViewCount = 1 + private var collapseTextViewCount = 1 companion object { fun getStartIntent(context: Context): Intent { @@ -28,6 +30,11 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + ViewCompat.setTranslationZ( + collapseAnimationInfo, + resources.getDimension(R.dimen.strong_elevation) + ) + seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { seekBarValue.text = progress.getSeekBarAnimationDuration().run { @@ -43,20 +50,20 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a collapseTextButton.setOnClickListener { collapseTextView.collapseHeightSpring( onProgressChange = { interpolatedTime -> - collapsePercentageTextView.text = (interpolatedTime * 100).toInt().toString() + collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() } ) { - collapseCountTextView.text = collapseTextViewCount++.toString() + collapseAnimationInfo.countTextView.text = collapseTextViewCount++.toString() } } collapseExpandTextButton.setOnClickListener { collapseTextView.expandHeightSpring( onProgressChange = { interpolatedTime -> - collapsePercentageTextView.text = (interpolatedTime * 100).toInt().toString() + collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() } ) { - collapseCountTextView.text = collapseTextViewCount++.toString() + collapseAnimationInfo.countTextView.text = collapseTextViewCount++.toString() } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt index 1b116af..6767880 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt @@ -5,7 +5,6 @@ import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.RippleDrawable -import android.graphics.drawable.ShapeDrawable import android.os.Build import android.view.View import android.view.ViewGroup @@ -50,10 +49,10 @@ fun View.setMaterialShapeDrawable(): View { val materialShapeDrawable = MaterialShapeDrawable(shapeAppearanceModel).apply { this.fillColor = ColorStateList.valueOf(Color.WHITE) - this.elevation = resources.getDimension(R.dimen.elevation) + this.elevation = resources.getDimension(R.dimen.strong_elevation) } - ViewCompat.setElevation(this, resources.getDimension(R.dimen.elevation)) + ViewCompat.setElevation(this, resources.getDimension(R.dimen.strong_elevation)) return this.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -70,7 +69,7 @@ fun View.setRoundShape(): View { shape = GradientDrawable.OVAL } - ViewCompat.setElevation(this, resources.getDimension(R.dimen.elevation2)) + ViewCompat.setElevation(this, resources.getDimension(R.dimen.medium_elevation)) ViewCompat.setBackground(this, ovalDrawable) return this diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/RoundTextView.kt b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/RoundTextView.kt index 4c7f2db..becaad7 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/RoundTextView.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/RoundTextView.kt @@ -16,7 +16,6 @@ class RoundTextView @JvmOverloads constructor( init { setRoundShape() setTextColor(Color.WHITE) - text = "0" gravity = Gravity.CENTER textSize = 12f height = 40.dpToPx(resources) diff --git a/app/src/main/res/layout/activity_collapse_animation.xml b/app/src/main/res/layout/activity_collapse_animation.xml index 343ac59..83b4da0 100644 --- a/app/src/main/res/layout/activity_collapse_animation.xml +++ b/app/src/main/res/layout/activity_collapse_animation.xml @@ -30,27 +30,9 @@ android:background="@color/backgroundColor" android:padding="@dimen/medium" android:text="@string/collapse_expand_animation" - app:layout_constraintBottom_toTopOf="@id/collapseCountTextView" + app:layout_constraintBottom_toTopOf="@id/collapseTextButton" app:layout_constraintTop_toBottomOf="@id/collapseLabel" /> - - - - + + + + + + + + + + + diff --git a/app/src/main/res/layout/container_seek_bar.xml b/app/src/main/res/layout/container_seek_bar.xml index cc3177e..bb03ba3 100644 --- a/app/src/main/res/layout/container_seek_bar.xml +++ b/app/src/main/res/layout/container_seek_bar.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> 16dp - 8dp - 18dp + 2dp + 4dp + 8dp 8dp 2dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2d3a31..08eeaa8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Visible/invisible fade in Collapse Fading Collapse/expand animation + Collapse/Expand animation Collapse animation Collapse and cancel animation Expand animation diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index a4efe99..f1e60a8 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -1,101 +1,11 @@ package br.alexandregpereira.jerry -import android.util.Log import android.view.View import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.Transformation -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation -/** - * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation - * handles double click. This method can be reverted in the middle of the animation if the - * [collapseHeightFading] method is called. - * - * @param duration The duration of the animation - * @param onAnimationEnd The function to call when the animation is finished - */ -fun View.expandHeightFading( - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null -) = expandFading(duration, isHeight = true, onAnimationEnd) - -/** - * Uses the [expandWidth] and [visibleFadeIn] animations in sequence. This animation - * handles double click. This method can be reverted in the middle of the animation if the - * [collapseWidthFading] method is called. - * - * @param duration The duration of the animation - * @param onAnimationEnd The function to call when the animation is finished - */ -fun View.expandWidthFading( - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null -) = expandFading(duration, isHeight = false, onAnimationEnd) - -private fun View.expandFading( - duration: Long = ANIMATION_SHORT_TIME, - isHeight: Boolean = true, - onAnimationEnd: (() -> Unit)? = null -) { - if (alpha == 1f && (isVisible() && isCollapsingRunning().not())) { - return - } - if (alpha == 1f) alpha = 0f - - if (alpha > 0f && alpha < 1f) { - visibleFadeIn(duration = duration, onAnimationEnd = onAnimationEnd) - return - } - - expand(duration = duration / 2, isHeight = isHeight) { - visibleFadeIn(duration = duration / 2, onAnimationEnd = onAnimationEnd) - } -} - -/** - * Uses the [hideFadeOut] and [collapseHeight] animations in sequence. This animation - * handles double click. This method can be reverted in the middle of the animation if the - * [expandHeightFading] method is called. - * - * @param duration The duration of the animation - * @param onAnimationEnd The function to call when the animation is finished - */ -fun View.collapseHeightFading( - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null -) = collapseFading(duration, isHeight = true, onAnimationEnd) - -/** - * Uses the [hideFadeOut] and [collapseWidth] animations in sequence. This animation - * handles double click. This method can be reverted in the middle of the animation if the - * [expandWidthFading] method is called. - * - * @param duration The duration of the animation - * @param onAnimationEnd The function to call when the animation is finished - */ -fun View.collapseWidthFading( - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null -) = collapseFading(duration, isHeight = false, onAnimationEnd) - -private fun View.collapseFading( - duration: Long = ANIMATION_SHORT_TIME, - isHeight: Boolean = true, - onAnimationEnd: (() -> Unit)? = null -) { - if (isExpandingRunning()) { - collapse(duration = duration, isHeight = isHeight, onAnimationEnd = onAnimationEnd) - return - } - - hideFadeOut(duration = duration / 2) { - collapse(duration = duration / 2, isHeight = isHeight, onAnimationEnd = onAnimationEnd) - } -} - /** * Animates expanding the height and changes the visibility status to VISIBLE. * This animation handles double click. This method can be reverted in the middle of the animation @@ -176,253 +86,7 @@ fun View.collapseWidth( onAnimationEnd = onAnimationEnd ) -private fun View.viewProperty( - isHeight: Boolean = true, - onProgressChange: ((progress: Float) -> Unit)? = null -) = object : FloatPropertyCompat("viewProperty") { - - private val originalValue: Int - get() = getOriginalValue(isHeight) - - private val initialValue: Int - get() = if (isExpandingRunning()) { - (getLayoutParamSize(isHeight)).let { - if (it == originalValue || it < 0) 0 else it - } - } else { - getCollapsingInitialValue(isHeight) - } - - private val targetValue: Int? - get() = if (isExpandingRunning()) { - getTargetValue(originalValue, isHeight) - } else { - null - } - - override fun getValue(view: View?): Float { - return initialValue.toFloat().apply { - Log.d("heightViewProperty", "get: $this") - } - } - - override fun setValue(view: View?, value: Float) { - Log.d("heightViewProperty", "set: $value") - val targetValue = targetValue - if (targetValue != null) { - setExpandingValue(value, targetValue) - } else { - setCollapsingValue(value) - } - - onProgressChange?.invoke(progressFunction(value)) - requestLayout() - } - - private val progressFunction = createProgressFunction() - - private fun createProgressFunction(): (Float) -> Float { - val viewSize = targetValue ?: initialValue - return if (isExpandingRunning()) { - { value -> - (viewSize - (viewSize - value)) / viewSize - } - } else { - { value -> - (viewSize - value) / viewSize - } - } - } - - private fun setCollapsingValue(value: Float) { - if (value == 0f) { - setLayoutParamSize(originalValue, isHeight) - } else { - setLayoutParamSize(value.toInt(), isHeight) - } - } - - private fun setExpandingValue(value: Float, targetValue: Int) { - val finalValue = if (value == 0f) 1f else { - if (value == targetValue.toFloat() && - originalValue == ViewGroup.LayoutParams.WRAP_CONTENT - ) { - ViewGroup.LayoutParams.WRAP_CONTENT - } else { - value - } - } - - setLayoutParamSize(finalValue.toInt(), isHeight) - } -} - -fun View.collapseHeightSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = collapseSpring( - stiffness = stiffness, - isHeight = true, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -fun View.collapseWidthSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = collapseSpring( - stiffness = stiffness, - isHeight = false, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -private fun View.collapseSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - isHeight: Boolean = true, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (isVisible().not() || isCollapsingRunning()) { - return - } - startCollapsingRunning() - - getOriginalValue(isHeight) - val initialValue = getCollapsingInitialValue(isHeight) - - startExpandCollapseAnimation( - stiffness = stiffness, - isHeight = isHeight, - targetValue = 0f, - onProgressChange = onProgressChange, - onAnimationEnd = { - gone() - onAnimationEnd?.invoke() - } - ) -} - -fun View.expandHeightSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = expandSpring( - stiffness = stiffness, - isHeight = true, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -fun View.expandWidthSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = expandSpring( - stiffness = stiffness, - isHeight = false, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -private fun View.expandSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, - isHeight: Boolean = true, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { - return - } - - val originalValue = getOriginalValue(isHeight) - val initialValue = (getLayoutParamSize(isHeight)).let { - if (it == originalValue || it < 0) 0 else it - } - val targetValue = getTargetValue(originalValue, isHeight) - - if (targetValue == null) { - finishExpandingCollapsingAnimation(onAnimationEnd) - return - } - startExpandingRunning() - - if (initialValue == 0) { - if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 - } - visible() - - startExpandCollapseAnimation( - stiffness = stiffness, - isHeight = isHeight, - targetValue = targetValue.toFloat(), - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd - ) -} - -private fun View.startExpandCollapseAnimation( - stiffness: Float, - isHeight: Boolean = true, - targetValue: Float, - onProgressChange: ((progress: Float) -> Unit)?, - onAnimationEnd: (() -> Unit)? -) { - val springAnimation = this.spring( - getExpandingCollapsingSpringKey(isHeight), - viewProperty(isHeight, onProgressChange), - stiffness = stiffness - ) - - getExpandingCollapsingEndListener(isHeight)?.also { - springAnimation.removeEndListener(it) - } - springAnimation.addExpandingCollapsingEndListener(view = this, isHeight, onAnimationEnd) - - springAnimation.animateToFinalPosition(targetValue) -} - -private fun SpringAnimation.addExpandingCollapsingEndListener( - view: View, - isHeight: Boolean, - onAnimationEnd: (() -> Unit)? = null -) { - DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> - view.getExpandingCollapsingEndListener(isHeight)?.let { - this.removeEndListener(it) - } - view.finishExpandingCollapsingAnimation(onAnimationEnd) - }.let { - this.addEndListener(it) - view.setTag(getExpandingCollapsingEndListenerKey(isHeight), it) - } -} - -private fun View.getExpandingCollapsingEndListener(isHeight: Boolean): DynamicAnimation.OnAnimationEndListener? { - return getTag(getExpandingCollapsingEndListenerKey(isHeight)).run { - this as? DynamicAnimation.OnAnimationEndListener - } -} - -private fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { - return if (isHeight) { - R.string.expanding_collapsing_height_spring_key - } else { - R.string.expanding_collapsing_width_spring_key - } -} - -private fun getExpandingCollapsingEndListenerKey(isHeight: Boolean): Int { - return if (isHeight) { - R.string.expanding_collapsing_height_end_listener_key - } else { - R.string.expanding_collapsing_width_end_listener_key - } -} - -private fun View.collapse( +internal fun View.collapse( duration: Long = ANIMATION_SHORT_TIME, isHeight: Boolean = true, onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, @@ -464,7 +128,7 @@ private fun View.collapse( startAnimation(animation) } -private fun View.expand( +internal fun View.expand( duration: Long = ANIMATION_SHORT_TIME, isHeight: Boolean = true, onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, @@ -539,110 +203,171 @@ fun Animation.setAnimationListener( return this } -private fun View.finishExpandingCollapsingAnimation(onAnimationEnd: (() -> Unit)?) { - clearOriginalValue() - clearExpandingCollapsingRunning() - onAnimationEnd?.invoke() -} - -private fun View.setLayoutParamSize(value: Int, isHeight: Boolean) { - if (isHeight) { - layoutParams.height = value +fun View.animateHeightVisibility( + visible: Boolean, + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (visible) { + expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) } else { - layoutParams.width = value + expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) } } -private fun View.getLayoutParamSize(isHeight: Boolean): Int { - return if (isHeight) layoutParams.height else layoutParams.width -} - - -private fun View.isExpandingCollapsingRunning(animationMode: Int): Boolean = - isAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) - -private fun View.setExpandingCollapsingRunning(animationMode: Int) = - setAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) - -fun View.isExpandingRunning() = isExpandingCollapsingRunning(ENTER_ANIMATION_MODE) - -fun View.isCollapsingRunning() = isExpandingCollapsingRunning(POP_ANIMATION_MODE) +/** + * Animates collapsing the height and changes the visibility status to GONE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [expandHeight] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.collapseHeightSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) -private fun View.clearExpandingCollapsingRunning() = - setExpandingCollapsingRunning(NONE_ANIMATION_MODE) +/** + * Animates collapsing the width and changes the visibility status to GONE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [expandWidth] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.collapseWidthSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) -private fun View.startExpandingRunning() = setExpandingCollapsingRunning(ENTER_ANIMATION_MODE) +/** + * Animates expanding the height and changes the visibility status to VISIBLE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [collapseHeight] method is called. Any alteration of the parent width during the this + * animation makes glitches in the animation. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.expandHeightSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) -private fun View.startCollapsingRunning() = setExpandingCollapsingRunning(POP_ANIMATION_MODE) +/** + * Animates expanding the width and changes the visibility status to VISIBLE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [collapseWidth] method is called. Any alteration of the parent width during the this animation + * makes glitches in the animation. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.expandWidthSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) -private fun View.getCollapsingInitialValue(isHeight: Boolean): Int { - val value = getLayoutParamSize(isHeight) - return value.takeIf { it > 0 } ?: run { - if (isHeight) measuredHeight else measuredWidth +internal fun View.collapseSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isVisible().not() || isCollapsingRunning()) { + return } -} + startCollapsingRunning() -private fun View.getOriginalValue(isHeight: Boolean): Int { - val key = getOriginalValueKey(isHeight) - return runCatching { - getTag(key) as Int - }.getOrElse { - (getLayoutParamSize(isHeight)).apply { - setTag(key, this) + getOriginalValue(isHeight) + getCollapsingInitialValue(isHeight) + + startExpandCollapseAnimation( + stiffness = stiffness, + isHeight = isHeight, + targetValue = 0f, + onProgressChange = onProgressChange, + onAnimationEnd = { + gone() + onAnimationEnd?.invoke() } - } + ) } -private fun getOriginalValueKey(isHeight: Boolean): Int { - return if (isHeight) { - R.string.expanding_collapsing_height_original_value_key - } else { - R.string.expanding_collapsing_width_original_value_key +internal fun View.expandSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { + return } -} - -private fun View.clearOriginalValue() { - runCatching { setTag(id, null) } -} - -private fun View.getTargetValue(originalValue: Int, isHeight: Boolean): Int? { - if (originalValue > 0) return originalValue - val parentSize = (parent as View).width - if (parentSize == 0) { - visible() - return null + val originalValue = getOriginalValue(isHeight) + val initialValue = (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it } + val targetValue = getTargetValue(originalValue, isHeight) - if (originalValue == ViewGroup.LayoutParams.MATCH_PARENT) return parentSize - - val widthMeasureSize: Int - val heightMeasureSize: Int - - val widthMeasureSpecMode: Int - val heightMeasureSpecMode: Int - - if (isHeight) { - widthMeasureSize = parentSize - heightMeasureSize = 0 - - widthMeasureSpecMode = View.MeasureSpec.EXACTLY - heightMeasureSpecMode = View.MeasureSpec.UNSPECIFIED - } else { - widthMeasureSize = 0 - heightMeasureSize = parentSize - - widthMeasureSpecMode = View.MeasureSpec.UNSPECIFIED - heightMeasureSpecMode = View.MeasureSpec.EXACTLY + if (targetValue == null) { + finishExpandingCollapsingAnimation(onAnimationEnd) + return } + startExpandingRunning() - val widthMeasureSpec = - View.MeasureSpec.makeMeasureSpec(widthMeasureSize, widthMeasureSpecMode) - - val heightMeasureSpec = - View.MeasureSpec.makeMeasureSpec(heightMeasureSize, heightMeasureSpecMode) - - measure(widthMeasureSpec, heightMeasureSpec) + if (initialValue == 0) { + if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 + } + visible() - return if (isHeight) measuredHeight else measuredWidth + startExpandCollapseAnimation( + stiffness = stiffness, + isHeight = isHeight, + targetValue = targetValue.toFloat(), + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd + ) } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt new file mode 100644 index 0000000..28c61b1 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -0,0 +1,175 @@ +package br.alexandregpereira.jerry + +import android.view.View +import android.view.ViewGroup +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringAnimation + +internal fun View.startExpandCollapseAnimation( + stiffness: Float, + isHeight: Boolean = true, + targetValue: Float, + onProgressChange: ((progress: Float) -> Unit)?, + onAnimationEnd: (() -> Unit)? +) { + val springAnimation = this.spring( + getExpandingCollapsingSpringKey(isHeight), + widthHeightViewProperty(isHeight, onProgressChange), + stiffness = stiffness + ) + + getExpandingCollapsingEndListener(isHeight)?.also { + springAnimation.removeEndListener(it) + } + springAnimation.addExpandingCollapsingEndListener(view = this, isHeight, onAnimationEnd) + + springAnimation.animateToFinalPosition(targetValue) +} + +internal fun SpringAnimation.addExpandingCollapsingEndListener( + view: View, + isHeight: Boolean, + onAnimationEnd: (() -> Unit)? = null +) { + DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> + view.getExpandingCollapsingEndListener(isHeight)?.let { + this.removeEndListener(it) + } + view.finishExpandingCollapsingAnimation(onAnimationEnd) + }.let { + this.addEndListener(it) + view.setTag(getExpandingCollapsingEndListenerKey(isHeight), it) + } +} + +internal fun View.getExpandingCollapsingEndListener(isHeight: Boolean): DynamicAnimation.OnAnimationEndListener? { + return getTag(getExpandingCollapsingEndListenerKey(isHeight)).run { + this as? DynamicAnimation.OnAnimationEndListener + } +} + +internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_spring_key + } else { + R.string.expanding_collapsing_width_spring_key + } +} + +internal fun getExpandingCollapsingEndListenerKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_end_listener_key + } else { + R.string.expanding_collapsing_width_end_listener_key + } +} + +internal fun View.finishExpandingCollapsingAnimation(onAnimationEnd: (() -> Unit)?) { + clearOriginalValue() + clearExpandingCollapsingRunning() + onAnimationEnd?.invoke() +} + +internal fun View.setLayoutParamSize(value: Int, isHeight: Boolean) { + if (isHeight) { + layoutParams.height = value + } else { + layoutParams.width = value + } +} + +internal fun View.getLayoutParamSize(isHeight: Boolean): Int { + return if (isHeight) layoutParams.height else layoutParams.width +} + + +internal fun View.isExpandingCollapsingRunning(animationMode: Int): Boolean = + isAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) + +internal fun View.setExpandingCollapsingRunning(animationMode: Int) = + setAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) + +internal fun View.isExpandingRunning() = isExpandingCollapsingRunning(ENTER_ANIMATION_MODE) + +internal fun View.isCollapsingRunning() = isExpandingCollapsingRunning(POP_ANIMATION_MODE) + +internal fun View.clearExpandingCollapsingRunning() = + setExpandingCollapsingRunning(NONE_ANIMATION_MODE) + +internal fun View.startExpandingRunning() = setExpandingCollapsingRunning(ENTER_ANIMATION_MODE) + +internal fun View.startCollapsingRunning() = setExpandingCollapsingRunning(POP_ANIMATION_MODE) + +internal fun View.getCollapsingInitialValue(isHeight: Boolean): Int { + val value = getLayoutParamSize(isHeight) + return value.takeIf { it > 0 } ?: run { + if (isHeight) measuredHeight else measuredWidth + } +} + +internal fun View.getOriginalValue(isHeight: Boolean): Int { + val key = getOriginalValueKey(isHeight) + return runCatching { + getTag(key) as Int + }.getOrElse { + (getLayoutParamSize(isHeight)).apply { + setTag(key, this) + } + } +} + +internal fun getOriginalValueKey(isHeight: Boolean): Int { + return if (isHeight) { + R.string.expanding_collapsing_height_original_value_key + } else { + R.string.expanding_collapsing_width_original_value_key + } +} + +internal fun View.clearOriginalValue() { + runCatching { setTag(id, null) } +} + +internal fun View.getTargetValue(originalValue: Int, isHeight: Boolean): Int? { + if (originalValue > 0) return originalValue + + val parentSize = (parent as View).width + if (parentSize == 0) { + visible() + return null + } + + if (originalValue == ViewGroup.LayoutParams.MATCH_PARENT) return parentSize + + val widthMeasureSize: Int + val heightMeasureSize: Int + + val widthMeasureSpecMode: Int + val heightMeasureSpecMode: Int + + if (isHeight) { + widthMeasureSize = parentSize + heightMeasureSize = 0 + + widthMeasureSpecMode = View.MeasureSpec.EXACTLY + heightMeasureSpecMode = View.MeasureSpec.UNSPECIFIED + } else { + widthMeasureSize = 0 + heightMeasureSize = parentSize + + widthMeasureSpecMode = View.MeasureSpec.UNSPECIFIED + heightMeasureSpecMode = View.MeasureSpec.EXACTLY + } + + val widthMeasureSpec = + View.MeasureSpec.makeMeasureSpec(widthMeasureSize, widthMeasureSpecMode) + + val heightMeasureSpec = + View.MeasureSpec.makeMeasureSpec(heightMeasureSize, heightMeasureSpecMode) + + measure(widthMeasureSpec, heightMeasureSpec) + + return if (isHeight) measuredHeight else measuredWidth +} + + diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt new file mode 100644 index 0000000..ebbb0fe --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt @@ -0,0 +1,90 @@ +package br.alexandregpereira.jerry + +import android.view.View + +/** + * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [collapseHeightFading] method is called. + * + * @param duration The duration of the animation + * @param onAnimationEnd The function to call when the animation is finished + */ +fun View.expandHeightFading( + duration: Long = ANIMATION_SHORT_TIME, + onAnimationEnd: (() -> Unit)? = null +) = expandFading(duration, isHeight = true, onAnimationEnd) + +/** + * Uses the [expandWidth] and [visibleFadeIn] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [collapseWidthFading] method is called. + * + * @param duration The duration of the animation + * @param onAnimationEnd The function to call when the animation is finished + */ +fun View.expandWidthFading( + duration: Long = ANIMATION_SHORT_TIME, + onAnimationEnd: (() -> Unit)? = null +) = expandFading(duration, isHeight = false, onAnimationEnd) + +/** + * Uses the [hideFadeOut] and [collapseHeight] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [expandHeightFading] method is called. + * + * @param duration The duration of the animation + * @param onAnimationEnd The function to call when the animation is finished + */ +fun View.collapseHeightFading( + duration: Long = ANIMATION_SHORT_TIME, + onAnimationEnd: (() -> Unit)? = null +) = collapseFading(duration, isHeight = true, onAnimationEnd) + +/** + * Uses the [hideFadeOut] and [collapseWidth] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [expandWidthFading] method is called. + * + * @param duration The duration of the animation + * @param onAnimationEnd The function to call when the animation is finished + */ +fun View.collapseWidthFading( + duration: Long = ANIMATION_SHORT_TIME, + onAnimationEnd: (() -> Unit)? = null +) = collapseFading(duration, isHeight = false, onAnimationEnd) + +private fun View.collapseFading( + duration: Long = ANIMATION_SHORT_TIME, + isHeight: Boolean = true, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning()) { + collapse(duration = duration, isHeight = isHeight, onAnimationEnd = onAnimationEnd) + return + } + + hideFadeOut(duration = duration / 2) { + collapse(duration = duration / 2, isHeight = isHeight, onAnimationEnd = onAnimationEnd) + } +} + +private fun View.expandFading( + duration: Long = ANIMATION_SHORT_TIME, + isHeight: Boolean = true, + onAnimationEnd: (() -> Unit)? = null +) { + if (alpha == 1f && (isVisible() && isCollapsingRunning().not())) { + return + } + if (alpha == 1f) alpha = 0f + + if (alpha > 0f && alpha < 1f) { + visibleFadeIn(duration = duration, onAnimationEnd = onAnimationEnd) + return + } + + expand(duration = duration / 2, isHeight = isHeight) { + visibleFadeIn(duration = duration / 2, onAnimationEnd = onAnimationEnd) + } +} \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 2625b81..8044e36 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -2,13 +2,12 @@ package br.alexandregpereira.jerry import android.view.View import android.view.ViewPropertyAnimator +import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce const val ANIMATION_SHORT_TIME = 200L -const val ANIMATION_MEDIUM_TIME = 400L -const val ANIMATION_LONG_TIME = 600L const val ANIMATION_FAST_STIFFNESS = 600f @@ -31,8 +30,7 @@ fun View.spring( key: Int, property: FloatPropertyCompat, dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, - stiffness: Float = SpringForce.STIFFNESS_LOW, - onValueUpdated: ((progress: Float) -> Unit)? = null + stiffness: Float = SpringForce.STIFFNESS_LOW ): SpringAnimation { var springAnimation = getTag(key) as? SpringAnimation if (springAnimation == null) { @@ -41,11 +39,6 @@ fun View.spring( this.dampingRatio = dampingRatio this.stiffness = stiffness } - onValueUpdated?.let { - addUpdateListener { _, value, _ -> - onValueUpdated(value) - } - } } setTag(key, springAnimation) } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt new file mode 100644 index 0000000..30271c8 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt @@ -0,0 +1,83 @@ +package br.alexandregpereira.jerry + +import android.view.View +import android.view.ViewGroup +import androidx.dynamicanimation.animation.FloatPropertyCompat + +internal fun View.widthHeightViewProperty( + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null +) = object : FloatPropertyCompat("viewProperty") { + + private val originalValue: Int + get() = getOriginalValue(isHeight) + + private val initialValue: Int + get() = if (isExpandingRunning()) { + (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it + } + } else { + getCollapsingInitialValue(isHeight) + } + + private val targetValue: Int? + get() = if (isExpandingRunning()) { + getTargetValue(originalValue, isHeight) + } else { + null + } + + private val progressFunction = createProgressFunction() + + override fun getValue(view: View?): Float { + return initialValue.toFloat() + } + + override fun setValue(view: View?, value: Float) { + val targetValue = targetValue + if (targetValue != null) { + setExpandingValue(value, targetValue) + } else { + setCollapsingValue(value) + } + + onProgressChange?.invoke(progressFunction(value)) + requestLayout() + } + + private fun setCollapsingValue(value: Float) { + if (value == 0f) { + setLayoutParamSize(originalValue, isHeight) + } else { + setLayoutParamSize(value.toInt(), isHeight) + } + } + + private fun setExpandingValue(value: Float, targetValue: Int) { + val finalValue = if (value == 0f) 1f else { + if (value == targetValue.toFloat() && + originalValue == ViewGroup.LayoutParams.WRAP_CONTENT + ) { + ViewGroup.LayoutParams.WRAP_CONTENT + } else { + value + } + } + + setLayoutParamSize(finalValue.toInt(), isHeight) + } + + private fun createProgressFunction(): (Float) -> Float { + val fullValue = targetValue ?: initialValue + return if (isExpandingRunning()) { + { value -> + (fullValue - (fullValue - value)) / fullValue + } + } else { + { value -> + (fullValue - value) / fullValue + } + } + } +} \ No newline at end of file From 39c94a0b240bae9f280478f89eaa6b2d03af2690 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 17:28:03 -0300 Subject: [PATCH 03/33] Create Fade Spring animations --- .../app/animation/FadeAnimationActivity.kt | 41 ++++-- .../res/layout/activity_fade_animation.xml | 2 +- app/src/main/res/values/strings.xml | 3 +- .../jerry/ExpandableAnimation.kt | 24 +++- .../jerry/ExpandableAnimationCommon.kt | 45 ------- .../alexandregpereira/jerry/FadeAnimation.kt | 122 +++++++++++++++++- .../alexandregpereira/jerry/ViewAnimation.kt | 51 ++++++++ jerry/src/main/res/values/strings.xml | 4 + 8 files changed, 231 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index 1df20b1..ff6b957 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -1,13 +1,13 @@ package br.alexandregpereira.jerry.app.animation -import br.alexandregpereira.jerry.invisibleFadeOut -import br.alexandregpereira.jerry.setTextFade -import br.alexandregpereira.jerry.visibleFadeIn -import br.alexandregpereira.jerry.app.R import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.invisibleFadeOutSpring +import br.alexandregpereira.jerry.setTextFade +import br.alexandregpereira.jerry.visibleFadeInSpring import kotlinx.android.synthetic.main.activity_fade_animation.* class FadeAnimationActivity : AppCompatActivity() { @@ -22,24 +22,47 @@ class FadeAnimationActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_fade_animation) + val list = listOf( + "Fade text change 2", + "Fade text change 3", + "Fade text change 4", + "Fade text change 5", + "Fade text change 1", + ).circularIterator() fadeTextButton.setOnClickListener { - fadeTextView.setTextFade("${System.currentTimeMillis()}") + fadeTextView.setTextFade(list.next()) } goneTextButton.setOnClickListener { - goneTextView.invisibleFadeOut() + goneTextView.invisibleFadeOutSpring() } goneVisibleTextButton.setOnClickListener { - goneTextView.visibleFadeIn() + goneTextView.visibleFadeInSpring() } visibleTextButton.setOnClickListener { - visibleTextView.visibleFadeIn() + visibleTextView.visibleFadeInSpring() } visibleInvisibleTextButton.setOnClickListener { - visibleTextView.invisibleFadeOut() + visibleTextView.invisibleFadeOutSpring() + } + } + + fun List.circularIterator(): Iterator { + val size = this.size + return object : MutableIterator { + var i = 0 + override fun hasNext(): Boolean { + return i < size + } + + override fun next(): T { + return this@circularIterator[i++ % size] + } + + override fun remove() {} } } } diff --git a/app/src/main/res/layout/activity_fade_animation.xml b/app/src/main/res/layout/activity_fade_animation.xml index 4ee7036..442ffed 100644 --- a/app/src/main/res/layout/activity_fade_animation.xml +++ b/app/src/main/res/layout/activity_fade_animation.xml @@ -30,7 +30,7 @@ style="@style/FirstButton.SecondButton" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/fade_text_change" /> + android:text="@string/fade_text_change_button" /> Jerry Fade - Fade text change + Fade text change 1 + Fade text change Invisible/visible fade out Invisible fade out Visible fade in diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index f1e60a8..4731e3f 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -324,10 +324,10 @@ internal fun View.collapseSpring( getOriginalValue(isHeight) getCollapsingInitialValue(isHeight) - startExpandCollapseAnimation( + startExpandCollapseSpringAnimation( + targetValue = 0f, stiffness = stiffness, isHeight = isHeight, - targetValue = 0f, onProgressChange = onProgressChange, onAnimationEnd = { gone() @@ -363,11 +363,27 @@ internal fun View.expandSpring( } visible() - startExpandCollapseAnimation( + startExpandCollapseSpringAnimation( + targetValue = targetValue.toFloat(), stiffness = stiffness, isHeight = isHeight, - targetValue = targetValue.toFloat(), onProgressChange = onProgressChange, onAnimationEnd = onAnimationEnd ) } + +private fun View.startExpandCollapseSpringAnimation( + targetValue: Float, + stiffness: Float, + isHeight: Boolean, + onProgressChange: ((progress: Float) -> Unit)?, + onAnimationEnd: (() -> Unit)?, +) = startSpringAnimation( + key = getExpandingCollapsingSpringKey(isHeight), + property = widthHeightViewProperty(isHeight, onProgressChange), + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { + finishExpandingCollapsingAnimation(onAnimationEnd) + } +) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index 28c61b1..959b323 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -2,51 +2,6 @@ package br.alexandregpereira.jerry import android.view.View import android.view.ViewGroup -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.SpringAnimation - -internal fun View.startExpandCollapseAnimation( - stiffness: Float, - isHeight: Boolean = true, - targetValue: Float, - onProgressChange: ((progress: Float) -> Unit)?, - onAnimationEnd: (() -> Unit)? -) { - val springAnimation = this.spring( - getExpandingCollapsingSpringKey(isHeight), - widthHeightViewProperty(isHeight, onProgressChange), - stiffness = stiffness - ) - - getExpandingCollapsingEndListener(isHeight)?.also { - springAnimation.removeEndListener(it) - } - springAnimation.addExpandingCollapsingEndListener(view = this, isHeight, onAnimationEnd) - - springAnimation.animateToFinalPosition(targetValue) -} - -internal fun SpringAnimation.addExpandingCollapsingEndListener( - view: View, - isHeight: Boolean, - onAnimationEnd: (() -> Unit)? = null -) { - DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> - view.getExpandingCollapsingEndListener(isHeight)?.let { - this.removeEndListener(it) - } - view.finishExpandingCollapsingAnimation(onAnimationEnd) - }.let { - this.addEndListener(it) - view.setTag(getExpandingCollapsingEndListenerKey(isHeight), it) - } -} - -internal fun View.getExpandingCollapsingEndListener(isHeight: Boolean): DynamicAnimation.OnAnimationEndListener? { - return getTag(getExpandingCollapsingEndListenerKey(isHeight)).run { - this as? DynamicAnimation.OnAnimationEndListener - } -} internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { return if (isHeight) { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index f10b5c6..ba7940e 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -3,6 +3,8 @@ package br.alexandregpereira.jerry import android.view.View import android.view.ViewPropertyAnimator import android.view.animation.Interpolator +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringAnimation /** * Change the visibility to GONE of the view using fade out animation. This method can be @@ -75,7 +77,7 @@ fun View.hideFadeOut( onAnimationEnd: (() -> Unit)? = null ) { if (isVisible().not() || isFadeOutRunning()) { - if (isFadeOutRunning().not()){ + if (isFadeOutRunning().not()) { onAnimationEnd?.invoke() } return @@ -128,3 +130,121 @@ private fun View.clearFadeInFadeOutRunning() = setFadeInFadeOutRunning(NONE_ANIM private fun View.startFadeInRunning() = setFadeInFadeOutRunning(ENTER_ANIMATION_MODE) private fun View.startFadeOutRunning() = setFadeInFadeOutRunning(POP_ANIMATION_MODE) + +/** + * Change the visibility to GONE of the view using fade out animation. This method can be + * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.goneFadeOutSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + hideFadeOutSpring(stiffness, ::gone, onAnimationEnd = onAnimationEnd) +} + +/** + * Change the visibility to INVISIBLE of the view using fade out animation. This method can be + * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.invisibleFadeOutSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + hideFadeOutSpring(stiffness, ::invisible, onAnimationEnd) +} + +/** + * Change the visibility to VISIBLE of the view using fade in animation. This method can be + * reverted in the middle of the animation if the [invisibleFadeOut] or [goneFadeOut] + * method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.visibleFadeInSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + if (isFadeInRunning() || (alpha == 1f && isVisible() && isFadeOutRunning().not())) { + if (isFadeInRunning().not()) { + onAnimationEnd?.invoke() + } + return + } + startFadeInRunning() + if (alpha == 1f) alpha = 0f + visible() + + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd + ) +} + +/** + * Start the fade out animation without changing the visibility status. The changes in the + * visibility status is delegate to the function [hide]. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_FAST_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.hideFadeOutSpring( + stiffness: Float = ANIMATION_FAST_STIFFNESS, + hide: (() -> Unit), + onAnimationEnd: (() -> Unit)? = null +) { + if (isVisible().not() || isFadeOutRunning()) { + if (isFadeOutRunning().not()) { + onAnimationEnd?.invoke() + } + return + } + startFadeOutRunning() + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = { + hide() + onAnimationEnd?.invoke() + } + ) +} + +private fun View.startFadeSpringAnimation( + targetValue: Float, + stiffness: Float, + onAnimationEnd: (() -> Unit)?, +) = startSpringAnimation( + key = R.string.alpha_spring_key, + property = DynamicAnimation.ALPHA, + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = R.string.alpha_end_listener_key to { + clearFadeInFadeOutRunning() + onAnimationEnd?.invoke() + } +) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 8044e36..9b4cdce 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -45,6 +45,57 @@ fun View.spring( return springAnimation } +internal fun View.startSpringAnimation( + key: Int, + property: FloatPropertyCompat, + targetValue: Float, + stiffness: Float = SpringForce.STIFFNESS_LOW, + dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, + endListenerPair: Pair Unit)>? = null +) { + val springAnimation = this.spring( + key, + property, + stiffness = stiffness, + dampingRatio = dampingRatio + ) + + endListenerPair?.let { + getSpringEndListener(key = it.first)?.also { endListener -> + springAnimation.removeEndListener(endListener) + } + springAnimation.addSpringEndListener( + key = it.first, + view = this, + onAnimationEnd = it.second + ) + } + + springAnimation.animateToFinalPosition(targetValue) +} + +internal fun SpringAnimation.addSpringEndListener( + key: Int, + view: View, + onAnimationEnd: () -> Unit +) { + DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> + view.getSpringEndListener(key)?.let { + this.removeEndListener(it) + } + onAnimationEnd() + }.let { + this.addEndListener(it) + view.setTag(key, it) + } +} + +internal fun View.getSpringEndListener(key: Int): DynamicAnimation.OnAnimationEndListener? { + return getTag(key).run { + this as? DynamicAnimation.OnAnimationEndListener + } +} + /** * Check if is animation is running using the view tag system. * diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index 77e2329..85bce01 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -3,6 +3,10 @@ is_expanding_collapsing_key is_fade_in_fade_out_key + + alpha_spring_key + alpha_end_listener_key + expanding_collapsing_height_original_value_key expanding_collapsing_height_spring_key expanding_collapsing_height_end_listener_key From d21cc1f2cf1019e82ab1e2f6ba244c03ce1c2754 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 18:23:10 -0300 Subject: [PATCH 04/33] Update collpase animation screen --- .../animation/CollapseAnimationActivity.kt | 40 +++++------------ .../jerry/app/animation/SeekBarHelper.kt | 7 --- .../layout/activity_collapse_animation.xml | 4 +- .../main/res/layout/container_seek_bar.xml | 43 ------------------- app/src/main/res/values/strings.xml | 4 +- .../jerry/ExpandableAnimation.kt | 22 +++++----- .../alexandregpereira/jerry/FadeAnimation.kt | 16 +++---- .../alexandregpereira/jerry/ViewAnimation.kt | 4 +- .../jerry/WidthHeightViewProperty.kt | 7 ++- 9 files changed, 37 insertions(+), 110 deletions(-) delete mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/SeekBarHelper.kt delete mode 100644 app/src/main/res/layout/container_seek_bar.xml diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 2dd048f..4e747ca 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -3,19 +3,15 @@ package br.alexandregpereira.jerry.app.animation import android.content.Context import android.content.Intent import android.os.Bundle -import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeight import br.alexandregpereira.jerry.collapseHeightSpring -import br.alexandregpereira.jerry.collapseWidth -import br.alexandregpereira.jerry.expandHeight +import br.alexandregpereira.jerry.collapseWidthSpring import br.alexandregpereira.jerry.expandHeightSpring -import br.alexandregpereira.jerry.expandWidth +import br.alexandregpereira.jerry.expandWidthSpring import kotlinx.android.synthetic.main.activity_collapse_animation.* import kotlinx.android.synthetic.main.container_animation_info.view.* -import kotlinx.android.synthetic.main.container_seek_bar.* class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_animation) { @@ -35,18 +31,6 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a resources.getDimension(R.dimen.strong_elevation) ) - seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - seekBarValue.text = progress.getSeekBarAnimationDuration().run { - "$this ms" - } - } - - override fun onStartTrackingTouch(seekBar: SeekBar?) {} - - override fun onStopTrackingTouch(seekBar: SeekBar?) {} - }) - collapseTextButton.setOnClickListener { collapseTextView.collapseHeightSpring( onProgressChange = { interpolatedTime -> @@ -68,39 +52,35 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseFixedTextButton.setOnClickListener { - collapseFixedTextView.collapseHeight( - duration = seekBar.progress.getSeekBarAnimationDuration() - ) + collapseFixedTextView.collapseHeightSpring() } collapseExpandFixedTextButton.setOnClickListener { - collapseFixedTextView.expandHeight( - duration = seekBar.progress.getSeekBarAnimationDuration() - ) + collapseFixedTextView.expandHeightSpring() } collapseWidthTextButton.setOnClickListener { - collapseWidthTextView.collapseWidth() + collapseWidthTextView.collapseWidthSpring() } collapseExpandWidthTextButton.setOnClickListener { - collapseWidthTextView.expandWidth() + collapseWidthTextView.expandWidthSpring() } collapseMatchWidthButton.setOnClickListener { - collapseMatchWidthView.collapseWidth() + collapseMatchWidthView.collapseWidthSpring() } collapseExpandMatchWidthButton.setOnClickListener { - collapseMatchWidthView.expandWidth() + collapseMatchWidthView.expandWidthSpring() } collapseFixedWidthButton.setOnClickListener { - collapseFixedWidthView.collapseWidth() + collapseFixedWidthView.collapseWidthSpring() } collapseExpandFixedWidthButton.setOnClickListener { - collapseFixedWidthView.expandWidth() + collapseFixedWidthView.expandWidthSpring() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/SeekBarHelper.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/SeekBarHelper.kt deleted file mode 100644 index 4b0430a..0000000 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/SeekBarHelper.kt +++ /dev/null @@ -1,7 +0,0 @@ -package br.alexandregpereira.jerry.app.animation - -import br.alexandregpereira.jerry.ANIMATION_SHORT_TIME - -fun Int.getSeekBarAnimationDuration(): Long { - return this.toLong() + ANIMATION_SHORT_TIME -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_collapse_animation.xml b/app/src/main/res/layout/activity_collapse_animation.xml index 83b4da0..c7f89f9 100644 --- a/app/src/main/res/layout/activity_collapse_animation.xml +++ b/app/src/main/res/layout/activity_collapse_animation.xml @@ -12,8 +12,6 @@ android:paddingTop="@dimen/small" android:paddingBottom="@dimen/x_large"> - - + app:layout_constraintTop_toTopOf="parent" /> - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa68ab3..31e97cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,7 +36,7 @@ Change background color Change text color This text needs to show entirely - Animation duration - 200 ms + Animation stiffness + 600 \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index 4731e3f..90bc304 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -205,7 +205,7 @@ fun Animation.setAnimationListener( fun View.animateHeightVisibility( visible: Boolean, - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) { @@ -223,13 +223,13 @@ fun View.animateHeightVisibility( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.collapseHeightSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = collapseSpring( @@ -246,13 +246,13 @@ fun View.collapseHeightSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.collapseWidthSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = collapseSpring( @@ -270,13 +270,13 @@ fun View.collapseWidthSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.expandHeightSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = expandSpring( @@ -294,13 +294,13 @@ fun View.expandHeightSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.expandWidthSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = expandSpring( @@ -311,7 +311,7 @@ fun View.expandWidthSpring( ) internal fun View.collapseSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null @@ -337,7 +337,7 @@ internal fun View.collapseSpring( } internal fun View.expandSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, onProgressChange: ((progress: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index ba7940e..8fb9e36 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -137,13 +137,13 @@ private fun View.startFadeOutRunning() = setFadeInFadeOutRunning(POP_ANIMATION_M * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.goneFadeOutSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { hideFadeOutSpring(stiffness, ::gone, onAnimationEnd = onAnimationEnd) @@ -155,13 +155,13 @@ fun View.goneFadeOutSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.invisibleFadeOutSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { hideFadeOutSpring(stiffness, ::invisible, onAnimationEnd) @@ -174,13 +174,13 @@ fun View.invisibleFadeOutSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.visibleFadeInSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { if (isFadeInRunning() || (alpha == 1f && isVisible() && isFadeOutRunning().not())) { @@ -206,13 +206,13 @@ fun View.visibleFadeInSpring( * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_FAST_STIFFNESS]. + * [ANIMATION_STIFFNESS]. * @param onAnimationEnd The function to call when the animation is finished. * * @see [SpringAnimation] */ fun View.hideFadeOutSpring( - stiffness: Float = ANIMATION_FAST_STIFFNESS, + stiffness: Float = ANIMATION_STIFFNESS, hide: (() -> Unit), onAnimationEnd: (() -> Unit)? = null ) { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 9b4cdce..0013b28 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -9,7 +9,7 @@ import androidx.dynamicanimation.animation.SpringForce const val ANIMATION_SHORT_TIME = 200L -const val ANIMATION_FAST_STIFFNESS = 600f +const val ANIMATION_STIFFNESS = 500f /** * Used to clear the key to verify if a animation is running. @@ -45,7 +45,7 @@ fun View.spring( return springAnimation } -internal fun View.startSpringAnimation( +fun View.startSpringAnimation( key: Int, property: FloatPropertyCompat, targetValue: Float, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt index 30271c8..20f39ec 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt @@ -47,11 +47,10 @@ internal fun View.widthHeightViewProperty( } private fun setCollapsingValue(value: Float) { - if (value == 0f) { - setLayoutParamSize(originalValue, isHeight) - } else { - setLayoutParamSize(value.toInt(), isHeight) + val finalValue = value.toInt().let { + if (it == 0) 1 else it } + setLayoutParamSize(finalValue, isHeight) } private fun setExpandingValue(value: Float, targetValue: Int) { From 54f319e5db149d5e9e983167cb7e4fe3d2aac2b9 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 18:39:40 -0300 Subject: [PATCH 05/33] Create set text Fade Spring animation --- .../app/animation/FadeAnimationActivity.kt | 12 ++--- .../alexandregpereira/jerry/FadeAnimation.kt | 2 +- .../jerry/TextViewAnimation.kt | 45 +++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index ff6b957..c7e6d45 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -6,7 +6,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.invisibleFadeOutSpring -import br.alexandregpereira.jerry.setTextFade +import br.alexandregpereira.jerry.setTextFadeSpring import br.alexandregpereira.jerry.visibleFadeInSpring import kotlinx.android.synthetic.main.activity_fade_animation.* @@ -23,14 +23,14 @@ class FadeAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_fade_animation) val list = listOf( - "Fade text change 2", - "Fade text change 3", - "Fade text change 4", - "Fade text change 5", + "Fade text changeasaaaaa 2", + "Fade text change asda sdas 3", + "asdasd asdasdasd 4", + "Fade asdasdasddas change 5", "Fade text change 1", ).circularIterator() fadeTextButton.setOnClickListener { - fadeTextView.setTextFade(list.next()) + fadeTextView.setTextFadeSpring(list.next()) } goneTextButton.setOnClickListener { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index 8fb9e36..7332137 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -234,7 +234,7 @@ fun View.hideFadeOutSpring( ) } -private fun View.startFadeSpringAnimation( +internal fun View.startFadeSpringAnimation( targetValue: Float, stiffness: Float, onAnimationEnd: (() -> Unit)?, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt index d03fb73..89c7cde 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt @@ -3,6 +3,7 @@ package br.alexandregpereira.jerry import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.TextView +import androidx.dynamicanimation.animation.SpringAnimation /** * Uses the [setTextFade], [expandHeightFading] or [collapseHeightFading] animation methods @@ -75,3 +76,47 @@ fun TextView.setTextFade( ) } } + +/** + * Changes the text of the [TextView] using cross fade animation. + * + * @param text The new text of the TextView + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * + * @see [SpringAnimation] + */ +fun TextView.setTextFadeSpring( + text: String = "", + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + val textView = this + val oldText = textView.text.toString() + + if (oldText == text) return + + if (oldText.isEmpty()) { + textView.text = text + textView.alpha = 0f + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd + ) + return + } + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness * 1.5f + ) { + textView.text = text + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness * 1.5f, + onAnimationEnd = onAnimationEnd + ) + } +} From ef3be865e6e680ba754772a9945abe6730c569ef Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 21:54:01 -0300 Subject: [PATCH 06/33] Organize files --- .../jerry/ExpandableAnimation.kt | 186 ----------------- .../jerry/ExpandableFadingAnimation.kt | 2 +- .../jerry/ExpandableSpingAnimation.kt | 189 ++++++++++++++++++ .../alexandregpereira/jerry/FadeAnimation.kt | 136 ------------- .../jerry/FadeAnimationCommon.kt | 19 ++ .../jerry/FadeSpringAnimation.kt | 123 ++++++++++++ .../jerry/TextViewAnimation.kt | 45 ----- .../jerry/TextViewSpringAnimation.kt | 48 +++++ .../alexandregpereira/jerry/ViewAnimation.kt | 2 +- 9 files changed, 381 insertions(+), 369 deletions(-) create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index 90bc304..1797f5c 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -4,7 +4,6 @@ import android.view.View import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.Transformation -import androidx.dynamicanimation.animation.SpringAnimation /** * Animates expanding the height and changes the visibility status to VISIBLE. @@ -202,188 +201,3 @@ fun Animation.setAnimationListener( return this } - -fun View.animateHeightVisibility( - visible: Boolean, - stiffness: Float = ANIMATION_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (visible) { - expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) - } else { - expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) - } -} - -/** - * Animates collapsing the height and changes the visibility status to GONE. - * This animation handles double click. This method can be reverted in the middle of the animation - * if the [expandHeight] method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.collapseHeightSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = collapseSpring( - stiffness = stiffness, - isHeight = true, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -/** - * Animates collapsing the width and changes the visibility status to GONE. - * This animation handles double click. This method can be reverted in the middle of the animation - * if the [expandWidth] method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.collapseWidthSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = collapseSpring( - stiffness = stiffness, - isHeight = false, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -/** - * Animates expanding the height and changes the visibility status to VISIBLE. - * This animation handles double click. This method can be reverted in the middle of the animation - * if the [collapseHeight] method is called. Any alteration of the parent width during the this - * animation makes glitches in the animation. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.expandHeightSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = expandSpring( - stiffness = stiffness, - isHeight = true, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -/** - * Animates expanding the width and changes the visibility status to VISIBLE. - * This animation handles double click. This method can be reverted in the middle of the animation - * if the [collapseWidth] method is called. Any alteration of the parent width during the this animation - * makes glitches in the animation. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.expandWidthSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) = expandSpring( - stiffness = stiffness, - isHeight = false, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd -) - -internal fun View.collapseSpring( - stiffness: Float = ANIMATION_STIFFNESS, - isHeight: Boolean = true, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (isVisible().not() || isCollapsingRunning()) { - return - } - startCollapsingRunning() - - getOriginalValue(isHeight) - getCollapsingInitialValue(isHeight) - - startExpandCollapseSpringAnimation( - targetValue = 0f, - stiffness = stiffness, - isHeight = isHeight, - onProgressChange = onProgressChange, - onAnimationEnd = { - gone() - onAnimationEnd?.invoke() - } - ) -} - -internal fun View.expandSpring( - stiffness: Float = ANIMATION_STIFFNESS, - isHeight: Boolean = true, - onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null -) { - if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { - return - } - - val originalValue = getOriginalValue(isHeight) - val initialValue = (getLayoutParamSize(isHeight)).let { - if (it == originalValue || it < 0) 0 else it - } - val targetValue = getTargetValue(originalValue, isHeight) - - if (targetValue == null) { - finishExpandingCollapsingAnimation(onAnimationEnd) - return - } - startExpandingRunning() - - if (initialValue == 0) { - if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 - } - visible() - - startExpandCollapseSpringAnimation( - targetValue = targetValue.toFloat(), - stiffness = stiffness, - isHeight = isHeight, - onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd - ) -} - -private fun View.startExpandCollapseSpringAnimation( - targetValue: Float, - stiffness: Float, - isHeight: Boolean, - onProgressChange: ((progress: Float) -> Unit)?, - onAnimationEnd: (() -> Unit)?, -) = startSpringAnimation( - key = getExpandingCollapsingSpringKey(isHeight), - property = widthHeightViewProperty(isHeight, onProgressChange), - targetValue = targetValue, - stiffness = stiffness, - endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { - finishExpandingCollapsingAnimation(onAnimationEnd) - } -) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt index ebbb0fe..3e4bd39 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt @@ -87,4 +87,4 @@ private fun View.expandFading( expand(duration = duration / 2, isHeight = isHeight) { visibleFadeIn(duration = duration / 2, onAnimationEnd = onAnimationEnd) } -} \ No newline at end of file +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt new file mode 100644 index 0000000..377d55b --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt @@ -0,0 +1,189 @@ +package br.alexandregpereira.jerry + +import android.view.View +import androidx.dynamicanimation.animation.SpringAnimation + +fun View.animateHeightVisibility( + visible: Boolean, + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (visible) { + expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) + } else { + collapseHeightSpring(stiffness, onProgressChange, onAnimationEnd) + } +} + +/** + * Animates collapsing the height and changes the visibility status to GONE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [expandHeight] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.collapseHeightSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +/** + * Animates collapsing the width and changes the visibility status to GONE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [expandWidth] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.collapseWidthSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = collapseSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +/** + * Animates expanding the height and changes the visibility status to VISIBLE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [collapseHeight] method is called. Any alteration of the parent width during the this + * animation makes glitches in the animation. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.expandHeightSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = true, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +/** + * Animates expanding the width and changes the visibility status to VISIBLE. + * This animation handles double click. This method can be reverted in the middle of the animation + * if the [collapseWidth] method is called. Any alteration of the parent width during the this animation + * makes glitches in the animation. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.expandWidthSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) = expandSpring( + stiffness = stiffness, + isHeight = false, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd +) + +internal fun View.collapseSpring( + stiffness: Float = ANIMATION_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isVisible().not() || isCollapsingRunning()) { + return + } + startCollapsingRunning() + + getOriginalValue(isHeight) + getCollapsingInitialValue(isHeight) + + startExpandCollapseSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + isHeight = isHeight, + onProgressChange = onProgressChange, + onAnimationEnd = { + gone() + onAnimationEnd?.invoke() + } + ) +} + +internal fun View.expandSpring( + stiffness: Float = ANIMATION_STIFFNESS, + isHeight: Boolean = true, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { + return + } + + val originalValue = getOriginalValue(isHeight) + val initialValue = (getLayoutParamSize(isHeight)).let { + if (it == originalValue || it < 0) 0 else it + } + val targetValue = getTargetValue(originalValue, isHeight) + + if (targetValue == null) { + finishExpandingCollapsingAnimation(onAnimationEnd) + return + } + startExpandingRunning() + + if (initialValue == 0) { + if (isHeight) layoutParams.height = 1 else layoutParams.width = 1 + } + visible() + + startExpandCollapseSpringAnimation( + targetValue = targetValue.toFloat(), + stiffness = stiffness, + isHeight = isHeight, + onProgressChange = onProgressChange, + onAnimationEnd = onAnimationEnd + ) +} + +private fun View.startExpandCollapseSpringAnimation( + targetValue: Float, + stiffness: Float, + isHeight: Boolean, + onProgressChange: ((progress: Float) -> Unit)?, + onAnimationEnd: (() -> Unit)?, +) = startSpringAnimation( + key = getExpandingCollapsingSpringKey(isHeight), + property = widthHeightViewProperty(isHeight, onProgressChange), + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { + finishExpandingCollapsingAnimation(onAnimationEnd) + } +) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index 7332137..6d1766f 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -3,8 +3,6 @@ package br.alexandregpereira.jerry import android.view.View import android.view.ViewPropertyAnimator import android.view.animation.Interpolator -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.SpringAnimation /** * Change the visibility to GONE of the view using fade out animation. This method can be @@ -114,137 +112,3 @@ fun View.animateFadeVisibility( .setDuration(duration) .apply { interpolator?.let { setInterpolator(it) } } } - -private fun View.isFadeInFadeOutRunning(animationMode: Int): Boolean = - isAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) - -private fun View.setFadeInFadeOutRunning(animationMode: Int) = - setAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) - -fun View.isFadeInRunning() = isFadeInFadeOutRunning(ENTER_ANIMATION_MODE) - -fun View.isFadeOutRunning() = isFadeInFadeOutRunning(POP_ANIMATION_MODE) - -private fun View.clearFadeInFadeOutRunning() = setFadeInFadeOutRunning(NONE_ANIMATION_MODE) - -private fun View.startFadeInRunning() = setFadeInFadeOutRunning(ENTER_ANIMATION_MODE) - -private fun View.startFadeOutRunning() = setFadeInFadeOutRunning(POP_ANIMATION_MODE) - -/** - * Change the visibility to GONE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.goneFadeOutSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) { - hideFadeOutSpring(stiffness, ::gone, onAnimationEnd = onAnimationEnd) -} - -/** - * Change the visibility to INVISIBLE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.invisibleFadeOutSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) { - hideFadeOutSpring(stiffness, ::invisible, onAnimationEnd) -} - -/** - * Change the visibility to VISIBLE of the view using fade in animation. This method can be - * reverted in the middle of the animation if the [invisibleFadeOut] or [goneFadeOut] - * method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.visibleFadeInSpring( - stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) { - if (isFadeInRunning() || (alpha == 1f && isVisible() && isFadeOutRunning().not())) { - if (isFadeInRunning().not()) { - onAnimationEnd?.invoke() - } - return - } - startFadeInRunning() - if (alpha == 1f) alpha = 0f - visible() - - startFadeSpringAnimation( - targetValue = 1f, - stiffness = stiffness, - onAnimationEnd = onAnimationEnd - ) -} - -/** - * Start the fade out animation without changing the visibility status. The changes in the - * visibility status is delegate to the function [hide]. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.hideFadeOutSpring( - stiffness: Float = ANIMATION_STIFFNESS, - hide: (() -> Unit), - onAnimationEnd: (() -> Unit)? = null -) { - if (isVisible().not() || isFadeOutRunning()) { - if (isFadeOutRunning().not()) { - onAnimationEnd?.invoke() - } - return - } - startFadeOutRunning() - - startFadeSpringAnimation( - targetValue = 0f, - stiffness = stiffness, - onAnimationEnd = { - hide() - onAnimationEnd?.invoke() - } - ) -} - -internal fun View.startFadeSpringAnimation( - targetValue: Float, - stiffness: Float, - onAnimationEnd: (() -> Unit)?, -) = startSpringAnimation( - key = R.string.alpha_spring_key, - property = DynamicAnimation.ALPHA, - targetValue = targetValue, - stiffness = stiffness, - endListenerPair = R.string.alpha_end_listener_key to { - clearFadeInFadeOutRunning() - onAnimationEnd?.invoke() - } -) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt new file mode 100644 index 0000000..e8f6917 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt @@ -0,0 +1,19 @@ +package br.alexandregpereira.jerry + +import android.view.View + +internal fun View.isFadeInFadeOutRunning(animationMode: Int): Boolean = + isAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) + +internal fun View.setFadeInFadeOutRunning(animationMode: Int) = + setAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) + +fun View.isFadeInRunning() = isFadeInFadeOutRunning(ENTER_ANIMATION_MODE) + +fun View.isFadeOutRunning() = isFadeInFadeOutRunning(POP_ANIMATION_MODE) + +internal fun View.clearFadeInFadeOutRunning() = setFadeInFadeOutRunning(NONE_ANIMATION_MODE) + +internal fun View.startFadeInRunning() = setFadeInFadeOutRunning(ENTER_ANIMATION_MODE) + +internal fun View.startFadeOutRunning() = setFadeInFadeOutRunning(POP_ANIMATION_MODE) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt new file mode 100644 index 0000000..855751d --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -0,0 +1,123 @@ +package br.alexandregpereira.jerry + +import android.view.View +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringAnimation + +/** + * Change the visibility to GONE of the view using fade out animation. This method can be + * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.goneFadeOutSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + hideFadeOutSpring(stiffness, ::gone, onAnimationEnd = onAnimationEnd) +} + +/** + * Change the visibility to INVISIBLE of the view using fade out animation. This method can be + * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.invisibleFadeOutSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + hideFadeOutSpring(stiffness, ::invisible, onAnimationEnd) +} + +/** + * Change the visibility to VISIBLE of the view using fade in animation. This method can be + * reverted in the middle of the animation if the [invisibleFadeOut] or [goneFadeOut] + * method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.visibleFadeInSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + if (isFadeInRunning() || (alpha == 1f && isVisible() && isFadeOutRunning().not())) { + if (isFadeInRunning().not()) { + onAnimationEnd?.invoke() + } + return + } + startFadeInRunning() + if (alpha == 1f) alpha = 0f + visible() + + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd + ) +} + +/** + * Start the fade out animation without changing the visibility status. The changes in the + * visibility status is delegate to the function [hide]. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +fun View.hideFadeOutSpring( + stiffness: Float = ANIMATION_STIFFNESS, + hide: (() -> Unit), + onAnimationEnd: (() -> Unit)? = null +) { + if (isVisible().not() || isFadeOutRunning()) { + if (isFadeOutRunning().not()) { + onAnimationEnd?.invoke() + } + return + } + startFadeOutRunning() + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = { + hide() + onAnimationEnd?.invoke() + } + ) +} + +internal fun View.startFadeSpringAnimation( + targetValue: Float, + stiffness: Float, + onAnimationEnd: (() -> Unit)?, +) = startSpringAnimation( + key = R.string.alpha_spring_key, + property = DynamicAnimation.ALPHA, + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = R.string.alpha_end_listener_key to { + clearFadeInFadeOutRunning() + onAnimationEnd?.invoke() + } +) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt index 89c7cde..d03fb73 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt @@ -3,7 +3,6 @@ package br.alexandregpereira.jerry import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.TextView -import androidx.dynamicanimation.animation.SpringAnimation /** * Uses the [setTextFade], [expandHeightFading] or [collapseHeightFading] animation methods @@ -76,47 +75,3 @@ fun TextView.setTextFade( ) } } - -/** - * Changes the text of the [TextView] using cross fade animation. - * - * @param text The new text of the TextView - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * - * @see [SpringAnimation] - */ -fun TextView.setTextFadeSpring( - text: String = "", - stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) { - val textView = this - val oldText = textView.text.toString() - - if (oldText == text) return - - if (oldText.isEmpty()) { - textView.text = text - textView.alpha = 0f - startFadeSpringAnimation( - targetValue = 1f, - stiffness = stiffness, - onAnimationEnd = onAnimationEnd - ) - return - } - - startFadeSpringAnimation( - targetValue = 0f, - stiffness = stiffness * 1.5f - ) { - textView.text = text - startFadeSpringAnimation( - targetValue = 1f, - stiffness = stiffness * 1.5f, - onAnimationEnd = onAnimationEnd - ) - } -} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt new file mode 100644 index 0000000..2a0a319 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt @@ -0,0 +1,48 @@ +package br.alexandregpereira.jerry + +import android.widget.TextView +import androidx.dynamicanimation.animation.SpringAnimation + +/** + * Changes the text of the [TextView] using cross fade animation. + * + * @param text The new text of the TextView + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * + * @see [SpringAnimation] + */ +fun TextView.setTextFadeSpring( + text: String = "", + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + val textView = this + val oldText = textView.text.toString() + + if (oldText == text) return + + if (oldText.isEmpty()) { + textView.text = text + textView.alpha = 0f + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd + ) + return + } + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness * 1.5f + ) { + textView.text = text + startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness * 1.5f, + onAnimationEnd = onAnimationEnd + ) + } +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 0013b28..a086bff 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -9,7 +9,7 @@ import androidx.dynamicanimation.animation.SpringForce const val ANIMATION_SHORT_TIME = 200L -const val ANIMATION_STIFFNESS = 500f +const val ANIMATION_STIFFNESS = 600f /** * Used to clear the key to verify if a animation is running. From 859486033ed19b90d933ec5d58e133c1347dd2f4 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Sun, 11 Oct 2020 22:30:54 -0300 Subject: [PATCH 07/33] Adjust code --- .../animation/CollapseAnimationActivity.kt | 11 +++ .../layout/activity_collapse_animation.xml | 27 ++++--- .../jerry/ExpandableAnimation.kt | 34 ++++---- .../jerry/ExpandableAnimationCommon.kt | 6 +- .../jerry/ExpandableSpingAnimation.kt | 20 +++-- .../java/br/alexandregpereira/jerry/Scroll.kt | 24 ------ .../br/alexandregpereira/jerry/TextView.kt | 22 ----- .../alexandregpereira/jerry/ValueAnimator.kt | 81 ------------------- .../jerry/WidthHeightViewProperty.kt | 8 +- 9 files changed, 65 insertions(+), 168 deletions(-) delete mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/Scroll.kt delete mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/TextView.kt delete mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ValueAnimator.kt diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 4e747ca..d74ad10 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -5,6 +5,8 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat +import br.alexandregpereira.jerry.animateHeightVisibility +import br.alexandregpereira.jerry.animateWidthVisibility import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.collapseHeightSpring import br.alexandregpereira.jerry.collapseWidthSpring @@ -16,6 +18,7 @@ import kotlinx.android.synthetic.main.container_animation_info.view.* class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_animation) { private var collapseTextViewCount = 1 + private var collapseMatchWidthViewVisible = true companion object { fun getStartIntent(context: Context): Intent { @@ -67,6 +70,14 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a collapseWidthTextView.expandWidthSpring() } + collapseReverseMatchWidthButton.setOnClickListener { + collapseMatchWidthView.animateWidthVisibility( + visible = collapseMatchWidthViewVisible.not().also { + collapseMatchWidthViewVisible = it + } + ) + } + collapseMatchWidthButton.setOnClickListener { collapseMatchWidthView.collapseWidthSpring() } diff --git a/app/src/main/res/layout/activity_collapse_animation.xml b/app/src/main/res/layout/activity_collapse_animation.xml index c7f89f9..15f8ba1 100644 --- a/app/src/main/res/layout/activity_collapse_animation.xml +++ b/app/src/main/res/layout/activity_collapse_animation.xml @@ -95,23 +95,17 @@ + app:layout_constraintTop_toBottomOf="@id/collapseWidthTextView" /> @@ -121,11 +115,11 @@ android:layout_width="match_parent" android:layout_height="60dp" android:background="@color/backgroundColor" - app:layout_constraintBottom_toTopOf="@id/collapseMatchWidthButton" + app:layout_constraintBottom_toTopOf="@id/collapseReverseMatchWidthButton" app:layout_constraintTop_toBottomOf="@id/collapseExpandWidthTextButton" /> + + + app:layout_constraintTop_toBottomOf="@id/collapseMatchWidthButton" /> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = expand( duration, isHeight = false, - onAnimationStart = null, - onProgressChange = null, + onProgressChange = onProgressChange, onAnimationEnd = onAnimationEnd ) @@ -61,7 +60,6 @@ fun View.collapseHeight( ) = collapse( duration, isHeight = true, - onAnimationStart = null, onProgressChange = onProgressChange, onAnimationEnd = onAnimationEnd ) @@ -76,12 +74,12 @@ fun View.collapseHeight( */ fun View.collapseWidth( duration: Long = ANIMATION_SHORT_TIME, + onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) = collapse( duration, isHeight = false, - onAnimationStart = null, - onProgressChange = null, + onProgressChange = onProgressChange, onAnimationEnd = onAnimationEnd ) @@ -89,7 +87,6 @@ internal fun View.collapse( duration: Long = ANIMATION_SHORT_TIME, isHeight: Boolean = true, onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, - onAnimationStart: (() -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) { if (isVisible().not() || isCollapsingRunning()) { @@ -97,7 +94,7 @@ internal fun View.collapse( } startCollapsingRunning() - val originalValue = getOriginalValue(isHeight) + val originalValue = getWidthOrHeightOriginalValue(isHeight) val initialValue = getCollapsingInitialValue(isHeight) val animation = object : Animation() { @@ -118,10 +115,12 @@ internal fun View.collapse( return true } } - animation.setAnimationListener(onEnd = { - gone() - finishExpandingCollapsingAnimation(onAnimationEnd) - }, onStart = onAnimationStart) + animation.setAnimationListener( + onEnd = { + gone() + finishExpandingCollapsingAnimation(onAnimationEnd) + } + ) animation.duration = duration startAnimation(animation) @@ -131,14 +130,13 @@ internal fun View.expand( duration: Long = ANIMATION_SHORT_TIME, isHeight: Boolean = true, onProgressChange: ((interpolatedTime: Float) -> Unit)? = null, - onAnimationStart: (() -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) { if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { return } - val originalValue = getOriginalValue(isHeight) + val originalValue = getWidthOrHeightOriginalValue(isHeight) val initialValue = (getLayoutParamSize(isHeight)).let { if (it == originalValue || it < 0) 0 else it } @@ -175,9 +173,11 @@ internal fun View.expand( return true } } - animation.setAnimationListener(onEnd = { - finishExpandingCollapsingAnimation(onAnimationEnd) - }, onStart = onAnimationStart) + animation.setAnimationListener( + onEnd = { + finishExpandingCollapsingAnimation(onAnimationEnd) + } + ) animation.duration = duration startAnimation(animation) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index 959b323..b7b7b7d 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -62,8 +62,8 @@ internal fun View.getCollapsingInitialValue(isHeight: Boolean): Int { } } -internal fun View.getOriginalValue(isHeight: Boolean): Int { - val key = getOriginalValueKey(isHeight) +internal fun View.getWidthOrHeightOriginalValue(isHeight: Boolean): Int { + val key = getWidthOrHeightOriginalValueKey(isHeight) return runCatching { getTag(key) as Int }.getOrElse { @@ -73,7 +73,7 @@ internal fun View.getOriginalValue(isHeight: Boolean): Int { } } -internal fun getOriginalValueKey(isHeight: Boolean): Int { +internal fun getWidthOrHeightOriginalValueKey(isHeight: Boolean): Int { return if (isHeight) { R.string.expanding_collapsing_height_original_value_key } else { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt index 377d55b..adfa774 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt @@ -16,6 +16,19 @@ fun View.animateHeightVisibility( } } +fun View.animateWidthVisibility( + visible: Boolean, + stiffness: Float = ANIMATION_STIFFNESS, + onProgressChange: ((progress: Float) -> Unit)? = null, + onAnimationEnd: (() -> Unit)? = null +) { + if (visible) { + expandWidthSpring(stiffness, onProgressChange, onAnimationEnd) + } else { + collapseWidthSpring(stiffness, onProgressChange, onAnimationEnd) + } +} + /** * Animates collapsing the height and changes the visibility status to GONE. * This animation handles double click. This method can be reverted in the middle of the animation @@ -120,10 +133,7 @@ internal fun View.collapseSpring( return } startCollapsingRunning() - - getOriginalValue(isHeight) - getCollapsingInitialValue(isHeight) - + getWidthOrHeightOriginalValue(isHeight) startExpandCollapseSpringAnimation( targetValue = 0f, stiffness = stiffness, @@ -146,7 +156,7 @@ internal fun View.expandSpring( return } - val originalValue = getOriginalValue(isHeight) + val originalValue = getWidthOrHeightOriginalValue(isHeight) val initialValue = (getLayoutParamSize(isHeight)).let { if (it == originalValue || it < 0) 0 else it } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/Scroll.kt b/jerry/src/main/java/br/alexandregpereira/jerry/Scroll.kt deleted file mode 100644 index 36fcfae..0000000 --- a/jerry/src/main/java/br/alexandregpereira/jerry/Scroll.kt +++ /dev/null @@ -1,24 +0,0 @@ -package br.alexandregpereira.jerry - -import androidx.recyclerview.widget.RecyclerView - -/** - * Function helper to add a [RecyclerView.OnScrollListener] using the Kotlin extension. - */ -fun RecyclerView.addOnScrollListenerWithScrolledPixels(block: (dx: Int, dy: Int) -> Unit) { - addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - block(dx, dy) - } - }) -} - -/** - * Function helper to add a [RecyclerView.OnScrollListener] using the Kotlin extension. This is the - * same as [addOnScrollListenerWithScrolledPixels], except the [block] function do not receive the - * scrolled pixels. - */ -fun RecyclerView.addOnScrollListener(block: () -> Unit) { - addOnScrollListenerWithScrolledPixels { _, _ -> block() } -} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextView.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextView.kt deleted file mode 100644 index 2c9fdcf..0000000 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextView.kt +++ /dev/null @@ -1,22 +0,0 @@ -package br.alexandregpereira.jerry - -import android.graphics.Paint -import android.widget.TextView - -/** - * Set the [Paint.STRIKE_THRU_TEXT_FLAG] flag to a [TextView]. - */ -fun TextView.setStrikeThrough() { - paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG -} - -/** - * Set the text of a [TextView] and change the [TextView] visibility depending of the [text] value. - * If [text] is null or empty, the text is gone, else is visible. - * - * @see [visibleOrGone] - */ -fun TextView.setTextOrGone(text: String?) { - visibleOrGone(visible = text != null && text.trim().isNotEmpty()) - setText(text) -} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ValueAnimator.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ValueAnimator.kt deleted file mode 100644 index 76ea466..0000000 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ValueAnimator.kt +++ /dev/null @@ -1,81 +0,0 @@ -package br.alexandregpereira.jerry - -import android.animation.TypeEvaluator -import android.animation.ValueAnimator - -/** - * Goes from a origin value [fromValue] to the final value [toValue] using the [ValueAnimator]. - * The lambda function [onValueChange] is called when the animation value is updated. - * When the value arrived at the [toValue], the [onAnimationEnd] is called to indicates that the - * animation is finished. - * - * @param duration Indicates the time that the [ValueAnimator] will take to goes from the - * [fromValue] to the [toValue]. - * - * @see [ValueAnimator] - */ -fun startValueAnimator( - fromValue: Float, - toValue: Float, - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null, - onValueChange: (Float) -> Unit -): ValueAnimator { - - return ValueAnimator.ofFloat(fromValue, toValue) - .startValueAnimator( - toValue, - duration, - onAnimationEnd = onAnimationEnd, - onValueChange = onValueChange - ) -} - -/** - * Goes from a origin value [fromValue] to the final value [toValue] using the [ValueAnimator]. - * The lambda function [onValueChange] is called when the animation value is updated. - * When the value arrived at the [toValue], the [onAnimationEnd] is called to indicates that the - * animation is finished. This is the same as [startValueAnimator], except this function receives - * a [TypeEvaluator] to allow developers to create animations on arbitrary property types, - * by allowing them to supply custom evaluators for types that are not automatically understood and - * used by the animation system. - * - * @param duration Indicates the time that the [ValueAnimator] will take to goes from the - * [fromValue] to the [toValue]. - * - * @see [ValueAnimator] - * @see [TypeEvaluator] - */ -fun TypeEvaluator<*>.startValueAnimator( - fromValue: Float, - toValue: Float, - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null, - onValueChange: (Float) -> Unit -): ValueAnimator { - - return ValueAnimator.ofObject(this, fromValue.toInt(), toValue.toInt()) - .startValueAnimator( - toValue, - duration, - onAnimationEnd = onAnimationEnd, - onValueChange = onValueChange - ) -} - -private fun ValueAnimator.startValueAnimator( - toValue: Float, - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null, - onValueChange: (Float) -> Unit -): ValueAnimator { - this.duration = duration - - this.addUpdateListener { valueAnimator -> - val animatedValue = valueAnimator.animatedValue as Float - onValueChange(animatedValue) - if (animatedValue == toValue) onAnimationEnd?.invoke() - } - - return this.apply { start() } -} \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt index 20f39ec..5857435 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt @@ -10,7 +10,7 @@ internal fun View.widthHeightViewProperty( ) = object : FloatPropertyCompat("viewProperty") { private val originalValue: Int - get() = getOriginalValue(isHeight) + get() = getWidthOrHeightOriginalValue(isHeight) private val initialValue: Int get() = if (isExpandingRunning()) { @@ -42,7 +42,9 @@ internal fun View.widthHeightViewProperty( setCollapsingValue(value) } - onProgressChange?.invoke(progressFunction(value)) + onProgressChange?.let { + it(progressFunction(value)) + } requestLayout() } @@ -79,4 +81,4 @@ internal fun View.widthHeightViewProperty( } } } -} \ No newline at end of file +} From 9924415a9bf25d1987818a171cb92d6608e3ef0d Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Mon, 12 Oct 2020 12:24:12 -0300 Subject: [PATCH 08/33] Adjust code, Create expandFading spring animation --- .../animation/CollapseAnimationActivity.kt | 2 +- .../CollapseFadingAnimationActivity.kt | 28 ++--- .../app/animation/ExpandAnimationActivity.kt | 48 +++++--- .../ExpandFadingAnimationActivity.kt | 30 ++--- .../app/animation/FadeAnimationActivity.kt | 24 ++-- .../TextExpandableAnimationActivity.kt | 34 ++++-- .../layout/activity_collapse_animation.xml | 7 +- .../res/layout/activity_expand_animation.xml | 109 +++++++++++++++-- .../activity_text_expandable_animation.xml | 2 +- app/src/main/res/values/strings.xml | 4 + jerry/build.gradle | 1 - .../jerry/ExpandableAnimation.kt | 4 +- .../jerry/ExpandableAnimationCommon.kt | 36 ++---- .../jerry/ExpandableFadingSpringAnimation.kt | 111 ++++++++++++++++++ .../jerry/ExpandableSpingAnimation.kt | 8 +- .../jerry/FadeSpringAnimation.kt | 4 +- .../jerry/TextViewSpringAnimation.kt | 35 ++++++ .../jerry/WidthHeightViewProperty.kt | 52 ++++---- 18 files changed, 390 insertions(+), 149 deletions(-) create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index d74ad10..1b946e0 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat -import br.alexandregpereira.jerry.animateHeightVisibility import br.alexandregpereira.jerry.animateWidthVisibility import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.collapseHeightSpring @@ -34,6 +33,7 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a resources.getDimension(R.dimen.strong_elevation) ) + collapseTextView.expandHeightSpring() collapseTextButton.setOnClickListener { collapseTextView.collapseHeightSpring( onProgressChange = { interpolatedTime -> diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt index 4af9ba3..19fede0 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightFading -import br.alexandregpereira.jerry.collapseWidthFading -import br.alexandregpereira.jerry.expandHeightFading -import br.alexandregpereira.jerry.expandWidthFading +import br.alexandregpereira.jerry.collapseHeightFadingSpring +import br.alexandregpereira.jerry.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandWidthFadingSpring import kotlinx.android.synthetic.main.activity_collapse_fading_animation.* class CollapseFadingAnimationActivity : AppCompatActivity() { @@ -24,43 +24,43 @@ class CollapseFadingAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_collapse_fading_animation) collapseFadingTextButton.setOnClickListener { - collapseFadingTextView.collapseHeightFading() + collapseFadingTextView.collapseHeightFadingSpring() } collapseExpandFadingTextButton.setOnClickListener { - collapseFadingTextView.expandHeightFading() + collapseFadingTextView.expandHeightFadingSpring() } collapseFixedFadingTextButton.setOnClickListener { - collapseFixedFadingTextView.collapseHeightFading() + collapseFixedFadingTextView.collapseHeightFadingSpring() } collapseExpandFixedFadingTextButton.setOnClickListener { - collapseFixedFadingTextView.expandHeightFading() + collapseFixedFadingTextView.expandHeightFadingSpring() } collapseWidthFadingTextButton.setOnClickListener { - collapseWidthFadingTextView.collapseWidthFading() + collapseWidthFadingTextView.collapseWidthFadingSpring() } collapseExpandWidthFadingTextButton.setOnClickListener { - collapseWidthFadingTextView.expandWidthFading() + collapseWidthFadingTextView.expandWidthFadingSpring() } collapseMatchWidthFadingButton.setOnClickListener { - collapseMatchWidthFadingView.collapseWidthFading() + collapseMatchWidthFadingView.collapseWidthFadingSpring() } collapseExpandMatchWidthFadingButton.setOnClickListener { - collapseMatchWidthFadingView.expandWidthFading() + collapseMatchWidthFadingView.expandWidthFadingSpring() } collapseFixedWidthFadingButton.setOnClickListener { - collapseFixedWidthFadingView.collapseWidthFading() + collapseFixedWidthFadingView.collapseWidthFadingSpring() } collapseExpandFixedWidthFadingButton.setOnClickListener { - collapseFixedWidthFadingView.expandWidthFading() + collapseFixedWidthFadingView.expandWidthFadingSpring() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt index 5b5e237..86c997c 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeight -import br.alexandregpereira.jerry.collapseWidth -import br.alexandregpereira.jerry.expandHeight -import br.alexandregpereira.jerry.expandWidth +import br.alexandregpereira.jerry.collapseHeightSpring +import br.alexandregpereira.jerry.collapseWidthSpring +import br.alexandregpereira.jerry.expandHeightSpring +import br.alexandregpereira.jerry.expandWidthSpring import kotlinx.android.synthetic.main.activity_expand_animation.* class ExpandAnimationActivity : AppCompatActivity() { @@ -24,51 +24,67 @@ class ExpandAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_expand_animation) expandTextButton.setOnClickListener { - expandTextView.expandHeight() + expandTextView.expandHeightSpring() } expandCollapseTextButton.setOnClickListener { - expandTextView.collapseHeight() + expandTextView.collapseHeightSpring() } expandWidthTextButton.setOnClickListener { - expandWidthTextView.expandWidth() + expandWidthTextView.expandWidthSpring() } expandCollapseWidthTextButton.setOnClickListener { - expandWidthTextView.collapseWidth() + expandWidthTextView.collapseWidthSpring() } expandFixedTextButton.setOnClickListener { - expandFixedTextView.expandHeight() + expandFixedTextView.expandHeightSpring() } expandCollapseFixedTextButton.setOnClickListener { - expandFixedTextView.collapseHeight() + expandFixedTextView.collapseHeightSpring() } expandWidthTextButton.setOnClickListener { - expandWidthTextView.expandWidth() + expandWidthTextView.expandWidthSpring() } expandCollapseWidthTextButton.setOnClickListener { - expandWidthTextView.collapseWidth() + expandWidthTextView.collapseWidthSpring() } expandMatchWidthButton.setOnClickListener { - expandMatchWidthView.expandWidth() + expandMatchWidthView.expandWidthSpring() } expandCollapseMatchWidthButton.setOnClickListener { - expandMatchWidthView.collapseWidth() + expandMatchWidthView.collapseWidthSpring() + } + + expand0dpWidthButton.setOnClickListener { + expand0dpWidthView.expandWidthSpring() + } + + expandCollapse0dpWidthButton.setOnClickListener { + expand0dpWidthView.collapseWidthSpring() + } + + expandHalf0dpWidthButton.setOnClickListener { + expandHalf0dpWidthView.expandWidthSpring() + } + + expandCollapseHalf0dpWidthButton.setOnClickListener { + expandHalf0dpWidthView.collapseWidthSpring() } expandFixedWidthButton.setOnClickListener { - expandFixedWidthView.expandWidth() + expandFixedWidthView.expandWidthSpring() } expandCollapseFixedWidthButton.setOnClickListener { - expandFixedWidthView.collapseWidth() + expandFixedWidthView.collapseWidthSpring() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt index 71c2a27..f726ad8 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt @@ -5,10 +5,12 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightFading +import br.alexandregpereira.jerry.collapseHeightFadingSpring import br.alexandregpereira.jerry.collapseWidthFading -import br.alexandregpereira.jerry.expandHeightFading +import br.alexandregpereira.jerry.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandHeightFadingSpring import br.alexandregpereira.jerry.expandWidthFading +import br.alexandregpereira.jerry.expandWidthFadingSpring import kotlinx.android.synthetic.main.activity_expand_fading_animation.* class ExpandFadingAnimationActivity : AppCompatActivity() { @@ -24,51 +26,51 @@ class ExpandFadingAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_expand_fading_animation) expandFadingTextButton.setOnClickListener { - expandFadingTextView.expandHeightFading() + expandFadingTextView.expandHeightFadingSpring() } expandCollapseFadingTextButton.setOnClickListener { - expandFadingTextView.collapseHeightFading() + expandFadingTextView.collapseHeightFadingSpring() } expandWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.expandWidthFading() + expandWidthFadingTextView.expandWidthFadingSpring() } expandCollapseWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.collapseWidthFading() + expandWidthFadingTextView.collapseWidthFadingSpring() } expandFixedFadingTextButton.setOnClickListener { - expandFixedFadingTextView.expandHeightFading() + expandFixedFadingTextView.expandHeightFadingSpring() } expandCollapseFixedFadingTextButton.setOnClickListener { - expandFixedFadingTextView.collapseHeightFading() + expandFixedFadingTextView.collapseHeightFadingSpring() } expandWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.expandWidthFading() + expandWidthFadingTextView.expandWidthFadingSpring() } expandCollapseWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.collapseWidthFading() + expandWidthFadingTextView.collapseWidthFadingSpring() } expandMatchWidthFadingButton.setOnClickListener { - expandMatchWidthFadingView.expandWidthFading() + expandMatchWidthFadingView.expandWidthFadingSpring() } expandCollapseMatchWidthFadingButton.setOnClickListener { - expandMatchWidthFadingView.collapseWidthFading() + expandMatchWidthFadingView.collapseWidthFadingSpring() } expandFixedWidthFadingButton.setOnClickListener { - expandFixedWidthFadingView.expandWidthFading() + expandFixedWidthFadingView.expandWidthFadingSpring() } expandCollapseFixedWidthFadingButton.setOnClickListener { - expandFixedWidthFadingView.collapseWidthFading() + expandFixedWidthFadingView.collapseWidthFadingSpring() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index c7e6d45..7a985f5 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -49,20 +49,20 @@ class FadeAnimationActivity : AppCompatActivity() { visibleTextView.invisibleFadeOutSpring() } } +} - fun List.circularIterator(): Iterator { - val size = this.size - return object : MutableIterator { - var i = 0 - override fun hasNext(): Boolean { - return i < size - } - - override fun next(): T { - return this@circularIterator[i++ % size] - } +fun List.circularIterator(): Iterator { + val size = this.size + return object : MutableIterator { + var i = 0 + override fun hasNext(): Boolean { + return i < size + } - override fun remove() {} + override fun next(): T { + return this@circularIterator[i++ % size] } + + override fun remove() {} } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt index 2de9a9c..8d2788d 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt @@ -1,12 +1,13 @@ package br.alexandregpereira.jerry.app.animation -import br.alexandregpereira.jerry.setTextExpandableAnimation -import br.alexandregpereira.jerry.app.R import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Handler +import android.os.Looper import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.setTextExpandableSpring import kotlinx.android.synthetic.main.activity_text_expandable_animation.* class TextExpandableAnimationActivity : AppCompatActivity() { @@ -22,45 +23,52 @@ class TextExpandableAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_text_expandable_animation) expandCancelTextButton.setOnClickListener { - expandCancelTextView.setTextExpandableAnimation( + expandCancelTextView.setTextExpandableSpring( getString(R.string.expand) ) - Handler().postDelayed({ - expandCancelTextView.setTextExpandableAnimation(null) + Handler(Looper.getMainLooper()).postDelayed({ + expandCancelTextView.setTextExpandableSpring(null) }, 100) } collapseCancelTextButton.setOnClickListener { - collapseCancelTextView.setTextExpandableAnimation(null) - Handler().postDelayed({ - collapseCancelTextView.setTextExpandableAnimation( + collapseCancelTextView.setTextExpandableSpring(null) + Handler(Looper.getMainLooper()).postDelayed({ + collapseCancelTextView.setTextExpandableSpring( getString(R.string.collapse) ) }, 100) } expandFadingTextButton.setOnClickListener { - expandFadingTextView.setTextExpandableAnimation( + expandFadingTextView.setTextExpandableSpring( getString(R.string.expand_collapse_animation) ) } expandCollapseFadingTextButton.setOnClickListener { - expandFadingTextView.setTextExpandableAnimation(null) + expandFadingTextView.setTextExpandableSpring(null) } collapseFadingTextButton.setOnClickListener { - collapseFadingTextView.setTextExpandableAnimation(null) + collapseFadingTextView.setTextExpandableSpring(null) } collapseExpandFadingTextButton.setOnClickListener { - collapseFadingTextView.setTextExpandableAnimation( + collapseFadingTextView.setTextExpandableSpring( getString(R.string.collapse_expand_animation) ) } + val list = listOf( + "Fade text changeasaaaaa 2", + "Fade text change asda sdas 3", + "asdasd asdasdasd 4", + "Fade asdasdasddas change 5", + "Fade text change 1", + ).circularIterator() textFadeTextButton.setOnClickListener { - textFadeTextView.setTextExpandableAnimation("${System.currentTimeMillis()}") + textFadeTextView.setTextExpandableSpring(list.next()) } } } diff --git a/app/src/main/res/layout/activity_collapse_animation.xml b/app/src/main/res/layout/activity_collapse_animation.xml index 15f8ba1..7305df5 100644 --- a/app/src/main/res/layout/activity_collapse_animation.xml +++ b/app/src/main/res/layout/activity_collapse_animation.xml @@ -28,8 +28,10 @@ android:background="@color/backgroundColor" android:padding="@dimen/medium" android:text="@string/collapse_expand_animation" + android:visibility="gone" app:layout_constraintBottom_toTopOf="@id/collapseTextButton" - app:layout_constraintTop_toBottomOf="@id/collapseLabel" /> + app:layout_constraintTop_toBottomOf="@id/collapseLabel" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/collapseWidthTextView" + app:layout_goneMarginTop="66dp" /> - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + + + + + + + + + + + @@ -152,6 +236,17 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + android:text="@string/fade_text_change_button" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31e97cb..c1da198 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,6 +21,10 @@ Expand wrap Width animation Collapse match Width animation Expand match Width animation + Collapse 0dp Width animation + Expand 0dp Width animation + Collapse half 0dp Width animation + Expand half 0dp Width animation Collapse fixed Width animation Expand fixed Width animation Expand Fading diff --git a/jerry/build.gradle b/jerry/build.gradle index 5c66764..31f7ecb 100644 --- a/jerry/build.gradle +++ b/jerry/build.gradle @@ -24,6 +24,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' } \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt index 8a5a8d8..237d3df 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt @@ -94,7 +94,7 @@ internal fun View.collapse( } startCollapsingRunning() - val originalValue = getWidthOrHeightOriginalValue(isHeight) + val originalValue = getOrStoreWidthOrHeightOriginalValue(isHeight) val initialValue = getCollapsingInitialValue(isHeight) val animation = object : Animation() { @@ -136,7 +136,7 @@ internal fun View.expand( return } - val originalValue = getWidthOrHeightOriginalValue(isHeight) + val originalValue = getOrStoreWidthOrHeightOriginalValue(isHeight) val initialValue = (getLayoutParamSize(isHeight)).let { if (it == originalValue || it < 0) 0 else it } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index b7b7b7d..2a64cf7 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -1,6 +1,7 @@ package br.alexandregpereira.jerry import android.view.View +import android.view.View.MeasureSpec import android.view.ViewGroup internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { @@ -62,7 +63,7 @@ internal fun View.getCollapsingInitialValue(isHeight: Boolean): Int { } } -internal fun View.getWidthOrHeightOriginalValue(isHeight: Boolean): Int { +internal fun View.getOrStoreWidthOrHeightOriginalValue(isHeight: Boolean): Int { val key = getWidthOrHeightOriginalValueKey(isHeight) return runCatching { getTag(key) as Int @@ -94,35 +95,14 @@ internal fun View.getTargetValue(originalValue: Int, isHeight: Boolean): Int? { return null } - if (originalValue == ViewGroup.LayoutParams.MATCH_PARENT) return parentSize - - val widthMeasureSize: Int - val heightMeasureSize: Int - - val widthMeasureSpecMode: Int - val heightMeasureSpecMode: Int - - if (isHeight) { - widthMeasureSize = parentSize - heightMeasureSize = 0 - - widthMeasureSpecMode = View.MeasureSpec.EXACTLY - heightMeasureSpecMode = View.MeasureSpec.UNSPECIFIED - } else { - widthMeasureSize = 0 - heightMeasureSize = parentSize - - widthMeasureSpecMode = View.MeasureSpec.UNSPECIFIED - heightMeasureSpecMode = View.MeasureSpec.EXACTLY + if (originalValue == ViewGroup.LayoutParams.MATCH_PARENT || originalValue == 0) { + return parentSize } - val widthMeasureSpec = - View.MeasureSpec.makeMeasureSpec(widthMeasureSize, widthMeasureSpecMode) - - val heightMeasureSpec = - View.MeasureSpec.makeMeasureSpec(heightMeasureSize, heightMeasureSpecMode) - - measure(widthMeasureSpec, heightMeasureSpec) + measure( + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + ) return if (isHeight) measuredHeight else measuredWidth } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt new file mode 100644 index 0000000..ffc4605 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt @@ -0,0 +1,111 @@ +package br.alexandregpereira.jerry + +import android.view.View +import androidx.dynamicanimation.animation.SpringAnimation + +/** + * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [collapseHeightFading] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished + * + * @see [SpringAnimation] + */ +fun View.expandHeightFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) = expandFadingSpring(stiffness, isHeight = true, onAnimationEnd) + +/** + * Uses the [expandWidth] and [visibleFadeIn] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [collapseWidthFading] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished + * + * @see [SpringAnimation] + */ +fun View.expandWidthFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) = expandFadingSpring(stiffness, isHeight = false, onAnimationEnd) + +/** + * Uses the [hideFadeOut] and [collapseHeight] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [expandHeightFading] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished + * + * @see [SpringAnimation] + */ +fun View.collapseHeightFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) = collapseFadingSpring(stiffness, isHeight = true, onAnimationEnd) + +/** + * Uses the [hideFadeOut] and [collapseWidth] animations in sequence. This animation + * handles double click. This method can be reverted in the middle of the animation if the + * [expandWidthFading] method is called. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished + * + * @see [SpringAnimation] + */ +fun View.collapseWidthFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) = collapseFadingSpring(stiffness, isHeight = false, onAnimationEnd) + +private fun View.collapseFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + isHeight: Boolean = true, + onAnimationEnd: (() -> Unit)? = null +) { + if (isExpandingRunning()) { + collapseSpring(isHeight = isHeight, onAnimationEnd = onAnimationEnd) + return + } + + hideFadeOutSpring(stiffness = stiffness * 2f) { + collapseSpring( + stiffness = stiffness * 2f, + isHeight = isHeight, + onAnimationEnd = onAnimationEnd + ) + } +} + +private fun View.expandFadingSpring( + stiffness: Float = ANIMATION_STIFFNESS, + isHeight: Boolean = true, + onAnimationEnd: (() -> Unit)? = null +) { + if (alpha == 1f && (isVisible() && isCollapsingRunning().not())) { + return + } + if (alpha == 1f) alpha = 0f + + if (alpha > 0f && alpha < 1f) { + visibleFadeInSpring(onAnimationEnd = onAnimationEnd) + return + } + + expandSpring(stiffness = stiffness * 2f, isHeight = isHeight) { + visibleFadeInSpring(stiffness = stiffness * 2f, onAnimationEnd = onAnimationEnd) + } +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt index adfa774..bc2e270 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt @@ -102,8 +102,8 @@ fun View.expandHeightSpring( /** * Animates expanding the width and changes the visibility status to VISIBLE. * This animation handles double click. This method can be reverted in the middle of the animation - * if the [collapseWidth] method is called. Any alteration of the parent width during the this animation - * makes glitches in the animation. + * if the [collapseWidth] method is called. Any alteration of the parent width during the this + * animation makes glitches in the animation. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -133,7 +133,7 @@ internal fun View.collapseSpring( return } startCollapsingRunning() - getWidthOrHeightOriginalValue(isHeight) + getOrStoreWidthOrHeightOriginalValue(isHeight) startExpandCollapseSpringAnimation( targetValue = 0f, stiffness = stiffness, @@ -156,7 +156,7 @@ internal fun View.expandSpring( return } - val originalValue = getWidthOrHeightOriginalValue(isHeight) + val originalValue = getOrStoreWidthOrHeightOriginalValue(isHeight) val initialValue = (getLayoutParamSize(isHeight)).let { if (it == originalValue || it < 0) 0 else it } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index 855751d..0f1066a 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -86,7 +86,7 @@ fun View.visibleFadeInSpring( */ fun View.hideFadeOutSpring( stiffness: Float = ANIMATION_STIFFNESS, - hide: (() -> Unit), + hide: (() -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null ) { if (isVisible().not() || isFadeOutRunning()) { @@ -101,7 +101,7 @@ fun View.hideFadeOutSpring( targetValue = 0f, stiffness = stiffness, onAnimationEnd = { - hide() + hide?.invoke() onAnimationEnd?.invoke() } ) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt index 2a0a319..5adb4bf 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt @@ -3,6 +3,41 @@ package br.alexandregpereira.jerry import android.widget.TextView import androidx.dynamicanimation.animation.SpringAnimation +/** + * Uses the [setTextFade], [expandHeightFading] or [collapseHeightFading] animation methods + * depending of the TextView state. If the new text is null or empty, the [collapseHeightFading] + * is used, else if the TextView is already visible, the [setTextFade] is used, else the + * [expandHeightFading] is used. + * + * @param text The new text of the TextView + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * + * @see [SpringAnimation] + */ +fun TextView.setTextExpandableSpring( + text: String?, + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + if (text == null || text.trim().isEmpty()) { + collapseHeightFadingSpring(stiffness = stiffness, onAnimationEnd = onAnimationEnd) + return + } + + if (isVisible() && isCollapsingRunning().not() && isFadeOutRunning().not()) { + setTextFadeSpring( + text, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd + ) + return + } + setText(text) + expandHeightFadingSpring(stiffness = stiffness, onAnimationEnd = onAnimationEnd) +} + /** * Changes the text of the [TextView] using cross fade animation. * diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt index 5857435..77f959e 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt @@ -10,7 +10,7 @@ internal fun View.widthHeightViewProperty( ) = object : FloatPropertyCompat("viewProperty") { private val originalValue: Int - get() = getWidthOrHeightOriginalValue(isHeight) + get() = getOrStoreWidthOrHeightOriginalValue(isHeight) private val initialValue: Int get() = if (isExpandingRunning()) { @@ -21,54 +21,42 @@ internal fun View.widthHeightViewProperty( getCollapsingInitialValue(isHeight) } - private val targetValue: Int? - get() = if (isExpandingRunning()) { + private var targetValue: Int? = null + + private val progressFunction by lazy { createProgressFunction() } + + override fun getValue(view: View?): Float { + targetValue = if (isExpandingRunning()) { getTargetValue(originalValue, isHeight) } else { null } - - private val progressFunction = createProgressFunction() - - override fun getValue(view: View?): Float { return initialValue.toFloat() } override fun setValue(view: View?, value: Float) { val targetValue = targetValue - if (targetValue != null) { - setExpandingValue(value, targetValue) - } else { - setCollapsingValue(value) + val finalValue = value.toInt().let { + if (it == 0) { + 1 + } else { + if (it == targetValue && + originalValue == ViewGroup.LayoutParams.WRAP_CONTENT + ) { + ViewGroup.LayoutParams.WRAP_CONTENT + } else { + it + } + } } + setLayoutParamSize(finalValue, isHeight) onProgressChange?.let { it(progressFunction(value)) } requestLayout() } - private fun setCollapsingValue(value: Float) { - val finalValue = value.toInt().let { - if (it == 0) 1 else it - } - setLayoutParamSize(finalValue, isHeight) - } - - private fun setExpandingValue(value: Float, targetValue: Int) { - val finalValue = if (value == 0f) 1f else { - if (value == targetValue.toFloat() && - originalValue == ViewGroup.LayoutParams.WRAP_CONTENT - ) { - ViewGroup.LayoutParams.WRAP_CONTENT - } else { - value - } - } - - setLayoutParamSize(finalValue.toInt(), isHeight) - } - private fun createProgressFunction(): (Float) -> Float { val fullValue = targetValue ?: initialValue return if (isExpandingRunning()) { From 423ba407b82fff84f6069de2a5776fb6e317737f Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Mon, 12 Oct 2020 12:58:46 -0300 Subject: [PATCH 09/33] Change fade functions names --- .../app/animation/FadeAnimationActivity.kt | 12 ++--- .../res/layout/activity_fade_animation.xml | 47 +++++++++++++------ app/src/main/res/values/strings.xml | 8 ++-- .../jerry/ExpandableFadingAnimation.kt | 8 ++-- .../jerry/ExpandableFadingSpringAnimation.kt | 32 +++++++++++-- .../alexandregpereira/jerry/FadeAnimation.kt | 27 +++-------- .../jerry/FadeSpringAnimation.kt | 38 +++++++-------- .../jerry/TextViewSpringAnimation.kt | 8 ++-- 8 files changed, 101 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index 7a985f5..4bde739 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -5,9 +5,9 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.invisibleFadeOutSpring +import br.alexandregpereira.jerry.fadeOutSpring import br.alexandregpereira.jerry.setTextFadeSpring -import br.alexandregpereira.jerry.visibleFadeInSpring +import br.alexandregpereira.jerry.fadeInSpring import kotlinx.android.synthetic.main.activity_fade_animation.* class FadeAnimationActivity : AppCompatActivity() { @@ -34,19 +34,19 @@ class FadeAnimationActivity : AppCompatActivity() { } goneTextButton.setOnClickListener { - goneTextView.invisibleFadeOutSpring() + goneTextView.fadeOutSpring() } goneVisibleTextButton.setOnClickListener { - goneTextView.visibleFadeInSpring() + goneTextView.fadeInSpring() } visibleTextButton.setOnClickListener { - visibleTextView.visibleFadeInSpring() + visibleTextView.fadeInSpring() } visibleInvisibleTextButton.setOnClickListener { - visibleTextView.invisibleFadeOutSpring() + visibleTextView.fadeOutSpring() } } } diff --git a/app/src/main/res/layout/activity_fade_animation.xml b/app/src/main/res/layout/activity_fade_animation.xml index 442ffed..60900b2 100644 --- a/app/src/main/res/layout/activity_fade_animation.xml +++ b/app/src/main/res/layout/activity_fade_animation.xml @@ -1,9 +1,10 @@ - + android:text="@string/fade" + app:layout_constraintBottom_toTopOf="@id/fadeTextView" + app:layout_constraintTop_toTopOf="parent" /> + android:text="@string/fade_text_change" + android:textColor="@color/textSecondaryColor" + app:layout_constraintBottom_toTopOf="@id/fadeTextButton" + app:layout_constraintTop_toBottomOf="@id/fadeLabel" /> + android:text="@string/fade_text_change_button" + app:layout_constraintBottom_toTopOf="@id/goneTextView" + app:layout_constraintTop_toBottomOf="@id/fadeTextView" /> + android:text="@string/invisible_visible_fade_out" + app:layout_constraintBottom_toTopOf="@id/goneTextButton" + app:layout_constraintTop_toBottomOf="@id/fadeTextButton" /> + android:text="@string/invisible_fade_out" + app:layout_constraintTop_toBottomOf="@id/goneTextView" + app:layout_goneMarginTop="56dp" /> + android:text="@string/visible_fade_in" + app:layout_constraintBottom_toTopOf="@id/visibleTextView" + app:layout_constraintTop_toBottomOf="@id/goneTextButton" /> + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@id/visibleTextButton" + app:layout_constraintTop_toBottomOf="@id/goneVisibleTextButton" /> + android:text="@string/visible_fade_in" + app:layout_constraintTop_toBottomOf="@id/visibleTextView" + app:layout_goneMarginTop="56dp" /> + android:text="@string/invisible_fade_out" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/visibleTextButton" /> - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1da198..092dc2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,10 +4,10 @@ Fade Fade text change 1 Fade text change - Invisible/visible fade out - Invisible fade out - Visible fade in - Visible/invisible fade in + Fade out/Fade in + Fade out + Fade in + Fade in/Fade out Collapse Fading Collapse/expand animation Collapse/Expand animation diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt index 3e4bd39..558b15a 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt @@ -3,7 +3,7 @@ package br.alexandregpereira.jerry import android.view.View /** - * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation + * Uses the [expandHeight] and [fadeIn] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the * [collapseHeightFading] method is called. * @@ -16,7 +16,7 @@ fun View.expandHeightFading( ) = expandFading(duration, isHeight = true, onAnimationEnd) /** - * Uses the [expandWidth] and [visibleFadeIn] animations in sequence. This animation + * Uses the [expandWidth] and [fadeIn] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the * [collapseWidthFading] method is called. * @@ -80,11 +80,11 @@ private fun View.expandFading( if (alpha == 1f) alpha = 0f if (alpha > 0f && alpha < 1f) { - visibleFadeIn(duration = duration, onAnimationEnd = onAnimationEnd) + fadeIn(duration = duration, onAnimationEnd = onAnimationEnd) return } expand(duration = duration / 2, isHeight = isHeight) { - visibleFadeIn(duration = duration / 2, onAnimationEnd = onAnimationEnd) + fadeIn(duration = duration / 2, onAnimationEnd = onAnimationEnd) } } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt index ffc4605..886b018 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt @@ -3,8 +3,32 @@ package br.alexandregpereira.jerry import android.view.View import androidx.dynamicanimation.animation.SpringAnimation +fun View.animateHeightFadingVisibility( + visible: Boolean, + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + if (visible) { + expandHeightFadingSpring(stiffness, onAnimationEnd) + } else { + collapseHeightFadingSpring(stiffness, onAnimationEnd) + } +} + +fun View.animateWidthFadingVisibility( + visible: Boolean, + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null +) { + if (visible) { + expandWidthFadingSpring(stiffness, onAnimationEnd) + } else { + collapseWidthFadingSpring(stiffness, onAnimationEnd) + } +} + /** - * Uses the [expandHeight] and [visibleFadeIn] animations in sequence. This animation + * Uses the [expandHeight] and [fadeIn] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the * [collapseHeightFading] method is called. * @@ -21,7 +45,7 @@ fun View.expandHeightFadingSpring( ) = expandFadingSpring(stiffness, isHeight = true, onAnimationEnd) /** - * Uses the [expandWidth] and [visibleFadeIn] animations in sequence. This animation + * Uses the [expandWidth] and [fadeIn] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the * [collapseWidthFading] method is called. * @@ -101,11 +125,11 @@ private fun View.expandFadingSpring( if (alpha == 1f) alpha = 0f if (alpha > 0f && alpha < 1f) { - visibleFadeInSpring(onAnimationEnd = onAnimationEnd) + fadeInSpring(onAnimationEnd = onAnimationEnd) return } expandSpring(stiffness = stiffness * 2f, isHeight = isHeight) { - visibleFadeInSpring(stiffness = stiffness * 2f, onAnimationEnd = onAnimationEnd) + fadeInSpring(stiffness = stiffness * 2f, onAnimationEnd = onAnimationEnd) } } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index 6d1766f..def2666 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -6,41 +6,27 @@ import android.view.animation.Interpolator /** * Change the visibility to GONE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * reverted in the middle of the animation if the [fadeIn] method is called. * * @param duration The duration of the animation * @param onAnimationEnd The function to call when the animation is finished */ -fun View.goneFadeOut( +fun View.fadeOut( duration: Long = ANIMATION_SHORT_TIME, onAnimationEnd: (() -> Unit)? = null ) { hideFadeOut(duration, ::gone, onAnimationEnd = onAnimationEnd) } -/** - * Change the visibility to INVISIBLE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. - * - * @param duration The duration of the animation - * @param onAnimationEnd The function to call when the animation is finished - */ -fun View.invisibleFadeOut( - duration: Long = ANIMATION_SHORT_TIME, - onAnimationEnd: (() -> Unit)? = null -) { - hideFadeOut(duration, ::invisible, onAnimationEnd) -} - /** * Change the visibility to VISIBLE of the view using fade in animation. This method can be - * reverted in the middle of the animation if the [invisibleFadeOut] or [goneFadeOut] + * reverted in the middle of the animation if the [fadeOut] * method is called. * * @param duration The duration of the animation * @param onAnimationEnd The function to call when the animation is finished */ -fun View.visibleFadeIn( +fun View.fadeIn( duration: Long = ANIMATION_SHORT_TIME, onAnimationEnd: (() -> Unit)? = null ) { @@ -66,10 +52,9 @@ fun View.visibleFadeIn( * @param duration The duration of the animation * @param onAnimationEnd The function to call when the animation is finished * - * @see [goneFadeOut] - * @see [invisibleFadeOut] + * @see [fadeOut] */ -fun View.hideFadeOut( +internal fun View.hideFadeOut( duration: Long = ANIMATION_SHORT_TIME, hide: (() -> Unit)? = null, onAnimationEnd: (() -> Unit)? = null diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index 0f1066a..ddd03d3 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -4,27 +4,21 @@ import android.view.View import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation -/** - * Change the visibility to GONE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -fun View.goneFadeOutSpring( +fun View.animateAlphaVisibility( + visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { - hideFadeOutSpring(stiffness, ::gone, onAnimationEnd = onAnimationEnd) + if (visible) { + fadeInSpring(stiffness, onAnimationEnd = onAnimationEnd) + } else { + fadeOutSpring(stiffness, onAnimationEnd = onAnimationEnd) + } } /** - * Change the visibility to INVISIBLE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [visibleFadeIn] method is called. + * Change the visibility to GONE of the view using fade out animation. This method can be + * reverted in the middle of the animation if the [fadeIn] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -33,16 +27,16 @@ fun View.goneFadeOutSpring( * * @see [SpringAnimation] */ -fun View.invisibleFadeOutSpring( +fun View.fadeOutSpring( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { - hideFadeOutSpring(stiffness, ::invisible, onAnimationEnd) + hideFadeOutSpring(stiffness, hide = ::gone, onAnimationEnd = onAnimationEnd) } /** * Change the visibility to VISIBLE of the view using fade in animation. This method can be - * reverted in the middle of the animation if the [invisibleFadeOut] or [goneFadeOut] + * reverted in the middle of the animation if the [fadeOut] * method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to @@ -52,7 +46,7 @@ fun View.invisibleFadeOutSpring( * * @see [SpringAnimation] */ -fun View.visibleFadeInSpring( +fun View.fadeInSpring( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: (() -> Unit)? = null ) { @@ -84,10 +78,10 @@ fun View.visibleFadeInSpring( * * @see [SpringAnimation] */ -fun View.hideFadeOutSpring( - stiffness: Float = ANIMATION_STIFFNESS, +internal fun View.hideFadeOutSpring( + stiffness: Float, hide: (() -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: (() -> Unit)? ) { if (isVisible().not() || isFadeOutRunning()) { if (isFadeOutRunning().not()) { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt index 5adb4bf..5afc600 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt @@ -4,10 +4,10 @@ import android.widget.TextView import androidx.dynamicanimation.animation.SpringAnimation /** - * Uses the [setTextFade], [expandHeightFading] or [collapseHeightFading] animation methods - * depending of the TextView state. If the new text is null or empty, the [collapseHeightFading] - * is used, else if the TextView is already visible, the [setTextFade] is used, else the - * [expandHeightFading] is used. + * Uses the [setTextFadeSpring], [expandHeightFadingSpring] or [collapseHeightFadingSpring] + * animation methods depending of the TextView state. If the new text is null or empty, + * the [collapseHeightFadingSpring] is used, else if the TextView is already visible, + * the [setTextFadeSpring] is used, else the [expandHeightFadingSpring] is used. * * @param text The new text of the TextView * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to From 159ff26abe4212ce385e2e08585ab3cd7a1f75cb Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 05:46:46 -0300 Subject: [PATCH 10/33] RecyclerView item animator and elevation animation --- app/src/main/AndroidManifest.xml | 2 + .../jerry/app/MainActivity.kt | 16 +- .../jerry/app/RecyclerViewActivity.kt | 111 +++ .../jerry/app/SpringItemAnimator.kt | 44 + .../jerry/app/widgets/Drawable.kt | 14 +- .../jerry/app/widgets/SecondaryButton.kt | 2 +- .../res/layout/activity_recycler_view.xml | 50 ++ jerry/build.gradle | 1 + .../jerry/BaseItemAnimator.java | 763 ++++++++++++++++++ .../jerry/ElevationSpringAnimation.kt | 37 + .../jerry/FadeSpringAnimation.kt | 6 +- .../alexandregpereira/jerry/ViewAnimation.kt | 2 +- jerry/src/main/res/values/strings.xml | 3 + 13 files changed, 1038 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt create mode 100644 app/src/main/res/layout/activity_recycler_view.xml create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b6666f6..f28b886 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,8 @@ + + \ No newline at end of file diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt index 33c9425..5b2b788 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt @@ -7,8 +7,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import br.alexandregpereira.jerry.app.animation.* -import br.alexandregpereira.jerry.app.widgets.setMaterialShapeDrawable +import br.alexandregpereira.jerry.app.animation.CollapseAnimationActivity +import br.alexandregpereira.jerry.app.animation.CollapseFadingAnimationActivity +import br.alexandregpereira.jerry.app.animation.ExpandAnimationActivity +import br.alexandregpereira.jerry.app.animation.ExpandFadingAnimationActivity +import br.alexandregpereira.jerry.app.animation.FadeAnimationActivity +import br.alexandregpereira.jerry.app.animation.TextExpandableAnimationActivity +import br.alexandregpereira.jerry.app.widgets.configMaterialShapeDrawable import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { @@ -38,6 +43,9 @@ class MainActivity : AppCompatActivity() { AnimationComponent.TEXT_EXPANDABLE.ordinal -> startActivity( TextExpandableAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.RECYCLER.ordinal -> startActivity( + RecyclerViewActivity.getStartIntent(this@MainActivity) + ) } } } @@ -49,7 +57,7 @@ fun getAnimationComponentsName(): List { } enum class AnimationComponent { - COLLAPSE, COLLAPSE_FADING, EXPAND, EXPAND_FADING, FADE, TEXT_EXPANDABLE + COLLAPSE, COLLAPSE_FADING, EXPAND, EXPAND_FADING, FADE, TEXT_EXPANDABLE, RECYCLER } class MainAdapter( @@ -78,7 +86,7 @@ class MainAdapter( } setTextColor(ContextCompat.getColor(context, R.color.textSecondaryColor)) - setMaterialShapeDrawable() + configMaterialShapeDrawable() }) } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt new file mode 100644 index 0000000..c36ea4d --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -0,0 +1,111 @@ +package br.alexandregpereira.jerry.app + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import br.alexandregpereira.jerry.app.widgets.setMaterialShapeDrawable +import br.alexandregpereira.jerry.dpToPx +import kotlinx.android.synthetic.main.activity_recycler_view.* +import java.util.* + +class RecyclerViewActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, RecyclerViewActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_recycler_view) + + val adapter = ExampleAdapter() + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + recyclerView.itemAnimator = SpringItemAnimator() + } + + val list = (0..2).map { + Item(id = it, value = it.toString()) + } + button.setOnClickListener { + adapter.submitList(list.shuffled()) + } + + button2.setOnClickListener { + adapter.submitList(list.map { + it.copy(value = "${UUID.randomUUID().toString().substring(0..10)} ${it.value}") + }) + } + + button3.setOnClickListener { + adapter.submitList(listOf()) + } + } +} + +class ExampleAdapter : ListAdapter(DiffUtil) { + + inner class ExampleViewHolder( + private val view: View + ) : RecyclerView.ViewHolder(view) { + + fun bind(item: Item) { + if (view is TextView) { + view.text = item.value + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder { + return ExampleViewHolder( + AppCompatTextView(parent.context).apply { + val padding = resources.getDimensionPixelOffset(R.dimen.text_padding) + layoutParams = ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 100.dpToPx(resources) + ).apply { + setMargins(padding / 2, padding / 2, padding / 2, padding / 2) + } + setPadding(padding, padding, padding, padding) + gravity = Gravity.CENTER_VERTICAL + setTextColor(ContextCompat.getColor(context, R.color.textSecondaryColor)) + setMaterialShapeDrawable(ContextCompat.getColor(context, R.color.backgroundHelperColor)) + } + ) + } + + override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} + +object DiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { + return oldItem == newItem + } + +} + +data class Item( + val id: Int, + val value: String +) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt new file mode 100644 index 0000000..fd9f573 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -0,0 +1,44 @@ +package br.alexandregpereira.jerry.app + +import androidx.annotation.RequiresApi +import androidx.recyclerview.widget.RecyclerView +import br.alexandregpereira.jerry.BaseItemAnimator +import br.alexandregpereira.jerry.dpToPx +import br.alexandregpereira.jerry.startFadeSpringAnimation +import br.alexandregpereira.jerry.startSpringElevation + +@RequiresApi(21) +class SpringItemAnimator : BaseItemAnimator() { + + override fun preAnimateAdd(holder: RecyclerView.ViewHolder): Boolean { + holder.itemView.alpha = 0f + holder.itemView.elevation = 0f + return true + } + + override fun startAddAnimation( + holder: RecyclerView.ViewHolder?, + onAnimationEndListener: OnAnimationEndListener + ): Boolean { + holder?.itemView?.startFadeSpringAnimation(1f) { + holder.itemView.startSpringElevation(4f.dpToPx(holder.itemView.resources)) { + onAnimationEndListener.onAnimationEnd() + } + } + return true + } + + override fun startRemoveAnimation( + holder: RecyclerView.ViewHolder?, + onAnimationEndListener: OnAnimationEndListener + ): Boolean { + holder?.itemView?.startSpringElevation(0f.dpToPx(holder.itemView.resources)) { + holder.itemView.startFadeSpringAnimation(0f) { + holder.itemView.alpha = 1f + holder.itemView.elevation = 4f + onAnimationEndListener.onAnimationEnd() + } + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt index 6767880..e05ee05 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/Drawable.kt @@ -32,8 +32,7 @@ fun View.setRippleDrawable(contentDrawable: Drawable): View { } } -fun View.setMaterialShapeDrawable(): View { - val cornerSize = resources.getDimension(R.dimen.corner_size) +fun View.configMaterialShapeDrawable(): View { val padding = resources.getDimensionPixelOffset(R.dimen.text_padding) layoutParams = ViewGroup.MarginLayoutParams( @@ -44,12 +43,19 @@ fun View.setMaterialShapeDrawable(): View { } setPadding(padding, padding, padding, padding) + return setMaterialShapeDrawable() +} + +fun View.setMaterialShapeDrawable( + color: Int = Color.WHITE +): View { + val cornerSize = resources.getDimension(R.dimen.corner_size) val shapeAppearanceModel = ShapeAppearanceModel.builder() .setAllCornerSizes(cornerSize).build() val materialShapeDrawable = MaterialShapeDrawable(shapeAppearanceModel).apply { - this.fillColor = ColorStateList.valueOf(Color.WHITE) - this.elevation = resources.getDimension(R.dimen.strong_elevation) + this.fillColor = ColorStateList.valueOf(color) + this.elevation = resources.getDimension(R.dimen.low_elevation) } ViewCompat.setElevation(this, resources.getDimension(R.dimen.strong_elevation)) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/SecondaryButton.kt b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/SecondaryButton.kt index 3c06025..c1b25b4 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/widgets/SecondaryButton.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/widgets/SecondaryButton.kt @@ -13,7 +13,7 @@ class SecondaryButton @JvmOverloads constructor( ) : AppCompatTextView(context, attrs, defStyleAttr) { init { - setMaterialShapeDrawable() + configMaterialShapeDrawable() setTextColor(ContextCompat.getColor(context, R.color.textColor)) textAlignment = TEXT_ALIGNMENT_CENTER } diff --git a/app/src/main/res/layout/activity_recycler_view.xml b/app/src/main/res/layout/activity_recycler_view.xml new file mode 100644 index 0000000..7bc9cab --- /dev/null +++ b/app/src/main/res/layout/activity_recycler_view.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/jerry/build.gradle b/jerry/build.gradle index 31f7ecb..5c66764 100644 --- a/jerry/build.gradle +++ b/jerry/build.gradle @@ -24,5 +24,6 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' } \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java new file mode 100644 index 0000000..e9fef7b --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java @@ -0,0 +1,763 @@ +package br.alexandregpereira.jerry; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.View; +import android.view.ViewPropertyAnimator; + +import androidx.annotation.NonNull; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * This implementation of {@link RecyclerView.ItemAnimator} provides basic + * animations on remove, add, and move events that happen to the items in + * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. + * + * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) + */ +public abstract class BaseItemAnimator extends SimpleItemAnimator { + private static final boolean DEBUG = false; + + private ArrayList mPendingRemovals = new ArrayList<>(); + private ArrayList mPendingAdditions = new ArrayList<>(); + private ArrayList mPendingMoves = new ArrayList<>(); + private ArrayList mPendingChanges = new ArrayList<>(); + + ArrayList> mAdditionsList = new ArrayList<>(); + ArrayList> mMovesList = new ArrayList<>(); + ArrayList> mChangesList = new ArrayList<>(); + + ArrayList mAddAnimations = new ArrayList<>(); + ArrayList mMoveAnimations = new ArrayList<>(); + ArrayList mRemoveAnimations = new ArrayList<>(); + ArrayList mChangeAnimations = new ArrayList<>(); + + protected boolean preAnimateRemove(RecyclerView.ViewHolder holder) { + return false; + } + + protected boolean preAnimateAdd(RecyclerView.ViewHolder holder) { + return false; + } + + protected boolean startRemoveAnimation(RecyclerView.ViewHolder holder, OnAnimationEndListener onAnimationEndListener) { + return false; + } + + protected boolean startAddAnimation( + RecyclerView.ViewHolder holder, + @NonNull OnAnimationEndListener onAnimationEndListener + ) { + return false; + } + + protected boolean startOldHolderChangeAnimation( + RecyclerView.ViewHolder oldHolder, + int fromX, + int fromY, + int toX, + int toY, + @NonNull OnAnimationEndListener onAnimationEndListener + ) { + return false; + } + + protected boolean startNewHolderChangeAnimation( + RecyclerView.ViewHolder newHolder, + @NonNull OnAnimationEndListener onAnimationEndListener + ) { + return false; + } + + protected boolean startMoveAnimation( + RecyclerView.ViewHolder holder, + int fromX, + int fromY, + int toX, + int toY, + @NonNull OnAnimationEndListener onAnimationEndListener + ) { + return false; + } + + public interface OnAnimationEndListener { + void onAnimationEnd(); + } + + private static class MoveInfo { + public RecyclerView.ViewHolder holder; + public int fromX, fromY, toX, toY; + + MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { + this.holder = holder; + this.fromX = fromX; + this.fromY = fromY; + this.toX = toX; + this.toY = toY; + } + } + + private static class ChangeInfo { + public RecyclerView.ViewHolder oldHolder, newHolder; + public int fromX, fromY, toX, toY; + + private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) { + this.oldHolder = oldHolder; + this.newHolder = newHolder; + } + + ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, + int fromX, int fromY, int toX, int toY) { + this(oldHolder, newHolder); + this.fromX = fromX; + this.fromY = fromY; + this.toX = toX; + this.toY = toY; + } + + @NotNull + @Override + public String toString() { + return "ChangeInfo{" + + "oldHolder=" + oldHolder + + ", newHolder=" + newHolder + + ", fromX=" + fromX + + ", fromY=" + fromY + + ", toX=" + toX + + ", toY=" + toY + + '}'; + } + } + + @Override + public void runPendingAnimations() { + boolean removalsPending = !mPendingRemovals.isEmpty(); + boolean movesPending = !mPendingMoves.isEmpty(); + boolean changesPending = !mPendingChanges.isEmpty(); + boolean additionsPending = !mPendingAdditions.isEmpty(); + if (!removalsPending && !movesPending && !additionsPending && !changesPending) { + // nothing to animate + return; + } + // First, remove stuff + for (RecyclerView.ViewHolder holder : mPendingRemovals) { + animateRemoveImpl(holder); + } + mPendingRemovals.clear(); + // Next, move stuff + if (movesPending) { + final ArrayList moves = new ArrayList<>(mPendingMoves); + mMovesList.add(moves); + mPendingMoves.clear(); + Runnable mover = new Runnable() { + @Override + public void run() { + for (MoveInfo moveInfo : moves) { + animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, + moveInfo.toX, moveInfo.toY); + } + moves.clear(); + mMovesList.remove(moves); + } + }; + if (removalsPending) { + View view = moves.get(0).holder.itemView; + ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); + } else { + mover.run(); + } + } + // Next, change stuff, to run in parallel with move animations + if (changesPending) { + final ArrayList changes = new ArrayList<>(mPendingChanges); + mChangesList.add(changes); + mPendingChanges.clear(); + Runnable changer = new Runnable() { + @Override + public void run() { + for (ChangeInfo change : changes) { + animateChangeImpl(change); + } + changes.clear(); + mChangesList.remove(changes); + } + }; + if (removalsPending) { + RecyclerView.ViewHolder holder = changes.get(0).oldHolder; + ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); + } else { + changer.run(); + } + } + // Next, add stuff + if (additionsPending) { + final ArrayList additions = new ArrayList<>(mPendingAdditions); + mAdditionsList.add(additions); + mPendingAdditions.clear(); + Runnable adder = new Runnable() { + @Override + public void run() { + for (RecyclerView.ViewHolder holder : additions) { + animateAddImpl(holder); + } + additions.clear(); + mAdditionsList.remove(additions); + } + }; + if (removalsPending || movesPending || changesPending) { + long removeDuration = removalsPending ? getRemoveDuration() : 0; + long moveDuration = movesPending ? getMoveDuration() : 0; + long changeDuration = changesPending ? getChangeDuration() : 0; + long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); + View view = additions.get(0).itemView; + ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); + } else { + adder.run(); + } + } + } + + @Override + public boolean animateRemove(final RecyclerView.ViewHolder holder) { + resetAnimation(holder); + preAnimateRemove(holder); + mPendingRemovals.add(holder); + return true; + } + + private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { + mRemoveAnimations.add(holder); + if (startRemoveAnimation(holder, new OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + dispatchRemoveFinished(holder); + mRemoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + })) { + return; + } + final View view = holder.itemView; + final ViewPropertyAnimator animation = view.animate(); + animation.setDuration(getRemoveDuration()).alpha(0).setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchRemoveStarting(holder); + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + view.setAlpha(1); + dispatchRemoveFinished(holder); + mRemoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + }).start(); + } + + @Override + public boolean animateAdd(final RecyclerView.ViewHolder holder) { + resetAnimation(holder); + if (!preAnimateAdd(holder)) { + holder.itemView.setAlpha(0); + } + mPendingAdditions.add(holder); + return true; + } + + void animateAddImpl(final RecyclerView.ViewHolder holder) { + mAddAnimations.add(holder); + if (startAddAnimation(holder, new OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + dispatchAddFinished(holder); + mAddAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + })) { + return; + } + final View view = holder.itemView; + final ViewPropertyAnimator animation = view.animate(); + animation.alpha(1).setDuration(getAddDuration()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchAddStarting(holder); + } + + @Override + public void onAnimationCancel(Animator animator) { + view.setAlpha(1); + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + dispatchAddFinished(holder); + mAddAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + }).start(); + } + + @Override + public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, + int toX, int toY) { + final View view = holder.itemView; + fromX += (int) holder.itemView.getTranslationX(); + fromY += (int) holder.itemView.getTranslationY(); + resetAnimation(holder); + int deltaX = toX - fromX; + int deltaY = toY - fromY; + if (deltaX == 0 && deltaY == 0) { + dispatchMoveFinished(holder); + return false; + } + if (deltaX != 0) { + view.setTranslationX(-deltaX); + } + if (deltaY != 0) { + view.setTranslationY(-deltaY); + } + mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); + return true; + } + + void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { + mMoveAnimations.add(holder); + if (startMoveAnimation(holder, fromX, fromY, toX, toY, new OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + dispatchMoveFinished(holder); + mMoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + })) { + return; + } + final View view = holder.itemView; + final int deltaX = toX - fromX; + final int deltaY = toY - fromY; + if (deltaX != 0) { + view.animate().translationX(0); + } + if (deltaY != 0) { + view.animate().translationY(0); + } + // TODO: make EndActions end listeners instead, since end actions aren't called when + // vpas are canceled (and can't end them. why?) + // need listener functionality in VPACompat for this. Ick. + final ViewPropertyAnimator animation = view.animate(); + animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchMoveStarting(holder); + } + + @Override + public void onAnimationCancel(Animator animator) { + if (deltaX != 0) { + view.setTranslationX(0); + } + if (deltaY != 0) { + view.setTranslationY(0); + } + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + dispatchMoveFinished(holder); + mMoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + }).start(); + } + + @Override + public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, + int fromX, int fromY, int toX, int toY) { + if (oldHolder == newHolder) { + // Don't know how to run change animations when the same view holder is re-used. + // run a move animation to handle position changes. + return animateMove(oldHolder, fromX, fromY, toX, toY); + } + final float prevTranslationX = oldHolder.itemView.getTranslationX(); + final float prevTranslationY = oldHolder.itemView.getTranslationY(); + final float prevAlpha = oldHolder.itemView.getAlpha(); + resetAnimation(oldHolder); + int deltaX = (int) (toX - fromX - prevTranslationX); + int deltaY = (int) (toY - fromY - prevTranslationY); + // recover prev translation state after ending animation + oldHolder.itemView.setTranslationX(prevTranslationX); + oldHolder.itemView.setTranslationY(prevTranslationY); + oldHolder.itemView.setAlpha(prevAlpha); + if (newHolder != null) { + // carry over translation values + resetAnimation(newHolder); + newHolder.itemView.setTranslationX(-deltaX); + newHolder.itemView.setTranslationY(-deltaY); + newHolder.itemView.setAlpha(0); + } + mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); + return true; + } + + void animateChangeImpl(final ChangeInfo changeInfo) { + final RecyclerView.ViewHolder holder = changeInfo.oldHolder; + final View view = holder == null ? null : holder.itemView; + final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; + final View newView = newHolder != null ? newHolder.itemView : null; + + + if (view != null) { + mChangeAnimations.add(changeInfo.oldHolder); + final boolean animateChange = startOldHolderChangeAnimation( + holder, + changeInfo.fromX, + changeInfo.fromY, + changeInfo.toX, + changeInfo.toY, + new OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + dispatchChangeFinished(changeInfo.oldHolder, true); + mChangeAnimations.remove(changeInfo.oldHolder); + dispatchFinishedWhenDone(); + } + }); + if (!animateChange) { + + final ViewPropertyAnimator oldViewAnim = view.animate().setDuration( + getChangeDuration()); + oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); + oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); + oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchChangeStarting(changeInfo.oldHolder, true); + } + + @Override + public void onAnimationEnd(Animator animator) { + oldViewAnim.setListener(null); + view.setAlpha(1); + view.setTranslationX(0); + view.setTranslationY(0); + dispatchChangeFinished(changeInfo.oldHolder, true); + mChangeAnimations.remove(changeInfo.oldHolder); + dispatchFinishedWhenDone(); + } + }).start(); + } + } + if (newView != null) { + mChangeAnimations.add(changeInfo.newHolder); + final boolean animateChange = startNewHolderChangeAnimation( + newHolder, + new OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + dispatchChangeFinished(changeInfo.newHolder, true); + mChangeAnimations.remove(changeInfo.oldHolder); + dispatchFinishedWhenDone(); + } + }); + if (!animateChange) { + final ViewPropertyAnimator newViewAnimation = newView.animate(); + newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()) + .alpha(1).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchChangeStarting(changeInfo.newHolder, false); + mChangeAnimations.remove(changeInfo.newHolder); + dispatchFinishedWhenDone(); + } + + @Override + public void onAnimationEnd(Animator animator) { + newViewAnimation.setListener(null); + newView.setAlpha(1); + newView.setTranslationX(0); + newView.setTranslationY(0); + dispatchChangeFinished(changeInfo.newHolder, false); + mChangeAnimations.remove(changeInfo.newHolder); + dispatchFinishedWhenDone(); + } + }).start(); + } + } + } + + private void endChangeAnimation(List infoList, RecyclerView.ViewHolder item) { + for (int i = infoList.size() - 1; i >= 0; i--) { + ChangeInfo changeInfo = infoList.get(i); + if (endChangeAnimationIfNecessary(changeInfo, item)) { + if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { + infoList.remove(changeInfo); + } + } + } + } + + private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { + if (changeInfo.oldHolder != null) { + endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); + } + if (changeInfo.newHolder != null) { + endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); + } + } + + private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) { + boolean oldItem = false; + if (changeInfo.newHolder == item) { + changeInfo.newHolder = null; + } else if (changeInfo.oldHolder == item) { + changeInfo.oldHolder = null; + oldItem = true; + } else { + return false; + } + item.itemView.setAlpha(1); + item.itemView.setTranslationX(0); + item.itemView.setTranslationY(0); + dispatchChangeFinished(item, oldItem); + return true; + } + + @Override + public void endAnimation(RecyclerView.ViewHolder item) { + final View view = item.itemView; + // this will trigger end callback which should set properties to their target values. + view.animate().cancel(); + // TODO if some other animations are chained to end, how do we cancel them as well? + for (int i = mPendingMoves.size() - 1; i >= 0; i--) { + MoveInfo moveInfo = mPendingMoves.get(i); + if (moveInfo.holder == item) { + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item); + mPendingMoves.remove(i); + } + } + endChangeAnimation(mPendingChanges, item); + if (mPendingRemovals.remove(item)) { + view.setAlpha(1); + dispatchRemoveFinished(item); + } + if (mPendingAdditions.remove(item)) { + view.setAlpha(1); + dispatchAddFinished(item); + } + + for (int i = mChangesList.size() - 1; i >= 0; i--) { + ArrayList changes = mChangesList.get(i); + endChangeAnimation(changes, item); + if (changes.isEmpty()) { + mChangesList.remove(i); + } + } + for (int i = mMovesList.size() - 1; i >= 0; i--) { + ArrayList moves = mMovesList.get(i); + for (int j = moves.size() - 1; j >= 0; j--) { + MoveInfo moveInfo = moves.get(j); + if (moveInfo.holder == item) { + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item); + moves.remove(j); + if (moves.isEmpty()) { + mMovesList.remove(i); + } + break; + } + } + } + for (int i = mAdditionsList.size() - 1; i >= 0; i--) { + ArrayList additions = mAdditionsList.get(i); + if (additions.remove(item)) { + view.setAlpha(1); + dispatchAddFinished(item); + if (additions.isEmpty()) { + mAdditionsList.remove(i); + } + } + } + + // animations should be ended by the cancel above. + if (mRemoveAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mRemoveAnimations list"); + } + + if (mAddAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mAddAnimations list"); + } + + if (mChangeAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mChangeAnimations list"); + } + + if (mMoveAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mMoveAnimations list"); + } + dispatchFinishedWhenDone(); + } + + private void resetAnimation(RecyclerView.ViewHolder holder) { + endAnimation(holder); + } + + @Override + public boolean isRunning() { + return (!mPendingAdditions.isEmpty() + || !mPendingChanges.isEmpty() + || !mPendingMoves.isEmpty() + || !mPendingRemovals.isEmpty() + || !mMoveAnimations.isEmpty() + || !mRemoveAnimations.isEmpty() + || !mAddAnimations.isEmpty() + || !mChangeAnimations.isEmpty() + || !mMovesList.isEmpty() + || !mAdditionsList.isEmpty() + || !mChangesList.isEmpty()); + } + + /** + * Check the state of currently pending and running animations. If there are none + * pending/running, call {@link #dispatchAnimationsFinished()} to notify any + * listeners. + */ + void dispatchFinishedWhenDone() { + if (!isRunning()) { + dispatchAnimationsFinished(); + } + } + + @Override + public void endAnimations() { + int count = mPendingMoves.size(); + for (int i = count - 1; i >= 0; i--) { + MoveInfo item = mPendingMoves.get(i); + View view = item.holder.itemView; + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item.holder); + mPendingMoves.remove(i); + } + count = mPendingRemovals.size(); + for (int i = count - 1; i >= 0; i--) { + RecyclerView.ViewHolder item = mPendingRemovals.get(i); + dispatchRemoveFinished(item); + mPendingRemovals.remove(i); + } + count = mPendingAdditions.size(); + for (int i = count - 1; i >= 0; i--) { + RecyclerView.ViewHolder item = mPendingAdditions.get(i); + item.itemView.setAlpha(1); + dispatchAddFinished(item); + mPendingAdditions.remove(i); + } + count = mPendingChanges.size(); + for (int i = count - 1; i >= 0; i--) { + endChangeAnimationIfNecessary(mPendingChanges.get(i)); + } + mPendingChanges.clear(); + if (!isRunning()) { + return; + } + + int listCount = mMovesList.size(); + for (int i = listCount - 1; i >= 0; i--) { + ArrayList moves = mMovesList.get(i); + count = moves.size(); + for (int j = count - 1; j >= 0; j--) { + MoveInfo moveInfo = moves.get(j); + RecyclerView.ViewHolder item = moveInfo.holder; + View view = item.itemView; + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(moveInfo.holder); + moves.remove(j); + if (moves.isEmpty()) { + mMovesList.remove(moves); + } + } + } + listCount = mAdditionsList.size(); + for (int i = listCount - 1; i >= 0; i--) { + ArrayList additions = mAdditionsList.get(i); + count = additions.size(); + for (int j = count - 1; j >= 0; j--) { + RecyclerView.ViewHolder item = additions.get(j); + View view = item.itemView; + view.setAlpha(1); + dispatchAddFinished(item); + additions.remove(j); + if (additions.isEmpty()) { + mAdditionsList.remove(additions); + } + } + } + listCount = mChangesList.size(); + for (int i = listCount - 1; i >= 0; i--) { + ArrayList changes = mChangesList.get(i); + count = changes.size(); + for (int j = count - 1; j >= 0; j--) { + endChangeAnimationIfNecessary(changes.get(j)); + if (changes.isEmpty()) { + mChangesList.remove(changes); + } + } + } + + cancelAll(mRemoveAnimations); + cancelAll(mMoveAnimations); + cancelAll(mAddAnimations); + cancelAll(mChangeAnimations); + + dispatchAnimationsFinished(); + } + + void cancelAll(List viewHolders) { + for (int i = viewHolders.size() - 1; i >= 0; i--) { + viewHolders.get(i).itemView.animate().cancel(); + } + } + + /** + * {@inheritDoc} + *

+ * If the payload list is not empty, DefaultItemAnimator returns true. + * When this is the case: + *

    + *
  • If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both + * ViewHolder arguments will be the same instance. + *
  • + *
  • + * If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, + * then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and + * run a move animation instead. + *
  • + *
+ */ + @Override + public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, + @NonNull List payloads) { + return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); + } +} \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt new file mode 100644 index 0000000..aac980f --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -0,0 +1,37 @@ +package br.alexandregpereira.jerry + +import android.view.View +import androidx.annotation.RequiresApi +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.SpringForce + +@RequiresApi(21) +internal fun elevationViewProperty() = object : FloatPropertyCompat( + "viewProperty" +) { + override fun setValue(view: View?, value: Float) { + view?.elevation = value + } + + override fun getValue(view: View?): Float { + return view?.elevation ?: 0f + } + +} + +@RequiresApi(21) +fun View.startSpringElevation( + targetValue: Float, + stiffness: Float = SpringForce.STIFFNESS_LOW, + onAnimationEnd: (() -> Unit)? = null +) { + startSpringAnimation( + key = R.string.elevation_spring_key, + property = elevationViewProperty(), + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = onAnimationEnd?.let { + R.string.elevation_end_listener_key to onAnimationEnd + } + ) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index ddd03d3..c26dbac 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -101,10 +101,10 @@ internal fun View.hideFadeOutSpring( ) } -internal fun View.startFadeSpringAnimation( +fun View.startFadeSpringAnimation( targetValue: Float, - stiffness: Float, - onAnimationEnd: (() -> Unit)?, + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: (() -> Unit)? = null, ) = startSpringAnimation( key = R.string.alpha_spring_key, property = DynamicAnimation.ALPHA, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index a086bff..3f2d1ad 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -51,7 +51,7 @@ fun View.startSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, - endListenerPair: Pair Unit)>? = null + endListenerPair: Pair Unit>? = null ) { val springAnimation = this.spring( key, diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index 85bce01..0dd3bee 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -7,6 +7,9 @@ alpha_spring_key alpha_end_listener_key + elevation_spring_key + elevation_end_listener_key + expanding_collapsing_height_original_value_key expanding_collapsing_height_spring_key expanding_collapsing_height_end_listener_key From 3b14a4a92186a4b63df993f8ded8040d7cd7c946 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 06:06:38 -0300 Subject: [PATCH 11/33] Adjust animation speed --- .../jerry/app/RecyclerViewActivity.kt | 11 ++++++----- .../alexandregpereira/jerry/app/SpringItemAnimator.kt | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index c36ea4d..52a4c40 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -28,6 +28,10 @@ class RecyclerViewActivity : AppCompatActivity() { } } + private val list = (0..2).map { + Item(id = it, value = it.toString()) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_recycler_view) @@ -39,11 +43,8 @@ class RecyclerViewActivity : AppCompatActivity() { recyclerView.itemAnimator = SpringItemAnimator() } - val list = (0..2).map { - Item(id = it, value = it.toString()) - } button.setOnClickListener { - adapter.submitList(list.shuffled()) + adapter.submitList(list) } button2.setOnClickListener { @@ -53,7 +54,7 @@ class RecyclerViewActivity : AppCompatActivity() { } button3.setOnClickListener { - adapter.submitList(listOf()) + adapter.submitList(listOf(list[0], list[2])) } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index fd9f573..9d4e7a5 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -20,8 +20,8 @@ class SpringItemAnimator : BaseItemAnimator() { holder: RecyclerView.ViewHolder?, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startFadeSpringAnimation(1f) { - holder.itemView.startSpringElevation(4f.dpToPx(holder.itemView.resources)) { + holder?.itemView?.startFadeSpringAnimation(1f, stiffness = 1200f) { + holder.itemView.startSpringElevation(4f.dpToPx(holder.itemView.resources), stiffness = 1200f) { onAnimationEndListener.onAnimationEnd() } } @@ -32,8 +32,8 @@ class SpringItemAnimator : BaseItemAnimator() { holder: RecyclerView.ViewHolder?, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startSpringElevation(0f.dpToPx(holder.itemView.resources)) { - holder.itemView.startFadeSpringAnimation(0f) { + holder?.itemView?.startSpringElevation(0f.dpToPx(holder.itemView.resources), stiffness = 1200f) { + holder.itemView.startFadeSpringAnimation(0f, stiffness = 1200f) { holder.itemView.alpha = 1f holder.itemView.elevation = 4f onAnimationEndListener.onAnimationEnd() From a20edd80021d5357596dc5f8beedabaaa7e26836 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 17:40:38 -0300 Subject: [PATCH 12/33] Adjust code --- .../jerry/app/RecyclerViewActivity.kt | 56 +++++++++++++++++-- .../jerry/app/SpringItemAnimator.kt | 33 ++++++++--- .../res/layout/activity_recycler_view.xml | 35 +++++++++++- app/src/main/res/values/strings.xml | 7 +++ .../jerry/BaseItemAnimator.java | 10 ++-- .../jerry/ElevationSpringAnimation.kt | 6 +- .../jerry/ExpandableAnimationCommon.kt | 14 ++--- .../jerry/FadeAnimationCommon.kt | 14 ++--- .../jerry/FadeSpringAnimation.kt | 4 ++ .../alexandregpereira/jerry/ViewAnimation.kt | 53 +++++++++++------- 10 files changed, 177 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index 52a4c40..7362e81 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -28,10 +28,12 @@ class RecyclerViewActivity : AppCompatActivity() { } } - private val list = (0..2).map { + private val list = (0..3).map { Item(id = it, value = it.toString()) } + private val list2 = listOf(list[0], list[2], list[3]) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_recycler_view) @@ -42,25 +44,57 @@ class RecyclerViewActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { recyclerView.itemAnimator = SpringItemAnimator() } + recyclerView.itemAnimator?.addDuration = 2000 + recyclerView.itemAnimator?.removeDuration = 2000 + recyclerView.itemAnimator?.changeDuration = 2000 + recyclerView.itemAnimator?.moveDuration = 2000 button.setOnClickListener { - adapter.submitList(list) + adapter.setData(list2) +// adapter.notifyItemRangeInserted(0, list2.size) } button2.setOnClickListener { - adapter.submitList(list.map { + adapter.setData(list2.map { it.copy(value = "${UUID.randomUUID().toString().substring(0..10)} ${it.value}") }) } button3.setOnClickListener { - adapter.submitList(listOf(list[0], list[2])) + adapter.setData(listOf()) + } + + button4.setOnClickListener { + adapter.setData(list) +// adapter.notifyItemInserted(1) + } + + button5.setOnClickListener { + adapter.setData(adapter.currentList.reversed()) + } + + button6.setOnClickListener { + adapter.setData(list2) } } } class ExampleAdapter : ListAdapter(DiffUtil) { +// var currentList: List = listOf() +// private set + + fun setData(list: List) { +// currentList = list + submitList(list) + } + +// override fun getItemCount(): Int = currentList.size +// +// private fun getItem(position: Int): Item { +// return currentList[position] +// } + inner class ExampleViewHolder( private val view: View ) : RecyclerView.ViewHolder(view) { @@ -73,12 +107,16 @@ class ExampleAdapter : ListAdapter(DiffU } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder { + val height = when (viewType) { + ViewType.TYPE_2.ordinal -> ViewType.TYPE_2.height + else -> ViewType.TYPE_1.height + } return ExampleViewHolder( AppCompatTextView(parent.context).apply { val padding = resources.getDimensionPixelOffset(R.dimen.text_padding) layoutParams = ViewGroup.MarginLayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - 100.dpToPx(resources) + 60.dpToPx(resources) ).apply { setMargins(padding / 2, padding / 2, padding / 2, padding / 2) } @@ -90,9 +128,17 @@ class ExampleAdapter : ListAdapter(DiffU ) } + override fun getItemViewType(position: Int): Int { + return (if (position == 1) ViewType.TYPE_2 else ViewType.TYPE_1).ordinal + } + override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) { holder.bind(getItem(position)) } + + enum class ViewType(val height: Int) { + TYPE_1(60), TYPE_2(100) + } } object DiffUtil : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index 9d4e7a5..493da94 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -4,12 +4,21 @@ import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.BaseItemAnimator import br.alexandregpereira.jerry.dpToPx +import br.alexandregpereira.jerry.startElevationSpringAnimation import br.alexandregpereira.jerry.startFadeSpringAnimation -import br.alexandregpereira.jerry.startSpringElevation + +private const val ANIMATION_STIFFNESS = 500f @RequiresApi(21) class SpringItemAnimator : BaseItemAnimator() { + private val elevationStiffness = ANIMATION_STIFFNESS * 2.5f + private val alphaStiffness = ANIMATION_STIFFNESS * 2f + private val elevationInitialValue = 0f + private val alphaInitialValue = 0f + private val elevationFinalValue = 4f + private val alphaFinalValue = 1f + override fun preAnimateAdd(holder: RecyclerView.ViewHolder): Boolean { holder.itemView.alpha = 0f holder.itemView.elevation = 0f @@ -20,8 +29,14 @@ class SpringItemAnimator : BaseItemAnimator() { holder: RecyclerView.ViewHolder?, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startFadeSpringAnimation(1f, stiffness = 1200f) { - holder.itemView.startSpringElevation(4f.dpToPx(holder.itemView.resources), stiffness = 1200f) { + holder?.itemView?.startFadeSpringAnimation( + targetValue = alphaFinalValue, + stiffness = alphaStiffness + ) { + holder.itemView.startElevationSpringAnimation( + targetValue = elevationFinalValue.dpToPx(holder.itemView.resources), + stiffness = elevationStiffness + ) { onAnimationEndListener.onAnimationEnd() } } @@ -32,10 +47,14 @@ class SpringItemAnimator : BaseItemAnimator() { holder: RecyclerView.ViewHolder?, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startSpringElevation(0f.dpToPx(holder.itemView.resources), stiffness = 1200f) { - holder.itemView.startFadeSpringAnimation(0f, stiffness = 1200f) { - holder.itemView.alpha = 1f - holder.itemView.elevation = 4f + holder?.itemView?.startElevationSpringAnimation( + targetValue = elevationInitialValue.dpToPx(holder.itemView.resources), + stiffness = elevationStiffness + ) { + holder.itemView.startFadeSpringAnimation( + targetValue = alphaInitialValue, + stiffness = alphaStiffness + ) { onAnimationEndListener.onAnimationEnd() } } diff --git a/app/src/main/res/layout/activity_recycler_view.xml b/app/src/main/res/layout/activity_recycler_view.xml index 7bc9cab..1324f26 100644 --- a/app/src/main/res/layout/activity_recycler_view.xml +++ b/app/src/main/res/layout/activity_recycler_view.xml @@ -13,6 +13,7 @@ android:layout_marginTop="@dimen/small" android:layout_marginStart="@dimen/x_small" android:layout_marginEnd="@dimen/x_small" + android:text="@string/add_all_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button2" app:layout_constraintTop_toTopOf="parent" /> @@ -24,6 +25,7 @@ android:layout_marginTop="@dimen/small" android:layout_marginStart="@dimen/x_small" android:layout_marginEnd="@dimen/x_small" + android:text="@string/change" app:layout_constraintStart_toEndOf="@id/button" app:layout_constraintEnd_toStartOf="@id/button3" app:layout_constraintTop_toTopOf="parent" /> @@ -35,16 +37,47 @@ android:layout_marginTop="@dimen/small" android:layout_marginStart="@dimen/x_small" android:layout_marginEnd="@dimen/x_small" + android:text="@string/remove_all_button" app:layout_constraintStart_toEndOf="@id/button2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + app:layout_constraintTop_toBottomOf="@id/button6" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 092dc2f..6b43caf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,4 +43,11 @@ Animation stiffness 600 + Add All + Remove All + Add + Remove + Shuffle + Change + \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java index e9fef7b..11921ad 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java +++ b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java @@ -39,15 +39,14 @@ public abstract class BaseItemAnimator extends SimpleItemAnimator { ArrayList mRemoveAnimations = new ArrayList<>(); ArrayList mChangeAnimations = new ArrayList<>(); - protected boolean preAnimateRemove(RecyclerView.ViewHolder holder) { - return false; - } - protected boolean preAnimateAdd(RecyclerView.ViewHolder holder) { return false; } - protected boolean startRemoveAnimation(RecyclerView.ViewHolder holder, OnAnimationEndListener onAnimationEndListener) { + protected boolean startRemoveAnimation( + RecyclerView.ViewHolder holder, + OnAnimationEndListener onAnimationEndListener + ) { return false; } @@ -227,7 +226,6 @@ public void run() { @Override public boolean animateRemove(final RecyclerView.ViewHolder holder) { resetAnimation(holder); - preAnimateRemove(holder); mPendingRemovals.add(holder); return true; } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt index aac980f..da2370d 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -20,7 +20,7 @@ internal fun elevationViewProperty() = object : FloatPropertyCompat( } @RequiresApi(21) -fun View.startSpringElevation( +fun View.startElevationSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, onAnimationEnd: (() -> Unit)? = null @@ -35,3 +35,7 @@ fun View.startSpringElevation( } ) } + +fun View.isElevationSpringAnimationRunning() : Boolean { + return isSpringAnimationRunning(R.string.elevation_spring_key) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index 2a64cf7..dbb65de 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -39,22 +39,22 @@ internal fun View.getLayoutParamSize(isHeight: Boolean): Int { } -internal fun View.isExpandingCollapsingRunning(animationMode: Int): Boolean = +internal fun View.isExpandingCollapsingRunning(animationMode: AnimationMode): Boolean = isAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) -internal fun View.setExpandingCollapsingRunning(animationMode: Int) = +internal fun View.setExpandingCollapsingRunning(animationMode: AnimationMode) = setAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) -internal fun View.isExpandingRunning() = isExpandingCollapsingRunning(ENTER_ANIMATION_MODE) +internal fun View.isExpandingRunning() = isExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) -internal fun View.isCollapsingRunning() = isExpandingCollapsingRunning(POP_ANIMATION_MODE) +internal fun View.isCollapsingRunning() = isExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) internal fun View.clearExpandingCollapsingRunning() = - setExpandingCollapsingRunning(NONE_ANIMATION_MODE) + setExpandingCollapsingRunning(AnimationMode.NONE_ANIMATION_MODE) -internal fun View.startExpandingRunning() = setExpandingCollapsingRunning(ENTER_ANIMATION_MODE) +internal fun View.startExpandingRunning() = setExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) -internal fun View.startCollapsingRunning() = setExpandingCollapsingRunning(POP_ANIMATION_MODE) +internal fun View.startCollapsingRunning() = setExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) internal fun View.getCollapsingInitialValue(isHeight: Boolean): Int { val value = getLayoutParamSize(isHeight) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt index e8f6917..9f36b65 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimationCommon.kt @@ -2,18 +2,18 @@ package br.alexandregpereira.jerry import android.view.View -internal fun View.isFadeInFadeOutRunning(animationMode: Int): Boolean = +internal fun View.isFadeInFadeOutRunning(animationMode: AnimationMode): Boolean = isAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) -internal fun View.setFadeInFadeOutRunning(animationMode: Int) = +internal fun View.setFadeInFadeOutRunning(animationMode: AnimationMode) = setAnimationRunning(R.string.is_fade_in_fade_out_key, animationMode) -fun View.isFadeInRunning() = isFadeInFadeOutRunning(ENTER_ANIMATION_MODE) +fun View.isFadeInRunning() = isFadeInFadeOutRunning(AnimationMode.ENTER_ANIMATION_MODE) -fun View.isFadeOutRunning() = isFadeInFadeOutRunning(POP_ANIMATION_MODE) +fun View.isFadeOutRunning() = isFadeInFadeOutRunning(AnimationMode.POP_ANIMATION_MODE) -internal fun View.clearFadeInFadeOutRunning() = setFadeInFadeOutRunning(NONE_ANIMATION_MODE) +internal fun View.clearFadeInFadeOutRunning() = setFadeInFadeOutRunning(AnimationMode.NONE_ANIMATION_MODE) -internal fun View.startFadeInRunning() = setFadeInFadeOutRunning(ENTER_ANIMATION_MODE) +internal fun View.startFadeInRunning() = setFadeInFadeOutRunning(AnimationMode.ENTER_ANIMATION_MODE) -internal fun View.startFadeOutRunning() = setFadeInFadeOutRunning(POP_ANIMATION_MODE) +internal fun View.startFadeOutRunning() = setFadeInFadeOutRunning(AnimationMode.POP_ANIMATION_MODE) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index c26dbac..d2b4ab1 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -115,3 +115,7 @@ fun View.startFadeSpringAnimation( onAnimationEnd?.invoke() } ) + +fun View.isFadeSpringAnimationRunning() : Boolean { + return isSpringAnimationRunning(R.string.alpha_spring_key) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 3f2d1ad..c8a1af5 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -11,20 +11,20 @@ const val ANIMATION_SHORT_TIME = 200L const val ANIMATION_STIFFNESS = 600f -/** - * Used to clear the key to verify if a animation is running. - */ -internal const val NONE_ANIMATION_MODE = 0 - -/** - * Used to identify if the animation that is running is an enter animation. - */ -internal const val ENTER_ANIMATION_MODE = 1 - -/** - * Used to identify if the animation that is running is an pop animation. - */ -internal const val POP_ANIMATION_MODE = 2 +enum class AnimationMode { + /** + * Used to clear the key to verify if a animation is running. + */ + NONE_ANIMATION_MODE, + /** + * Used to identify if the animation that is running is an enter animation. + */ + ENTER_ANIMATION_MODE, + /** + * Used to identify if the animation that is running is an pop animation. + */ + POP_ANIMATION_MODE +} fun View.spring( key: Int, @@ -100,25 +100,36 @@ internal fun View.getSpringEndListener(key: Int): DynamicAnimation.OnAnimationEn * Check if is animation is running using the view tag system. * * @param key The key of the tag must be a string resource id. - * @param animationMode The value to be store at the key. Must be [NONE_ANIMATION_MODE], - * [ENTER_ANIMATION_MODE] or [POP_ANIMATION_MODE]. + * @param animationMode The value to be store at the key. Must be [AnimationMode.NONE_ANIMATION_MODE], + * [AnimationMode.ENTER_ANIMATION_MODE] or [AnimationMode.POP_ANIMATION_MODE]. */ -internal fun View.isAnimationRunning(key: Int, animationMode: Int): Boolean { +internal fun View.isAnimationRunning(key: Int, animationMode: AnimationMode): Boolean { return runCatching { getTag(key) as Int }.getOrElse { 0 - } == animationMode + } == animationMode.ordinal +} + +/** + * Check if a [SpringAnimation] is running using the view tag system. Unlike [isAnimationRunning], + * this function do not distinguishes if is an enter animation or the pop animation is running. + * + * @param key The key of the tag must be a string resource id. + */ +internal fun View.isSpringAnimationRunning(key: Int): Boolean { + val springAnimation = getTag(key) as? SpringAnimation + return springAnimation != null && springAnimation.isRunning } /** * Set the key value to check if is animation is running using the view tag system. * * @param key The key of the tag, it must be a string resource id. - * @param animationMode The value to be store at the key. Must be [NONE_ANIMATION_MODE], - * [ENTER_ANIMATION_MODE] or [POP_ANIMATION_MODE]. + * @param animationMode The value to be store at the key. Must be [AnimationMode.NONE_ANIMATION_MODE], + * [AnimationMode.ENTER_ANIMATION_MODE] or [AnimationMode.POP_ANIMATION_MODE]. */ -internal fun View.setAnimationRunning(key: Int, animationMode: Int) { +internal fun View.setAnimationRunning(key: Int, animationMode: AnimationMode) { runCatching { setTag(key, animationMode) } From 9ace46640ec4c9609db75d774eb0b3e59c7feaac Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 17:44:10 -0300 Subject: [PATCH 13/33] Adjust code --- .../alexandregpereira/jerry/app/RecyclerViewActivity.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index 7362e81..89508f8 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -44,10 +44,10 @@ class RecyclerViewActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { recyclerView.itemAnimator = SpringItemAnimator() } - recyclerView.itemAnimator?.addDuration = 2000 - recyclerView.itemAnimator?.removeDuration = 2000 - recyclerView.itemAnimator?.changeDuration = 2000 - recyclerView.itemAnimator?.moveDuration = 2000 +// recyclerView.itemAnimator?.addDuration = 2000 +// recyclerView.itemAnimator?.removeDuration = 2000 +// recyclerView.itemAnimator?.changeDuration = 2000 +// recyclerView.itemAnimator?.moveDuration = 2000 button.setOnClickListener { adapter.setData(list2) From 954ee7ffa045b9e032ec1199b4186513502ca74e Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 20:30:54 -0300 Subject: [PATCH 14/33] Fix the fucking canceled case --- .../jerry/app/RecyclerViewActivity.kt | 4 +- .../jerry/app/SpringItemAnimator.kt | 53 ++++++++++++------- jerry/build.gradle | 2 +- .../jerry/ElevationSpringAnimation.kt | 15 ++++-- .../jerry/ExpandableAnimationCommon.kt | 9 ++++ .../jerry/ExpandableFadingSpringAnimation.kt | 24 ++++----- .../jerry/ExpandableSpingAnimation.kt | 26 ++++----- .../jerry/FadeSpringAnimation.kt | 34 ++++++------ .../jerry/TextViewSpringAnimation.kt | 4 +- .../alexandregpereira/jerry/ViewAnimation.kt | 10 ++-- 10 files changed, 107 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index 89508f8..53c8755 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -28,11 +28,11 @@ class RecyclerViewActivity : AppCompatActivity() { } } - private val list = (0..3).map { + private val list = (0..33).map { Item(id = it, value = it.toString()) } - private val list2 = listOf(list[0], list[2], list[3]) + private val list2 = listOf(list[0]) + list.subList(2, list.size) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index 493da94..90d7b1c 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -2,13 +2,14 @@ package br.alexandregpereira.jerry.app import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView +import br.alexandregpereira.jerry.ANIMATION_STIFFNESS import br.alexandregpereira.jerry.BaseItemAnimator import br.alexandregpereira.jerry.dpToPx +import br.alexandregpereira.jerry.elevationSpring +import br.alexandregpereira.jerry.fadeSpring import br.alexandregpereira.jerry.startElevationSpringAnimation import br.alexandregpereira.jerry.startFadeSpringAnimation -private const val ANIMATION_STIFFNESS = 500f - @RequiresApi(21) class SpringItemAnimator : BaseItemAnimator() { @@ -26,16 +27,22 @@ class SpringItemAnimator : BaseItemAnimator() { } override fun startAddAnimation( - holder: RecyclerView.ViewHolder?, + holder: RecyclerView.ViewHolder, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startFadeSpringAnimation( - targetValue = alphaFinalValue, - stiffness = alphaStiffness - ) { - holder.itemView.startElevationSpringAnimation( - targetValue = elevationFinalValue.dpToPx(holder.itemView.resources), - stiffness = elevationStiffness + val view = holder.itemView + + holder.itemView.fadeSpring(stiffness = alphaStiffness).cancel() + holder.itemView.startFadeSpringAnimation(targetValue = alphaFinalValue) { canceled -> + + if (canceled) { + onAnimationEndListener.onAnimationEnd() + return@startFadeSpringAnimation + } + + holder.itemView.elevationSpring(stiffness = elevationStiffness).cancel() + view.startElevationSpringAnimation( + targetValue = elevationFinalValue.dpToPx(view.resources) ) { onAnimationEndListener.onAnimationEnd() } @@ -44,17 +51,25 @@ class SpringItemAnimator : BaseItemAnimator() { } override fun startRemoveAnimation( - holder: RecyclerView.ViewHolder?, + holder: RecyclerView.ViewHolder, onAnimationEndListener: OnAnimationEndListener ): Boolean { - holder?.itemView?.startElevationSpringAnimation( - targetValue = elevationInitialValue.dpToPx(holder.itemView.resources), - stiffness = elevationStiffness - ) { - holder.itemView.startFadeSpringAnimation( - targetValue = alphaInitialValue, - stiffness = alphaStiffness - ) { + val view = holder.itemView + + holder.itemView.elevationSpring(stiffness = elevationStiffness).cancel() + holder.itemView.startElevationSpringAnimation( + targetValue = elevationInitialValue.dpToPx(view.resources) + ) { canceled -> + + if (canceled) { + onAnimationEndListener.onAnimationEnd() + return@startElevationSpringAnimation + } + + holder.itemView.fadeSpring(stiffness = alphaStiffness).cancel() + view.startFadeSpringAnimation(targetValue = alphaInitialValue) { + view.alpha = alphaFinalValue + view.elevation = elevationFinalValue onAnimationEndListener.onAnimationEnd() } } diff --git a/jerry/build.gradle b/jerry/build.gradle index 5c66764..f61b065 100644 --- a/jerry/build.gradle +++ b/jerry/build.gradle @@ -25,5 +25,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' + api 'androidx.dynamicanimation:dynamicanimation:1.0.0' } \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt index da2370d..15069f7 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -19,11 +19,20 @@ internal fun elevationViewProperty() = object : FloatPropertyCompat( } +@RequiresApi(21) +fun View.elevationSpring( + stiffness: Float = SpringForce.STIFFNESS_LOW +) = spring( + key = R.string.elevation_spring_key, + property = elevationViewProperty(), + stiffness = stiffness +) + @RequiresApi(21) fun View.startElevationSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { startSpringAnimation( key = R.string.elevation_spring_key, @@ -35,7 +44,3 @@ fun View.startElevationSpringAnimation( } ) } - -fun View.isElevationSpringAnimationRunning() : Boolean { - return isSpringAnimationRunning(R.string.elevation_spring_key) -} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index dbb65de..04583fb 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -26,6 +26,15 @@ internal fun View.finishExpandingCollapsingAnimation(onAnimationEnd: (() -> Unit onAnimationEnd?.invoke() } +internal fun View.finishExpandingCollapsingAnimation( + canceled: Boolean, + onAnimationEnd: ((canceled: Boolean) -> Unit)? +) { + clearOriginalValue() + clearExpandingCollapsingRunning() + onAnimationEnd?.invoke(canceled) +} + internal fun View.setLayoutParamSize(value: Int, isHeight: Boolean) { if (isHeight) { layoutParams.height = value diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt index 886b018..2bf0d1e 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt @@ -6,7 +6,7 @@ import androidx.dynamicanimation.animation.SpringAnimation fun View.animateHeightFadingVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (visible) { expandHeightFadingSpring(stiffness, onAnimationEnd) @@ -18,7 +18,7 @@ fun View.animateHeightFadingVisibility( fun View.animateWidthFadingVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (visible) { expandWidthFadingSpring(stiffness, onAnimationEnd) @@ -41,8 +41,8 @@ fun View.animateWidthFadingVisibility( */ fun View.expandHeightFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) = expandFadingSpring(stiffness, isHeight = true, onAnimationEnd) + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = expandFadingSpring(stiffness, isHeight = true, onAnimationEnd = onAnimationEnd) /** * Uses the [expandWidth] and [fadeIn] animations in sequence. This animation @@ -58,8 +58,8 @@ fun View.expandHeightFadingSpring( */ fun View.expandWidthFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) = expandFadingSpring(stiffness, isHeight = false, onAnimationEnd) + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = expandFadingSpring(stiffness, isHeight = false, onAnimationEnd = onAnimationEnd) /** * Uses the [hideFadeOut] and [collapseHeight] animations in sequence. This animation @@ -75,8 +75,8 @@ fun View.expandWidthFadingSpring( */ fun View.collapseHeightFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) = collapseFadingSpring(stiffness, isHeight = true, onAnimationEnd) + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = collapseFadingSpring(stiffness, isHeight = true, onAnimationEnd = onAnimationEnd) /** * Uses the [hideFadeOut] and [collapseWidth] animations in sequence. This animation @@ -92,13 +92,13 @@ fun View.collapseHeightFadingSpring( */ fun View.collapseWidthFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null -) = collapseFadingSpring(stiffness, isHeight = false, onAnimationEnd) + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = collapseFadingSpring(stiffness, isHeight = false, onAnimationEnd = onAnimationEnd) private fun View.collapseFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (isExpandingRunning()) { collapseSpring(isHeight = isHeight, onAnimationEnd = onAnimationEnd) @@ -117,7 +117,7 @@ private fun View.collapseFadingSpring( private fun View.expandFadingSpring( stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (alpha == 1f && (isVisible() && isCollapsingRunning().not())) { return diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt index bc2e270..c3f21bd 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt @@ -7,7 +7,7 @@ fun View.animateHeightVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (visible) { expandHeightSpring(stiffness, onProgressChange, onAnimationEnd) @@ -20,7 +20,7 @@ fun View.animateWidthVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (visible) { expandWidthSpring(stiffness, onProgressChange, onAnimationEnd) @@ -44,7 +44,7 @@ fun View.animateWidthVisibility( fun View.collapseHeightSpring( stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) = collapseSpring( stiffness = stiffness, isHeight = true, @@ -67,7 +67,7 @@ fun View.collapseHeightSpring( fun View.collapseWidthSpring( stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) = collapseSpring( stiffness = stiffness, isHeight = false, @@ -91,7 +91,7 @@ fun View.collapseWidthSpring( fun View.expandHeightSpring( stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) = expandSpring( stiffness = stiffness, isHeight = true, @@ -115,7 +115,7 @@ fun View.expandHeightSpring( fun View.expandWidthSpring( stiffness: Float = ANIMATION_STIFFNESS, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) = expandSpring( stiffness = stiffness, isHeight = false, @@ -127,7 +127,7 @@ internal fun View.collapseSpring( stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (isVisible().not() || isCollapsingRunning()) { return @@ -141,7 +141,7 @@ internal fun View.collapseSpring( onProgressChange = onProgressChange, onAnimationEnd = { gone() - onAnimationEnd?.invoke() + onAnimationEnd?.invoke(it) } ) } @@ -150,7 +150,7 @@ internal fun View.expandSpring( stiffness: Float = ANIMATION_STIFFNESS, isHeight: Boolean = true, onProgressChange: ((progress: Float) -> Unit)? = null, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (isExpandingRunning() || (isVisible() && isCollapsingRunning().not())) { return @@ -163,7 +163,7 @@ internal fun View.expandSpring( val targetValue = getTargetValue(originalValue, isHeight) if (targetValue == null) { - finishExpandingCollapsingAnimation(onAnimationEnd) + finishExpandingCollapsingAnimation(canceled = false, onAnimationEnd = onAnimationEnd) return } startExpandingRunning() @@ -187,13 +187,13 @@ private fun View.startExpandCollapseSpringAnimation( stiffness: Float, isHeight: Boolean, onProgressChange: ((progress: Float) -> Unit)?, - onAnimationEnd: (() -> Unit)?, + onAnimationEnd: ((canceled: Boolean) -> Unit)?, ) = startSpringAnimation( key = getExpandingCollapsingSpringKey(isHeight), property = widthHeightViewProperty(isHeight, onProgressChange), targetValue = targetValue, stiffness = stiffness, - endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { - finishExpandingCollapsingAnimation(onAnimationEnd) + endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { canceled -> + finishExpandingCollapsingAnimation(canceled, onAnimationEnd) } ) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index d2b4ab1..45a212f 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -7,7 +7,7 @@ import androidx.dynamicanimation.animation.SpringAnimation fun View.animateAlphaVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (visible) { fadeInSpring(stiffness, onAnimationEnd = onAnimationEnd) @@ -29,7 +29,7 @@ fun View.animateAlphaVisibility( */ fun View.fadeOutSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { hideFadeOutSpring(stiffness, hide = ::gone, onAnimationEnd = onAnimationEnd) } @@ -48,11 +48,11 @@ fun View.fadeOutSpring( */ fun View.fadeInSpring( stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (isFadeInRunning() || (alpha == 1f && isVisible() && isFadeOutRunning().not())) { if (isFadeInRunning().not()) { - onAnimationEnd?.invoke() + onAnimationEnd?.invoke(false) } return } @@ -81,11 +81,11 @@ fun View.fadeInSpring( internal fun View.hideFadeOutSpring( stiffness: Float, hide: (() -> Unit)? = null, - onAnimationEnd: (() -> Unit)? + onAnimationEnd: ((canceled: Boolean) -> Unit)? ) { if (isVisible().not() || isFadeOutRunning()) { if (isFadeOutRunning().not()) { - onAnimationEnd?.invoke() + onAnimationEnd?.invoke(false) } return } @@ -94,28 +94,32 @@ internal fun View.hideFadeOutSpring( startFadeSpringAnimation( targetValue = 0f, stiffness = stiffness, - onAnimationEnd = { + onAnimationEnd = { canceled -> hide?.invoke() - onAnimationEnd?.invoke() + onAnimationEnd?.invoke(canceled) } ) } +fun View.fadeSpring( + stiffness: Float = ANIMATION_STIFFNESS +) = spring( + key = R.string.alpha_spring_key, + property = DynamicAnimation.ALPHA, + stiffness = stiffness +) + fun View.startFadeSpringAnimation( targetValue: Float, stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null, ) = startSpringAnimation( key = R.string.alpha_spring_key, property = DynamicAnimation.ALPHA, targetValue = targetValue, stiffness = stiffness, - endListenerPair = R.string.alpha_end_listener_key to { + endListenerPair = R.string.alpha_end_listener_key to { canceled -> clearFadeInFadeOutRunning() - onAnimationEnd?.invoke() + onAnimationEnd?.invoke(canceled) } ) - -fun View.isFadeSpringAnimationRunning() : Boolean { - return isSpringAnimationRunning(R.string.alpha_spring_key) -} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt index 5afc600..376f653 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt @@ -19,7 +19,7 @@ import androidx.dynamicanimation.animation.SpringAnimation fun TextView.setTextExpandableSpring( text: String?, stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { if (text == null || text.trim().isEmpty()) { collapseHeightFadingSpring(stiffness = stiffness, onAnimationEnd = onAnimationEnd) @@ -51,7 +51,7 @@ fun TextView.setTextExpandableSpring( fun TextView.setTextFadeSpring( text: String = "", stiffness: Float = ANIMATION_STIFFNESS, - onAnimationEnd: (() -> Unit)? = null + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { val textView = this val oldText = textView.text.toString() diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index c8a1af5..ac030c5 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -51,7 +51,7 @@ fun View.startSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, - endListenerPair: Pair Unit>? = null + endListenerPair: Pair Unit>? = null ) { val springAnimation = this.spring( key, @@ -77,13 +77,13 @@ fun View.startSpringAnimation( internal fun SpringAnimation.addSpringEndListener( key: Int, view: View, - onAnimationEnd: () -> Unit + onAnimationEnd: (canceled: Boolean) -> Unit ) { - DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> + DynamicAnimation.OnAnimationEndListener { animation, canceled, _, _ -> view.getSpringEndListener(key)?.let { - this.removeEndListener(it) + animation.removeEndListener(it) } - onAnimationEnd() + onAnimationEnd(canceled) }.let { this.addEndListener(it) view.setTag(key, it) From abb6f093681d5848017e34bea5f651fd94fe92d5 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 20:42:16 -0300 Subject: [PATCH 15/33] Change list size --- .../java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index 53c8755..da30771 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -28,7 +28,7 @@ class RecyclerViewActivity : AppCompatActivity() { } } - private val list = (0..33).map { + private val list = (0..4).map { Item(id = it, value = it.toString()) } From 164657c4430772f71b2a81d2d48b927cff1c2dfc Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 20:45:37 -0300 Subject: [PATCH 16/33] Change list size --- .../java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index da30771..1ce2034 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -116,7 +116,7 @@ class ExampleAdapter : ListAdapter(DiffU val padding = resources.getDimensionPixelOffset(R.dimen.text_padding) layoutParams = ViewGroup.MarginLayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - 60.dpToPx(resources) + height.dpToPx(resources) ).apply { setMargins(padding / 2, padding / 2, padding / 2, padding / 2) } From e644d9b71c2efb3f1c33c1336bc82e3878d38f1b Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Tue, 13 Oct 2020 23:41:28 -0300 Subject: [PATCH 17/33] Create change animations --- .../jerry/app/SpringItemAnimator.kt | 117 +++++++++++---- .../jerry/BaseItemAnimator.java | 135 ++++++++---------- .../jerry/ElevationSpringAnimation.kt | 4 +- .../jerry/ExpandableAnimationCommon.kt | 6 +- .../jerry/FadeSpringAnimation.kt | 4 +- .../jerry/SpringAnimationPropertyKey.kt | 12 ++ .../jerry/TransalationSpringAnimation.kt | 53 +++++++ .../alexandregpereira/jerry/ViewAnimation.kt | 24 +++- jerry/src/main/res/values/strings.xml | 6 + 9 files changed, 248 insertions(+), 113 deletions(-) create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/SpringAnimationPropertyKey.kt create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index 90d7b1c..7a483a5 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -5,10 +5,13 @@ import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.ANIMATION_STIFFNESS import br.alexandregpereira.jerry.BaseItemAnimator import br.alexandregpereira.jerry.dpToPx -import br.alexandregpereira.jerry.elevationSpring import br.alexandregpereira.jerry.fadeSpring import br.alexandregpereira.jerry.startElevationSpringAnimation import br.alexandregpereira.jerry.startFadeSpringAnimation +import br.alexandregpereira.jerry.startTranslationXSpringAnimation +import br.alexandregpereira.jerry.startTranslationYSpringAnimation +import br.alexandregpereira.jerry.translationXSpring +import br.alexandregpereira.jerry.translationYSpring @RequiresApi(21) class SpringItemAnimator : BaseItemAnimator() { @@ -27,52 +30,118 @@ class SpringItemAnimator : BaseItemAnimator() { } override fun startAddAnimation( - holder: RecyclerView.ViewHolder, - onAnimationEndListener: OnAnimationEndListener + holder: RecyclerView.ViewHolder ): Boolean { - val view = holder.itemView - - holder.itemView.fadeSpring(stiffness = alphaStiffness).cancel() - holder.itemView.startFadeSpringAnimation(targetValue = alphaFinalValue) { canceled -> + holder.itemView.startFadeSpringAnimation( + stiffness = alphaStiffness, + targetValue = alphaFinalValue + ) { canceled -> if (canceled) { - onAnimationEndListener.onAnimationEnd() + onAnimateAddFinished(holder) return@startFadeSpringAnimation } - holder.itemView.elevationSpring(stiffness = elevationStiffness).cancel() - view.startElevationSpringAnimation( - targetValue = elevationFinalValue.dpToPx(view.resources) + holder.itemView.startElevationSpringAnimation( + stiffness = elevationStiffness, + targetValue = elevationFinalValue.dpToPx(holder.itemView.resources) ) { - onAnimationEndListener.onAnimationEnd() + onAnimateAddFinished(holder) } } return true } override fun startRemoveAnimation( - holder: RecyclerView.ViewHolder, - onAnimationEndListener: OnAnimationEndListener + holder: RecyclerView.ViewHolder ): Boolean { - val view = holder.itemView - - holder.itemView.elevationSpring(stiffness = elevationStiffness).cancel() holder.itemView.startElevationSpringAnimation( - targetValue = elevationInitialValue.dpToPx(view.resources) + stiffness = elevationStiffness, + targetValue = elevationInitialValue.dpToPx(holder.itemView.resources) ) { canceled -> if (canceled) { - onAnimationEndListener.onAnimationEnd() + onAnimateRemoveFinished(holder) return@startElevationSpringAnimation } - holder.itemView.fadeSpring(stiffness = alphaStiffness).cancel() - view.startFadeSpringAnimation(targetValue = alphaInitialValue) { - view.alpha = alphaFinalValue - view.elevation = elevationFinalValue - onAnimationEndListener.onAnimationEnd() + holder.itemView.startFadeSpringAnimation(stiffness = alphaStiffness, targetValue = alphaInitialValue) { + holder.itemView.alpha = alphaFinalValue + holder.itemView.elevation = elevationFinalValue + onAnimateRemoveFinished(holder) } } return true } + + override fun startOldHolderChangeAnimation( + oldHolder: RecyclerView.ViewHolder, + translationX: Float, + translationY: Float + ): Boolean { + oldHolder.startChangeAnimation( + alphaTargetValue = alphaInitialValue, + translationXTargetValue = translationX, + translationYTargetValue = translationY, + oldItem = true + ) + return true + } + + override fun startNewHolderChangeAnimation( + newHolder: RecyclerView.ViewHolder + ): Boolean { + onNewViewAnimateChangeStarted(newHolder) + + newHolder.startChangeAnimation( + alphaTargetValue = alphaFinalValue, + translationXTargetValue = 0f, + translationYTargetValue = 0f, + oldItem = false + ) + return true + } + + private fun RecyclerView.ViewHolder.startChangeAnimation( + alphaTargetValue: Float, + translationXTargetValue: Float, + translationYTargetValue: Float, + oldItem: Boolean + ) { + val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> + if (completed) { + itemView.alpha = alphaFinalValue + itemView.elevation = elevationFinalValue + itemView.translationX = 0f + itemView.translationY = 0f + onAnimateChangeFinished(this, oldItem) + } + } + + this.itemView.apply { + elevation = elevationInitialValue + startFadeSpringAnimation( + stiffness = alphaStiffness, + targetValue = alphaTargetValue + ) { + this@startChangeAnimation.onAnimationEnd( + translationYSpring().isRunning.not() + && translationXSpring().isRunning.not() + ) + } + + startTranslationXSpringAnimation(targetValue = translationXTargetValue) { + this@startChangeAnimation.onAnimationEnd( + translationYSpring().isRunning.not() + && fadeSpring().isRunning.not() + ) + } + startTranslationYSpringAnimation(targetValue = translationYTargetValue) { + this@startChangeAnimation.onAnimationEnd( + fadeSpring().isRunning.not() + && translationXSpring().isRunning.not() + ) + } + } + } } \ No newline at end of file diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java index 11921ad..f10b173 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java +++ b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java @@ -44,33 +44,27 @@ protected boolean preAnimateAdd(RecyclerView.ViewHolder holder) { } protected boolean startRemoveAnimation( - RecyclerView.ViewHolder holder, - OnAnimationEndListener onAnimationEndListener + RecyclerView.ViewHolder holder ) { return false; } protected boolean startAddAnimation( - RecyclerView.ViewHolder holder, - @NonNull OnAnimationEndListener onAnimationEndListener + RecyclerView.ViewHolder holder ) { return false; } protected boolean startOldHolderChangeAnimation( RecyclerView.ViewHolder oldHolder, - int fromX, - int fromY, - int toX, - int toY, - @NonNull OnAnimationEndListener onAnimationEndListener + float translationX, + float translationY ) { return false; } protected boolean startNewHolderChangeAnimation( - RecyclerView.ViewHolder newHolder, - @NonNull OnAnimationEndListener onAnimationEndListener + RecyclerView.ViewHolder newHolder ) { return false; } @@ -80,8 +74,7 @@ protected boolean startMoveAnimation( int fromX, int fromY, int toX, - int toY, - @NonNull OnAnimationEndListener onAnimationEndListener + int toY ) { return false; } @@ -232,14 +225,7 @@ public boolean animateRemove(final RecyclerView.ViewHolder holder) { private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { mRemoveAnimations.add(holder); - if (startRemoveAnimation(holder, new OnAnimationEndListener() { - @Override - public void onAnimationEnd() { - dispatchRemoveFinished(holder); - mRemoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - })) { + if (startRemoveAnimation(holder)) { return; } final View view = holder.itemView; @@ -255,13 +241,17 @@ public void onAnimationStart(Animator animator) { public void onAnimationEnd(Animator animator) { animation.setListener(null); view.setAlpha(1); - dispatchRemoveFinished(holder); - mRemoveAnimations.remove(holder); - dispatchFinishedWhenDone(); + onAnimateRemoveFinished(holder); } }).start(); } + public void onAnimateRemoveFinished(final RecyclerView.ViewHolder holder) { + dispatchRemoveFinished(holder); + mRemoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + @Override public boolean animateAdd(final RecyclerView.ViewHolder holder) { resetAnimation(holder); @@ -274,14 +264,7 @@ public boolean animateAdd(final RecyclerView.ViewHolder holder) { void animateAddImpl(final RecyclerView.ViewHolder holder) { mAddAnimations.add(holder); - if (startAddAnimation(holder, new OnAnimationEndListener() { - @Override - public void onAnimationEnd() { - dispatchAddFinished(holder); - mAddAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - })) { + if (startAddAnimation(holder)) { return; } final View view = holder.itemView; @@ -301,13 +284,17 @@ public void onAnimationCancel(Animator animator) { @Override public void onAnimationEnd(Animator animator) { animation.setListener(null); - dispatchAddFinished(holder); - mAddAnimations.remove(holder); - dispatchFinishedWhenDone(); + onAnimateAddFinished(holder); } }).start(); } + public void onAnimateAddFinished(final RecyclerView.ViewHolder holder) { + dispatchAddFinished(holder); + mAddAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + @Override public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { @@ -333,14 +320,7 @@ public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { mMoveAnimations.add(holder); - if (startMoveAnimation(holder, fromX, fromY, toX, toY, new OnAnimationEndListener() { - @Override - public void onAnimationEnd() { - dispatchMoveFinished(holder); - mMoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - })) { + if (startMoveAnimation(holder, fromX, fromY, toX, toY)) { return; } final View view = holder.itemView; @@ -417,29 +397,24 @@ void animateChangeImpl(final ChangeInfo changeInfo) { final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; final View newView = newHolder != null ? newHolder.itemView : null; - if (view != null) { mChangeAnimations.add(changeInfo.oldHolder); + + final float translationX = changeInfo.toX - changeInfo.fromX; + final float translationY = changeInfo.toY - changeInfo.fromY; + final boolean animateChange = startOldHolderChangeAnimation( holder, - changeInfo.fromX, - changeInfo.fromY, - changeInfo.toX, - changeInfo.toY, - new OnAnimationEndListener() { - @Override - public void onAnimationEnd() { - dispatchChangeFinished(changeInfo.oldHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); - } - }); + translationX, + translationY + ); + if (!animateChange) { final ViewPropertyAnimator oldViewAnim = view.animate().setDuration( getChangeDuration()); - oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); - oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); + oldViewAnim.translationX(translationX); + oldViewAnim.translationY(translationY); oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { @@ -452,34 +427,22 @@ public void onAnimationEnd(Animator animator) { view.setAlpha(1); view.setTranslationX(0); view.setTranslationY(0); - dispatchChangeFinished(changeInfo.oldHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); + onAnimateChangeFinished(changeInfo.oldHolder, true); } }).start(); } } if (newView != null) { mChangeAnimations.add(changeInfo.newHolder); - final boolean animateChange = startNewHolderChangeAnimation( - newHolder, - new OnAnimationEndListener() { - @Override - public void onAnimationEnd() { - dispatchChangeFinished(changeInfo.newHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); - } - }); + + final boolean animateChange = startNewHolderChangeAnimation(newHolder); if (!animateChange) { final ViewPropertyAnimator newViewAnimation = newView.animate(); newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()) .alpha(1).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { - dispatchChangeStarting(changeInfo.newHolder, false); - mChangeAnimations.remove(changeInfo.newHolder); - dispatchFinishedWhenDone(); + onNewViewAnimateChangeStarted(changeInfo.newHolder); } @Override @@ -488,15 +451,28 @@ public void onAnimationEnd(Animator animator) { newView.setAlpha(1); newView.setTranslationX(0); newView.setTranslationY(0); - dispatchChangeFinished(changeInfo.newHolder, false); - mChangeAnimations.remove(changeInfo.newHolder); - dispatchFinishedWhenDone(); + onAnimateChangeFinished(changeInfo.newHolder, false); } }).start(); } } } + public void onAnimateChangeFinished( + final RecyclerView.ViewHolder holder, + final boolean oldItem + ) { + dispatchChangeFinished(holder, oldItem); + mChangeAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + + public void onNewViewAnimateChangeStarted(final RecyclerView.ViewHolder newHolder) { + dispatchChangeStarting(newHolder, false); + mChangeAnimations.remove(newHolder); + dispatchFinishedWhenDone(); + } + private void endChangeAnimation(List infoList, RecyclerView.ViewHolder item) { for (int i = infoList.size() - 1; i >= 0; i--) { ChangeInfo changeInfo = infoList.get(i); @@ -539,6 +515,7 @@ public void endAnimation(RecyclerView.ViewHolder item) { final View view = item.itemView; // this will trigger end callback which should set properties to their target values. view.animate().cancel(); + ViewAnimationKt.cancelSpringAnimation(view); // TODO if some other animations are chained to end, how do we cancel them as well? for (int i = mPendingMoves.size() - 1; i >= 0; i--) { MoveInfo moveInfo = mPendingMoves.get(i); @@ -733,7 +710,9 @@ public void endAnimations() { void cancelAll(List viewHolders) { for (int i = viewHolders.size() - 1; i >= 0; i--) { - viewHolders.get(i).itemView.animate().cancel(); + final View view = viewHolders.get(i).itemView; + view.animate().cancel(); + ViewAnimationKt.cancelSpringAnimation(view); } } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt index 15069f7..baa9d2e 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -23,7 +23,7 @@ internal fun elevationViewProperty() = object : FloatPropertyCompat( fun View.elevationSpring( stiffness: Float = SpringForce.STIFFNESS_LOW ) = spring( - key = R.string.elevation_spring_key, + key = SpringAnimationPropertyKey.ELEVATION, property = elevationViewProperty(), stiffness = stiffness ) @@ -35,7 +35,7 @@ fun View.startElevationSpringAnimation( onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { startSpringAnimation( - key = R.string.elevation_spring_key, + key = SpringAnimationPropertyKey.ELEVATION, property = elevationViewProperty(), targetValue = targetValue, stiffness = stiffness, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt index 04583fb..16ec6b7 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt @@ -4,11 +4,11 @@ import android.view.View import android.view.View.MeasureSpec import android.view.ViewGroup -internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { +internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): SpringAnimationPropertyKey { return if (isHeight) { - R.string.expanding_collapsing_height_spring_key + SpringAnimationPropertyKey.HEIGHT } else { - R.string.expanding_collapsing_width_spring_key + SpringAnimationPropertyKey.WIDTH } } diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index 45a212f..af22a44 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -104,7 +104,7 @@ internal fun View.hideFadeOutSpring( fun View.fadeSpring( stiffness: Float = ANIMATION_STIFFNESS ) = spring( - key = R.string.alpha_spring_key, + key = SpringAnimationPropertyKey.ALPHA, property = DynamicAnimation.ALPHA, stiffness = stiffness ) @@ -114,7 +114,7 @@ fun View.startFadeSpringAnimation( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null, ) = startSpringAnimation( - key = R.string.alpha_spring_key, + key = SpringAnimationPropertyKey.ALPHA, property = DynamicAnimation.ALPHA, targetValue = targetValue, stiffness = stiffness, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/SpringAnimationPropertyKey.kt b/jerry/src/main/java/br/alexandregpereira/jerry/SpringAnimationPropertyKey.kt new file mode 100644 index 0000000..fbaa6d0 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/SpringAnimationPropertyKey.kt @@ -0,0 +1,12 @@ +package br.alexandregpereira.jerry + +import androidx.annotation.StringRes + +enum class SpringAnimationPropertyKey(@StringRes val id: Int) { + ALPHA(R.string.alpha_spring_key), + ELEVATION(R.string.elevation_spring_key), + TRANSLATION_X(R.string.translation_x_spring_key), + TRANSLATION_Y(R.string.translation_y_spring_key), + HEIGHT(R.string.expanding_collapsing_height_spring_key), + WIDTH(R.string.expanding_collapsing_width_spring_key) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt new file mode 100644 index 0000000..3bc8d40 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt @@ -0,0 +1,53 @@ +package br.alexandregpereira.jerry + +import android.view.View +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce + +fun View.translationXSpring( + stiffness: Float = SpringForce.STIFFNESS_LOW +) = spring( + key = SpringAnimationPropertyKey.TRANSLATION_X, + property = DynamicAnimation.TRANSLATION_X, + stiffness = stiffness +) + +fun View.startTranslationXSpringAnimation( + targetValue: Float, + stiffness: Float = SpringForce.STIFFNESS_LOW, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) { + startSpringAnimation( + key = SpringAnimationPropertyKey.TRANSLATION_X, + property = DynamicAnimation.TRANSLATION_X, + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = onAnimationEnd?.let { + R.string.translation_x_end_listener_key to onAnimationEnd + } + ) +} + +fun View.translationYSpring( + stiffness: Float = SpringForce.STIFFNESS_LOW +) = spring( + key = SpringAnimationPropertyKey.TRANSLATION_Y, + property = DynamicAnimation.TRANSLATION_Y, + stiffness = stiffness +) + +fun View.startTranslationYSpringAnimation( + targetValue: Float, + stiffness: Float = SpringForce.STIFFNESS_LOW, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) { + startSpringAnimation( + key = SpringAnimationPropertyKey.TRANSLATION_Y, + property = DynamicAnimation.TRANSLATION_Y, + targetValue = targetValue, + stiffness = stiffness, + endListenerPair = onAnimationEnd?.let { + R.string.translation_y_end_listener_key to onAnimationEnd + } + ) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index ac030c5..31eac05 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -26,13 +26,25 @@ enum class AnimationMode { POP_ANIMATION_MODE } +fun View.cancelSpringAnimation() { + SpringAnimationPropertyKey.values().forEach { + getSpringAnimation(it)?.cancel() + } +} + +fun View.skipToEndSpringAnimation() { + SpringAnimationPropertyKey.values().forEach { property -> + getSpringAnimation(property)?.takeIf { it.canSkipToEnd() }?.skipToEnd() + } +} + fun View.spring( - key: Int, + key: SpringAnimationPropertyKey, property: FloatPropertyCompat, dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, stiffness: Float = SpringForce.STIFFNESS_LOW ): SpringAnimation { - var springAnimation = getTag(key) as? SpringAnimation + var springAnimation = getSpringAnimation(key) if (springAnimation == null) { springAnimation = SpringAnimation(this, property).apply { spring = SpringForce().apply { @@ -40,13 +52,13 @@ fun View.spring( this.stiffness = stiffness } } - setTag(key, springAnimation) + setTag(key.id, springAnimation) } return springAnimation } fun View.startSpringAnimation( - key: Int, + key: SpringAnimationPropertyKey, property: FloatPropertyCompat, targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, @@ -90,6 +102,10 @@ internal fun SpringAnimation.addSpringEndListener( } } +fun View.getSpringAnimation(key: SpringAnimationPropertyKey): SpringAnimation? { + return getTag(key.id) as? SpringAnimation +} + internal fun View.getSpringEndListener(key: Int): DynamicAnimation.OnAnimationEndListener? { return getTag(key).run { this as? DynamicAnimation.OnAnimationEndListener diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index 0dd3bee..a3e11fd 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -10,6 +10,12 @@ elevation_spring_key elevation_end_listener_key + translation_x_spring_key + translation_x_end_listener_key + + translation_y_spring_key + translation_y_end_listener_key + expanding_collapsing_height_original_value_key expanding_collapsing_height_spring_key expanding_collapsing_height_end_listener_key From 518e674d86fbad7c0e2f89fc87b6d5543e2406b2 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Wed, 14 Oct 2020 00:19:57 -0300 Subject: [PATCH 18/33] Create move spring animations --- .../jerry/app/RecyclerViewActivity.kt | 2 +- .../jerry/app/SpringItemAnimator.kt | 40 ++++++++++++++++++- .../jerry/BaseItemAnimator.java | 25 +++++++----- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt index 1ce2034..b1cd9ad 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt @@ -74,7 +74,7 @@ class RecyclerViewActivity : AppCompatActivity() { } button6.setOnClickListener { - adapter.setData(list2) + adapter.setData(list.subList(0, 3) + list[4]) } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index 7a483a5..f2f518a 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -65,7 +65,10 @@ class SpringItemAnimator : BaseItemAnimator() { return@startElevationSpringAnimation } - holder.itemView.startFadeSpringAnimation(stiffness = alphaStiffness, targetValue = alphaInitialValue) { + holder.itemView.startFadeSpringAnimation( + stiffness = alphaStiffness, + targetValue = alphaInitialValue + ) { holder.itemView.alpha = alphaFinalValue holder.itemView.elevation = elevationFinalValue onAnimateRemoveFinished(holder) @@ -74,6 +77,41 @@ class SpringItemAnimator : BaseItemAnimator() { return true } + override fun startMoveAnimation( + holder: RecyclerView.ViewHolder, + deltaX: Int, + deltaY: Int + ): Boolean { + val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> + if (completed) { + onAnimateMoveFinished(this) + } + } + + holder.itemView.apply { + val translationXTargetValue = if (deltaX != 0) 0f else translationX + val translationYTargetValue = if (deltaY != 0) 0f else translationY + + startTranslationXSpringAnimation(targetValue = translationXTargetValue) { canceled -> + if (canceled) { + if (deltaX != 0) translationX = 0f + } + holder.onAnimationEnd( + translationYSpring().isRunning.not() + ) + } + startTranslationYSpringAnimation(targetValue = translationYTargetValue) { canceled -> + if (canceled) { + if (deltaY != 0) translationY = 0f + } + holder.onAnimationEnd( + translationXSpring().isRunning.not() + ) + } + } + return true + } + override fun startOldHolderChangeAnimation( oldHolder: RecyclerView.ViewHolder, translationX: Float, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java index f10b173..4c0dd1d 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java +++ b/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java @@ -71,10 +71,8 @@ protected boolean startNewHolderChangeAnimation( protected boolean startMoveAnimation( RecyclerView.ViewHolder holder, - int fromX, - int fromY, - int toX, - int toY + int deltaX, + int deltaY ) { return false; } @@ -320,12 +318,15 @@ public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { mMoveAnimations.add(holder); - if (startMoveAnimation(holder, fromX, fromY, toX, toY)) { - return; - } + final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; + + if (startMoveAnimation(holder, deltaX, deltaY)) { + return; + } + if (deltaX != 0) { view.animate().translationX(0); } @@ -355,13 +356,17 @@ public void onAnimationCancel(Animator animator) { @Override public void onAnimationEnd(Animator animator) { animation.setListener(null); - dispatchMoveFinished(holder); - mMoveAnimations.remove(holder); - dispatchFinishedWhenDone(); + onAnimateMoveFinished(holder); } }).start(); } + public void onAnimateMoveFinished(final RecyclerView.ViewHolder holder) { + dispatchMoveFinished(holder); + mMoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { From 7c506102ad9c930fab489f80705fbfc57a9fec1d Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Wed, 14 Oct 2020 12:47:13 -0300 Subject: [PATCH 19/33] Organize packages --- .../jerry/app/SpringItemAnimator.kt | 2 +- .../animation/CollapseAnimationActivity.kt | 10 ++-- .../CollapseFadingAnimationActivity.kt | 8 +-- .../app/animation/ExpandAnimationActivity.kt | 8 +-- .../ExpandFadingAnimationActivity.kt | 10 ++-- .../app/animation/FadeAnimationActivity.kt | 2 +- .../TextExpandableAnimationActivity.kt | 2 +- .../jerry/FadeSpringAnimation.kt | 51 ++++++------------- .../alexandregpereira/jerry/ViewAnimation.kt | 6 +-- .../{ => animator}/BaseItemAnimator.java | 26 +++++----- .../{ => expandable}/ExpandableAnimation.kt | 6 ++- .../ExpandableAnimationCommon.kt | 8 ++- .../ExpandableFadingAnimation.kt | 6 ++- .../ExpandableFadingSpringAnimation.kt | 24 +++++---- .../ExpandableSpingAnimation.kt | 15 ++++-- .../WidthHeightViewProperty.kt | 2 +- .../jerry/{ => textview}/TextViewAnimation.kt | 11 +++- .../{ => textview}/TextViewSpringAnimation.kt | 9 +++- 18 files changed, 113 insertions(+), 93 deletions(-) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => animator}/BaseItemAnimator.java (96%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/ExpandableAnimation.kt (96%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/ExpandableAnimationCommon.kt (91%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/ExpandableFadingAnimation.kt (93%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/ExpandableFadingSpringAnimation.kt (83%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/ExpandableSpingAnimation.kt (92%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => expandable}/WidthHeightViewProperty.kt (97%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => textview}/TextViewAnimation.kt (81%) rename jerry/src/main/java/br/alexandregpereira/jerry/{ => textview}/TextViewSpringAnimation.kt (84%) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt index f2f518a..6a69cf1 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt @@ -3,7 +3,7 @@ package br.alexandregpereira.jerry.app import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.ANIMATION_STIFFNESS -import br.alexandregpereira.jerry.BaseItemAnimator +import br.alexandregpereira.jerry.animator.BaseItemAnimator import br.alexandregpereira.jerry.dpToPx import br.alexandregpereira.jerry.fadeSpring import br.alexandregpereira.jerry.startElevationSpringAnimation diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 1b946e0..7d15705 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -5,12 +5,12 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat -import br.alexandregpereira.jerry.animateWidthVisibility +import br.alexandregpereira.jerry.expandable.animateWidthVisibility import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightSpring -import br.alexandregpereira.jerry.collapseWidthSpring -import br.alexandregpereira.jerry.expandHeightSpring -import br.alexandregpereira.jerry.expandWidthSpring +import br.alexandregpereira.jerry.expandable.collapseHeightSpring +import br.alexandregpereira.jerry.expandable.collapseWidthSpring +import br.alexandregpereira.jerry.expandable.expandHeightSpring +import br.alexandregpereira.jerry.expandable.expandWidthSpring import kotlinx.android.synthetic.main.activity_collapse_animation.* import kotlinx.android.synthetic.main.container_animation_info.view.* diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt index 19fede0..fdf8c30 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightFadingSpring -import br.alexandregpereira.jerry.collapseWidthFadingSpring -import br.alexandregpereira.jerry.expandHeightFadingSpring -import br.alexandregpereira.jerry.expandWidthFadingSpring +import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring +import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring import kotlinx.android.synthetic.main.activity_collapse_fading_animation.* class CollapseFadingAnimationActivity : AppCompatActivity() { diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt index 86c997c..207687c 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightSpring -import br.alexandregpereira.jerry.collapseWidthSpring -import br.alexandregpereira.jerry.expandHeightSpring -import br.alexandregpereira.jerry.expandWidthSpring +import br.alexandregpereira.jerry.expandable.collapseHeightSpring +import br.alexandregpereira.jerry.expandable.collapseWidthSpring +import br.alexandregpereira.jerry.expandable.expandHeightSpring +import br.alexandregpereira.jerry.expandable.expandWidthSpring import kotlinx.android.synthetic.main.activity_expand_animation.* class ExpandAnimationActivity : AppCompatActivity() { diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt index f726ad8..42e9f6c 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt @@ -5,12 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.collapseHeightFadingSpring -import br.alexandregpereira.jerry.collapseWidthFading -import br.alexandregpereira.jerry.collapseWidthFadingSpring -import br.alexandregpereira.jerry.expandHeightFadingSpring -import br.alexandregpereira.jerry.expandWidthFading -import br.alexandregpereira.jerry.expandWidthFadingSpring +import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring +import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring import kotlinx.android.synthetic.main.activity_expand_fading_animation.* class ExpandFadingAnimationActivity : AppCompatActivity() { diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index 4bde739..9b8209c 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -6,7 +6,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.fadeOutSpring -import br.alexandregpereira.jerry.setTextFadeSpring +import br.alexandregpereira.jerry.textview.setTextFadeSpring import br.alexandregpereira.jerry.fadeInSpring import kotlinx.android.synthetic.main.activity_fade_animation.* diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt index 8d2788d..ab085e5 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt @@ -7,7 +7,7 @@ import android.os.Handler import android.os.Looper import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.setTextExpandableSpring +import br.alexandregpereira.jerry.textview.setTextExpandableSpring import kotlinx.android.synthetic.main.activity_text_expandable_animation.* class TextExpandableAnimationActivity : AppCompatActivity() { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index af22a44..9e6c260 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -31,7 +31,22 @@ fun View.fadeOutSpring( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { - hideFadeOutSpring(stiffness, hide = ::gone, onAnimationEnd = onAnimationEnd) + if (isVisible().not() || isFadeOutRunning()) { + if (isFadeOutRunning().not()) { + onAnimationEnd?.invoke(false) + } + return + } + startFadeOutRunning() + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = { canceled -> + gone() + onAnimationEnd?.invoke(canceled) + } + ) } /** @@ -67,40 +82,6 @@ fun View.fadeInSpring( ) } -/** - * Start the fade out animation without changing the visibility status. The changes in the - * visibility status is delegate to the function [hide]. - * - * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to - * the object attached when the spring is not at the final position. Default stiffness is - * [ANIMATION_STIFFNESS]. - * @param onAnimationEnd The function to call when the animation is finished. - * - * @see [SpringAnimation] - */ -internal fun View.hideFadeOutSpring( - stiffness: Float, - hide: (() -> Unit)? = null, - onAnimationEnd: ((canceled: Boolean) -> Unit)? -) { - if (isVisible().not() || isFadeOutRunning()) { - if (isFadeOutRunning().not()) { - onAnimationEnd?.invoke(false) - } - return - } - startFadeOutRunning() - - startFadeSpringAnimation( - targetValue = 0f, - stiffness = stiffness, - onAnimationEnd = { canceled -> - hide?.invoke() - onAnimationEnd?.invoke(canceled) - } - ) -} - fun View.fadeSpring( stiffness: Float = ANIMATION_STIFFNESS ) = spring( diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 31eac05..0f009ea 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -121,10 +121,10 @@ internal fun View.getSpringEndListener(key: Int): DynamicAnimation.OnAnimationEn */ internal fun View.isAnimationRunning(key: Int, animationMode: AnimationMode): Boolean { return runCatching { - getTag(key) as Int + getTag(key) as AnimationMode }.getOrElse { - 0 - } == animationMode.ordinal + AnimationMode.NONE_ANIMATION_MODE + } == animationMode } /** diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java similarity index 96% rename from jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java rename to jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java index 4c0dd1d..abb5fd6 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/BaseItemAnimator.java +++ b/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java @@ -1,4 +1,4 @@ -package br.alexandregpereira.jerry; +package br.alexandregpereira.jerry.animator; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -15,6 +15,8 @@ import java.util.ArrayList; import java.util.List; +import br.alexandregpereira.jerry.ViewAnimationKt; + /** * This implementation of {@link RecyclerView.ItemAnimator} provides basic * animations on remove, add, and move events that happen to the items in @@ -25,19 +27,19 @@ public abstract class BaseItemAnimator extends SimpleItemAnimator { private static final boolean DEBUG = false; - private ArrayList mPendingRemovals = new ArrayList<>(); - private ArrayList mPendingAdditions = new ArrayList<>(); - private ArrayList mPendingMoves = new ArrayList<>(); - private ArrayList mPendingChanges = new ArrayList<>(); + final private ArrayList mPendingRemovals = new ArrayList<>(); + final private ArrayList mPendingAdditions = new ArrayList<>(); + final private ArrayList mPendingMoves = new ArrayList<>(); + final private ArrayList mPendingChanges = new ArrayList<>(); - ArrayList> mAdditionsList = new ArrayList<>(); - ArrayList> mMovesList = new ArrayList<>(); - ArrayList> mChangesList = new ArrayList<>(); + final ArrayList> mAdditionsList = new ArrayList<>(); + final ArrayList> mMovesList = new ArrayList<>(); + final ArrayList> mChangesList = new ArrayList<>(); - ArrayList mAddAnimations = new ArrayList<>(); - ArrayList mMoveAnimations = new ArrayList<>(); - ArrayList mRemoveAnimations = new ArrayList<>(); - ArrayList mChangeAnimations = new ArrayList<>(); + final ArrayList mAddAnimations = new ArrayList<>(); + final ArrayList mMoveAnimations = new ArrayList<>(); + final ArrayList mRemoveAnimations = new ArrayList<>(); + final ArrayList mChangeAnimations = new ArrayList<>(); protected boolean preAnimateAdd(RecyclerView.ViewHolder holder) { return false; diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt similarity index 96% rename from jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt index 237d3df..e895214 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt @@ -1,9 +1,13 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.Transformation +import br.alexandregpereira.jerry.ANIMATION_SHORT_TIME +import br.alexandregpereira.jerry.gone +import br.alexandregpereira.jerry.isVisible +import br.alexandregpereira.jerry.visible /** * Animates expanding the height and changes the visibility status to VISIBLE. diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt similarity index 91% rename from jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt index 16ec6b7..a772ec9 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt @@ -1,8 +1,14 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View import android.view.View.MeasureSpec import android.view.ViewGroup +import br.alexandregpereira.jerry.AnimationMode +import br.alexandregpereira.jerry.R +import br.alexandregpereira.jerry.SpringAnimationPropertyKey +import br.alexandregpereira.jerry.isAnimationRunning +import br.alexandregpereira.jerry.setAnimationRunning +import br.alexandregpereira.jerry.visible internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): SpringAnimationPropertyKey { return if (isHeight) { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingAnimation.kt similarity index 93% rename from jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingAnimation.kt index 558b15a..8e6d0eb 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingAnimation.kt @@ -1,6 +1,10 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View +import br.alexandregpereira.jerry.ANIMATION_SHORT_TIME +import br.alexandregpereira.jerry.fadeIn +import br.alexandregpereira.jerry.hideFadeOut +import br.alexandregpereira.jerry.isVisible /** * Uses the [expandHeight] and [fadeIn] animations in sequence. This animation diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt similarity index 83% rename from jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt index 2bf0d1e..39c116c 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableFadingSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt @@ -1,7 +1,11 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View import androidx.dynamicanimation.animation.SpringAnimation +import br.alexandregpereira.jerry.ANIMATION_STIFFNESS +import br.alexandregpereira.jerry.fadeInSpring +import br.alexandregpereira.jerry.fadeOutSpring +import br.alexandregpereira.jerry.isVisible fun View.animateHeightFadingVisibility( visible: Boolean, @@ -28,9 +32,9 @@ fun View.animateWidthFadingVisibility( } /** - * Uses the [expandHeight] and [fadeIn] animations in sequence. This animation + * Uses the [expandHeightSpring] and [fadeInSpring] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the - * [collapseHeightFading] method is called. + * [collapseHeightFadingSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -45,9 +49,9 @@ fun View.expandHeightFadingSpring( ) = expandFadingSpring(stiffness, isHeight = true, onAnimationEnd = onAnimationEnd) /** - * Uses the [expandWidth] and [fadeIn] animations in sequence. This animation + * Uses the [expandWidthSpring] and [fadeInSpring] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the - * [collapseWidthFading] method is called. + * [collapseWidthFadingSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -62,9 +66,9 @@ fun View.expandWidthFadingSpring( ) = expandFadingSpring(stiffness, isHeight = false, onAnimationEnd = onAnimationEnd) /** - * Uses the [hideFadeOut] and [collapseHeight] animations in sequence. This animation + * Uses the [fadeOutSpring] and [collapseHeightSpring] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the - * [expandHeightFading] method is called. + * [expandHeightFadingSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -79,9 +83,9 @@ fun View.collapseHeightFadingSpring( ) = collapseFadingSpring(stiffness, isHeight = true, onAnimationEnd = onAnimationEnd) /** - * Uses the [hideFadeOut] and [collapseWidth] animations in sequence. This animation + * Uses the [fadeOutSpring] and [collapseWidthSpring] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the - * [expandWidthFading] method is called. + * [expandWidthFadingSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -105,7 +109,7 @@ private fun View.collapseFadingSpring( return } - hideFadeOutSpring(stiffness = stiffness * 2f) { + fadeOutSpring(stiffness = stiffness * 2f) { collapseSpring( stiffness = stiffness * 2f, isHeight = isHeight, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt similarity index 92% rename from jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt index c3f21bd..53a803d 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt @@ -1,7 +1,12 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View import androidx.dynamicanimation.animation.SpringAnimation +import br.alexandregpereira.jerry.ANIMATION_STIFFNESS +import br.alexandregpereira.jerry.gone +import br.alexandregpereira.jerry.isVisible +import br.alexandregpereira.jerry.startSpringAnimation +import br.alexandregpereira.jerry.visible fun View.animateHeightVisibility( visible: Boolean, @@ -32,7 +37,7 @@ fun View.animateWidthVisibility( /** * Animates collapsing the height and changes the visibility status to GONE. * This animation handles double click. This method can be reverted in the middle of the animation - * if the [expandHeight] method is called. + * if the [expandHeightSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -55,7 +60,7 @@ fun View.collapseHeightSpring( /** * Animates collapsing the width and changes the visibility status to GONE. * This animation handles double click. This method can be reverted in the middle of the animation - * if the [expandWidth] method is called. + * if the [expandWidthSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -78,7 +83,7 @@ fun View.collapseWidthSpring( /** * Animates expanding the height and changes the visibility status to VISIBLE. * This animation handles double click. This method can be reverted in the middle of the animation - * if the [collapseHeight] method is called. Any alteration of the parent width during the this + * if the [collapseHeightSpring] method is called. Any alteration of the parent width during the this * animation makes glitches in the animation. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to @@ -102,7 +107,7 @@ fun View.expandHeightSpring( /** * Animates expanding the width and changes the visibility status to VISIBLE. * This animation handles double click. This method can be reverted in the middle of the animation - * if the [collapseWidth] method is called. Any alteration of the parent width during the this + * if the [collapseWidthSpring] method is called. Any alteration of the parent width during the this * animation makes glitches in the animation. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/WidthHeightViewProperty.kt similarity index 97% rename from jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/expandable/WidthHeightViewProperty.kt index 77f959e..349ec5f 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/WidthHeightViewProperty.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/WidthHeightViewProperty.kt @@ -1,4 +1,4 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.expandable import android.view.View import android.view.ViewGroup diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt similarity index 81% rename from jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt index d03fb73..b672a43 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt @@ -1,8 +1,17 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.textview import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.TextView +import br.alexandregpereira.jerry.ANIMATION_SHORT_TIME +import br.alexandregpereira.jerry.expandable.collapseHeightFading +import br.alexandregpereira.jerry.expandable.expandHeightFading +import br.alexandregpereira.jerry.expandable.isCollapsingRunning +import br.alexandregpereira.jerry.fadeInAnimation +import br.alexandregpereira.jerry.fadeOutAnimation +import br.alexandregpereira.jerry.isFadeOutRunning +import br.alexandregpereira.jerry.isVisible +import br.alexandregpereira.jerry.onAnimationEnd /** * Uses the [setTextFade], [expandHeightFading] or [collapseHeightFading] animation methods diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewSpringAnimation.kt similarity index 84% rename from jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewSpringAnimation.kt index 376f653..5d945a6 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TextViewSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewSpringAnimation.kt @@ -1,7 +1,14 @@ -package br.alexandregpereira.jerry +package br.alexandregpereira.jerry.textview import android.widget.TextView import androidx.dynamicanimation.animation.SpringAnimation +import br.alexandregpereira.jerry.ANIMATION_STIFFNESS +import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring +import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandable.isCollapsingRunning +import br.alexandregpereira.jerry.isFadeOutRunning +import br.alexandregpereira.jerry.isVisible +import br.alexandregpereira.jerry.startFadeSpringAnimation /** * Uses the [setTextFadeSpring], [expandHeightFadingSpring] or [collapseHeightFadingSpring] From 467cc705fe935de1f553c3de37654ddd5fca274b Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Wed, 14 Oct 2020 13:30:12 -0300 Subject: [PATCH 20/33] Add commentaries --- .../jerry/animator/BaseItemAnimator.java | 75 +++++++++++++++++-- .../ExpandableFadingSpringAnimation.kt | 8 ++ .../expandable/ExpandableSpingAnimation.kt | 8 ++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java b/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java index abb5fd6..e8a8b79 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java +++ b/jerry/src/main/java/br/alexandregpereira/jerry/animator/BaseItemAnimator.java @@ -20,7 +20,12 @@ /** * This implementation of {@link RecyclerView.ItemAnimator} provides basic * animations on remove, add, and move events that happen to the items in - * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. + * a RecyclerView. The functions {@link BaseItemAnimator#preAnimateAdd}, + * {@link BaseItemAnimator#startRemoveAnimation}, + * {@link BaseItemAnimator#startAddAnimation}, + * {@link BaseItemAnimator#startOldHolderChangeAnimation}, + * {@link BaseItemAnimator#startNewHolderChangeAnimation} and + * {@link BaseItemAnimator#startMoveAnimation} gives the option to override the default animations. * * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) */ @@ -41,22 +46,62 @@ public abstract class BaseItemAnimator extends SimpleItemAnimator { final ArrayList mRemoveAnimations = new ArrayList<>(); final ArrayList mChangeAnimations = new ArrayList<>(); + /** + * It is call before the {@link BaseItemAnimator#startAddAnimation} execution. Utilizes + * this function to change the view properties to animate on the + * {@link BaseItemAnimator#startAddAnimation}. + * + * @param holder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @return true if the method was override and you want to animate the add action. + */ protected boolean preAnimateAdd(RecyclerView.ViewHolder holder) { return false; } + /** + * Override this function if you want to change the default remove animation. + * When the animation finishes, or when the animation is canceled, + * the method {@link BaseItemAnimator#onAnimateRemoveFinished} needs to be called. + * + * @param holder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @return true if the method was override and you want to animate the remove action. + */ protected boolean startRemoveAnimation( RecyclerView.ViewHolder holder ) { return false; } + /** + * Override this function if you want to change the default add animation. + * When the animation finishes, or when the animation is canceled, + * the method {@link BaseItemAnimator#onAnimateAddFinished} needs to be called. + * + * @param holder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @return true if the method was override and you want to animate the remove action. + */ protected boolean startAddAnimation( RecyclerView.ViewHolder holder ) { return false; } + /** + * Override this function if you want to change the default change animation. + * When the animation finishes, or when the animation is canceled, + * the method {@link BaseItemAnimator#onAnimateChangeFinished} needs to be called. + * The method {@link BaseItemAnimator#startNewHolderChangeAnimation} needs to be override + * too when this method is override. + * + * @param oldHolder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @param translationX The final value of the {@link View#getTranslationX} + * @param translationY The final value of the {@link View#getTranslationY} + * @return true if the method was override and you want to animate the remove action. + */ protected boolean startOldHolderChangeAnimation( RecyclerView.ViewHolder oldHolder, float translationX, @@ -65,12 +110,36 @@ protected boolean startOldHolderChangeAnimation( return false; } + /** + * Override this function if you want to change the default change animation. + * When the animation finishes, or when the animation is canceled, + * the method {@link BaseItemAnimator#onAnimateChangeFinished} needs to be called. + * The method {@link BaseItemAnimator#startOldHolderChangeAnimation} needs to be override + * too when this method is override. + * + * @param newHolder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @return true if the method was override and you want to animate the remove action. + */ protected boolean startNewHolderChangeAnimation( RecyclerView.ViewHolder newHolder ) { return false; } + /** + * Override this function if you want to change the default add animation. + * When the animation finishes, or when the animation is canceled, + * the method {@link BaseItemAnimator#onAnimateMoveFinished} needs to be called. + * + * @param holder The {@link RecyclerView.ViewHolder} who contains the view that will + * be animated. + * @param deltaX Indicates if the {@link View#getTranslationX} needs to be changed to the + * original position (0f). deltaX needs to be different to 0. + * @param deltaY Indicates if the {@link View#getTranslationY} needs to be changed to the + * original position (0f). deltaY needs to be different to 0. + * @return true if the method was override and you want to animate the remove action. + */ protected boolean startMoveAnimation( RecyclerView.ViewHolder holder, int deltaX, @@ -79,10 +148,6 @@ protected boolean startMoveAnimation( return false; } - public interface OnAnimationEndListener { - void onAnimationEnd(); - } - private static class MoveInfo { public RecyclerView.ViewHolder holder; public int fromX, fromY, toX, toY; diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt index 39c116c..a05a02a 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt @@ -7,6 +7,10 @@ import br.alexandregpereira.jerry.fadeInSpring import br.alexandregpereira.jerry.fadeOutSpring import br.alexandregpereira.jerry.isVisible +/** + * Animates the View visibility depending of the [visible] flag. If [visible] is true, the + * [expandHeightFadingSpring] is called, else the [collapseHeightFadingSpring] is called. + */ fun View.animateHeightFadingVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, @@ -19,6 +23,10 @@ fun View.animateHeightFadingVisibility( } } +/** + * Animates the View visibility depending of the [visible] flag. If [visible] is true, the + * [expandWidthFadingSpring] is called, else the [collapseWidthFadingSpring] is called. + */ fun View.animateWidthFadingVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt index 53a803d..5e00a4a 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt @@ -8,6 +8,10 @@ import br.alexandregpereira.jerry.isVisible import br.alexandregpereira.jerry.startSpringAnimation import br.alexandregpereira.jerry.visible +/** + * Animates the View visibility depending of the [visible] flag. If [visible] is true, the + * [expandHeightSpring] is called, else the [collapseHeightSpring] is called. + */ fun View.animateHeightVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, @@ -21,6 +25,10 @@ fun View.animateHeightVisibility( } } +/** + * Animates the View visibility depending of the [visible] flag. If [visible] is true, the + * [expandWidthSpring] is called, else the [collapseWidthSpring] is called. + */ fun View.animateWidthVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, From b4af9ea5a8119021577d4618ec72a2f69c9bdfc7 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Wed, 14 Oct 2020 14:22:30 -0300 Subject: [PATCH 21/33] create spring activities --- app/src/main/AndroidManifest.xml | 16 ++- .../jerry/app/MainActivity.kt | 52 +++++++++- .../animation/CollapseAnimationActivity.kt | 30 +++--- .../CollapseFadingAnimationActivity.kt | 28 +++--- .../CollapseFadingSpringAnimationActivity.kt | 68 +++++++++++++ .../CollapseSpringAnimationActivity.kt | 99 +++++++++++++++++++ .../app/animation/ExpandAnimationActivity.kt | 40 ++++---- .../ExpandFadingAnimationActivity.kt | 32 +++--- .../ExpandFadingSpringAnimationActivity.kt | 76 ++++++++++++++ .../ExpandSpringAnimationActivity.kt | 92 +++++++++++++++++ .../app/animation/FadeAnimationActivity.kt | 32 ++---- .../animation/FadeSpringAnimationActivity.kt | 56 +++++++++++ .../jerry/app/animation/Iterator.kt | 17 ++++ .../TextExpandableAnimationActivity.kt | 20 ++-- .../TextExpandableSpringAnimationActivity.kt | 76 ++++++++++++++ .../ExampleAdapter.kt} | 89 ++--------------- .../app/recyclerview/RecyclerViewActivity.kt | 64 ++++++++++++ .../RecyclerViewSpringActivity.kt | 66 +++++++++++++ .../{ => recyclerview}/SpringItemAnimator.kt | 2 +- app/src/main/res/layout/activity_main.xml | 30 ++++-- .../res/layout/activity_recycler_view.xml | 2 +- .../activity_text_expandable_animation.xml | 1 + app/src/main/res/values/strings.xml | 15 ++- .../alexandregpereira/jerry/FadeAnimation.kt | 2 +- .../jerry/FadeSpringAnimation.kt | 59 +++++++---- .../ExpandableFadingSpringAnimation.kt | 5 +- .../jerry/textview/TextViewAnimation.kt | 2 +- 27 files changed, 843 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingSpringAnimationActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseSpringAnimationActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingSpringAnimationActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandSpringAnimationActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeSpringAnimationActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/Iterator.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableSpringAnimationActivity.kt rename app/src/main/java/br/alexandregpereira/jerry/app/{RecyclerViewActivity.kt => recyclerview/ExampleAdapter.kt} (51%) create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewActivity.kt create mode 100644 app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt rename app/src/main/java/br/alexandregpereira/jerry/app/{ => recyclerview}/SpringItemAnimator.kt (99%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f28b886..16c389a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,19 +17,33 @@ + + + + + + + + + + + + - + + + \ No newline at end of file diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt index 5b2b788..33a1f85 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/MainActivity.kt @@ -9,17 +9,26 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.app.animation.CollapseAnimationActivity import br.alexandregpereira.jerry.app.animation.CollapseFadingAnimationActivity +import br.alexandregpereira.jerry.app.animation.CollapseSpringAnimationActivity +import br.alexandregpereira.jerry.app.animation.CollapseFadingSpringAnimationActivity import br.alexandregpereira.jerry.app.animation.ExpandAnimationActivity import br.alexandregpereira.jerry.app.animation.ExpandFadingAnimationActivity +import br.alexandregpereira.jerry.app.animation.ExpandSpringAnimationActivity +import br.alexandregpereira.jerry.app.animation.ExpandFadingSpringAnimationActivity import br.alexandregpereira.jerry.app.animation.FadeAnimationActivity +import br.alexandregpereira.jerry.app.animation.FadeSpringAnimationActivity import br.alexandregpereira.jerry.app.animation.TextExpandableAnimationActivity +import br.alexandregpereira.jerry.app.animation.TextExpandableSpringAnimationActivity +import br.alexandregpereira.jerry.app.recyclerview.RecyclerViewActivity +import br.alexandregpereira.jerry.app.recyclerview.RecyclerViewSpringActivity import br.alexandregpereira.jerry.app.widgets.configMaterialShapeDrawable import kotlinx.android.synthetic.main.activity_main.* -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(R.layout.activity_main) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + + appBarLayout.isLiftOnScroll = true componentsRecycler.apply { layoutManager = LinearLayoutManager(this@MainActivity) @@ -28,24 +37,48 @@ class MainActivity : AppCompatActivity() { AnimationComponent.FADE.ordinal -> startActivity( FadeAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.FADE_SPRING.ordinal -> startActivity( + FadeSpringAnimationActivity.getStartIntent(this@MainActivity) + ) + AnimationComponent.COLLAPSE_FADING.ordinal -> startActivity( + CollapseFadingAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.COLLAPSE_FADING.ordinal -> startActivity( CollapseFadingAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.COLLAPSE_FADING_SPRING.ordinal -> startActivity( + CollapseFadingSpringAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.EXPAND_FADING.ordinal -> startActivity( ExpandFadingAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.EXPAND_FADING_SPRING.ordinal -> startActivity( + ExpandFadingSpringAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.COLLAPSE.ordinal -> startActivity( CollapseAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.COLLAPSE_SPRING.ordinal -> startActivity( + CollapseSpringAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.EXPAND.ordinal -> startActivity( ExpandAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.EXPAND_SPRING.ordinal -> startActivity( + ExpandSpringAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.TEXT_EXPANDABLE.ordinal -> startActivity( TextExpandableAnimationActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.TEXT_EXPANDABLE_SPRING.ordinal -> startActivity( + TextExpandableSpringAnimationActivity.getStartIntent(this@MainActivity) + ) AnimationComponent.RECYCLER.ordinal -> startActivity( RecyclerViewActivity.getStartIntent(this@MainActivity) ) + AnimationComponent.RECYCLER_SPRING.ordinal -> startActivity( + RecyclerViewSpringActivity.getStartIntent(this@MainActivity) + ) } } } @@ -57,7 +90,20 @@ fun getAnimationComponentsName(): List { } enum class AnimationComponent { - COLLAPSE, COLLAPSE_FADING, EXPAND, EXPAND_FADING, FADE, TEXT_EXPANDABLE, RECYCLER + COLLAPSE, + COLLAPSE_SPRING, + COLLAPSE_FADING, + COLLAPSE_FADING_SPRING, + EXPAND, + EXPAND_SPRING, + EXPAND_FADING, + EXPAND_FADING_SPRING, + FADE, + FADE_SPRING, + TEXT_EXPANDABLE, + TEXT_EXPANDABLE_SPRING, + RECYCLER, + RECYCLER_SPRING } class MainAdapter( diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt index 7d15705..6ff8ba7 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseAnimationActivity.kt @@ -7,10 +7,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import br.alexandregpereira.jerry.expandable.animateWidthVisibility import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.expandable.collapseHeightSpring -import br.alexandregpereira.jerry.expandable.collapseWidthSpring -import br.alexandregpereira.jerry.expandable.expandHeightSpring -import br.alexandregpereira.jerry.expandable.expandWidthSpring +import br.alexandregpereira.jerry.expandable.collapseHeight +import br.alexandregpereira.jerry.expandable.collapseWidth +import br.alexandregpereira.jerry.expandable.expandHeight +import br.alexandregpereira.jerry.expandable.expandWidth import kotlinx.android.synthetic.main.activity_collapse_animation.* import kotlinx.android.synthetic.main.container_animation_info.view.* @@ -33,9 +33,9 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a resources.getDimension(R.dimen.strong_elevation) ) - collapseTextView.expandHeightSpring() + collapseTextView.expandHeight() collapseTextButton.setOnClickListener { - collapseTextView.collapseHeightSpring( + collapseTextView.collapseHeight( onProgressChange = { interpolatedTime -> collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() } @@ -45,7 +45,7 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseExpandTextButton.setOnClickListener { - collapseTextView.expandHeightSpring( + collapseTextView.expandHeight( onProgressChange = { interpolatedTime -> collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() } @@ -55,19 +55,19 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseFixedTextButton.setOnClickListener { - collapseFixedTextView.collapseHeightSpring() + collapseFixedTextView.collapseHeight() } collapseExpandFixedTextButton.setOnClickListener { - collapseFixedTextView.expandHeightSpring() + collapseFixedTextView.expandHeight() } collapseWidthTextButton.setOnClickListener { - collapseWidthTextView.collapseWidthSpring() + collapseWidthTextView.collapseWidth() } collapseExpandWidthTextButton.setOnClickListener { - collapseWidthTextView.expandWidthSpring() + collapseWidthTextView.expandWidth() } collapseReverseMatchWidthButton.setOnClickListener { @@ -79,19 +79,19 @@ class CollapseAnimationActivity : AppCompatActivity(R.layout.activity_collapse_a } collapseMatchWidthButton.setOnClickListener { - collapseMatchWidthView.collapseWidthSpring() + collapseMatchWidthView.collapseWidth() } collapseExpandMatchWidthButton.setOnClickListener { - collapseMatchWidthView.expandWidthSpring() + collapseMatchWidthView.expandWidth() } collapseFixedWidthButton.setOnClickListener { - collapseFixedWidthView.collapseWidthSpring() + collapseFixedWidthView.collapseWidth() } collapseExpandFixedWidthButton.setOnClickListener { - collapseFixedWidthView.expandWidthSpring() + collapseFixedWidthView.expandWidth() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt index fdf8c30..c967190 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring -import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring -import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring -import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring +import br.alexandregpereira.jerry.expandable.collapseHeightFading +import br.alexandregpereira.jerry.expandable.collapseWidthFading +import br.alexandregpereira.jerry.expandable.expandHeightFading +import br.alexandregpereira.jerry.expandable.expandWidthFading import kotlinx.android.synthetic.main.activity_collapse_fading_animation.* class CollapseFadingAnimationActivity : AppCompatActivity() { @@ -24,43 +24,43 @@ class CollapseFadingAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_collapse_fading_animation) collapseFadingTextButton.setOnClickListener { - collapseFadingTextView.collapseHeightFadingSpring() + collapseFadingTextView.collapseHeightFading() } collapseExpandFadingTextButton.setOnClickListener { - collapseFadingTextView.expandHeightFadingSpring() + collapseFadingTextView.expandHeightFading() } collapseFixedFadingTextButton.setOnClickListener { - collapseFixedFadingTextView.collapseHeightFadingSpring() + collapseFixedFadingTextView.collapseHeightFading() } collapseExpandFixedFadingTextButton.setOnClickListener { - collapseFixedFadingTextView.expandHeightFadingSpring() + collapseFixedFadingTextView.expandHeightFading() } collapseWidthFadingTextButton.setOnClickListener { - collapseWidthFadingTextView.collapseWidthFadingSpring() + collapseWidthFadingTextView.collapseWidthFading() } collapseExpandWidthFadingTextButton.setOnClickListener { - collapseWidthFadingTextView.expandWidthFadingSpring() + collapseWidthFadingTextView.expandWidthFading() } collapseMatchWidthFadingButton.setOnClickListener { - collapseMatchWidthFadingView.collapseWidthFadingSpring() + collapseMatchWidthFadingView.collapseWidthFading() } collapseExpandMatchWidthFadingButton.setOnClickListener { - collapseMatchWidthFadingView.expandWidthFadingSpring() + collapseMatchWidthFadingView.expandWidthFading() } collapseFixedWidthFadingButton.setOnClickListener { - collapseFixedWidthFadingView.collapseWidthFadingSpring() + collapseFixedWidthFadingView.collapseWidthFading() } collapseExpandFixedWidthFadingButton.setOnClickListener { - collapseFixedWidthFadingView.expandWidthFadingSpring() + collapseFixedWidthFadingView.expandWidthFading() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingSpringAnimationActivity.kt new file mode 100644 index 0000000..7ab0e36 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseFadingSpringAnimationActivity.kt @@ -0,0 +1,68 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring +import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring +import kotlinx.android.synthetic.main.activity_collapse_fading_animation.* + +class CollapseFadingSpringAnimationActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, CollapseFadingSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_collapse_fading_animation) + + collapseFadingLabel.setText(R.string.collapse_fading_spring) + + collapseFadingTextButton.setOnClickListener { + collapseFadingTextView.collapseHeightFadingSpring() + } + + collapseExpandFadingTextButton.setOnClickListener { + collapseFadingTextView.expandHeightFadingSpring() + } + + collapseFixedFadingTextButton.setOnClickListener { + collapseFixedFadingTextView.collapseHeightFadingSpring() + } + + collapseExpandFixedFadingTextButton.setOnClickListener { + collapseFixedFadingTextView.expandHeightFadingSpring() + } + + collapseWidthFadingTextButton.setOnClickListener { + collapseWidthFadingTextView.collapseWidthFadingSpring() + } + + collapseExpandWidthFadingTextButton.setOnClickListener { + collapseWidthFadingTextView.expandWidthFadingSpring() + } + + collapseMatchWidthFadingButton.setOnClickListener { + collapseMatchWidthFadingView.collapseWidthFadingSpring() + } + + collapseExpandMatchWidthFadingButton.setOnClickListener { + collapseMatchWidthFadingView.expandWidthFadingSpring() + } + + collapseFixedWidthFadingButton.setOnClickListener { + collapseFixedWidthFadingView.collapseWidthFadingSpring() + } + + collapseExpandFixedWidthFadingButton.setOnClickListener { + collapseFixedWidthFadingView.expandWidthFadingSpring() + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseSpringAnimationActivity.kt new file mode 100644 index 0000000..ead33e8 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/CollapseSpringAnimationActivity.kt @@ -0,0 +1,99 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import br.alexandregpereira.jerry.expandable.animateWidthVisibility +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.expandable.collapseHeightSpring +import br.alexandregpereira.jerry.expandable.collapseWidthSpring +import br.alexandregpereira.jerry.expandable.expandHeightSpring +import br.alexandregpereira.jerry.expandable.expandWidthSpring +import kotlinx.android.synthetic.main.activity_collapse_animation.* +import kotlinx.android.synthetic.main.container_animation_info.view.* + +class CollapseSpringAnimationActivity : AppCompatActivity(R.layout.activity_collapse_animation) { + + private var collapseTextViewCount = 1 + private var collapseMatchWidthViewVisible = true + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, CollapseSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + collapseLabel.setText(R.string.collapse_spring) + + ViewCompat.setTranslationZ( + collapseAnimationInfo, + resources.getDimension(R.dimen.strong_elevation) + ) + + collapseTextView.expandHeightSpring() + collapseTextButton.setOnClickListener { + collapseTextView.collapseHeightSpring( + onProgressChange = { interpolatedTime -> + collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() + } + ) { + collapseAnimationInfo.countTextView.text = collapseTextViewCount++.toString() + } + } + + collapseExpandTextButton.setOnClickListener { + collapseTextView.expandHeightSpring( + onProgressChange = { interpolatedTime -> + collapseAnimationInfo.percentageTextView.text = (interpolatedTime * 100).toInt().toString() + } + ) { + collapseAnimationInfo.countTextView.text = collapseTextViewCount++.toString() + } + } + + collapseFixedTextButton.setOnClickListener { + collapseFixedTextView.collapseHeightSpring() + } + + collapseExpandFixedTextButton.setOnClickListener { + collapseFixedTextView.expandHeightSpring() + } + + collapseWidthTextButton.setOnClickListener { + collapseWidthTextView.collapseWidthSpring() + } + + collapseExpandWidthTextButton.setOnClickListener { + collapseWidthTextView.expandWidthSpring() + } + + collapseReverseMatchWidthButton.setOnClickListener { + collapseMatchWidthView.animateWidthVisibility( + visible = collapseMatchWidthViewVisible.not().also { + collapseMatchWidthViewVisible = it + } + ) + } + + collapseMatchWidthButton.setOnClickListener { + collapseMatchWidthView.collapseWidthSpring() + } + + collapseExpandMatchWidthButton.setOnClickListener { + collapseMatchWidthView.expandWidthSpring() + } + + collapseFixedWidthButton.setOnClickListener { + collapseFixedWidthView.collapseWidthSpring() + } + + collapseExpandFixedWidthButton.setOnClickListener { + collapseFixedWidthView.expandWidthSpring() + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt index 207687c..12f85e0 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.expandable.collapseHeightSpring -import br.alexandregpereira.jerry.expandable.collapseWidthSpring -import br.alexandregpereira.jerry.expandable.expandHeightSpring -import br.alexandregpereira.jerry.expandable.expandWidthSpring +import br.alexandregpereira.jerry.expandable.collapseHeight +import br.alexandregpereira.jerry.expandable.collapseWidth +import br.alexandregpereira.jerry.expandable.expandHeight +import br.alexandregpereira.jerry.expandable.expandWidth import kotlinx.android.synthetic.main.activity_expand_animation.* class ExpandAnimationActivity : AppCompatActivity() { @@ -24,67 +24,67 @@ class ExpandAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_expand_animation) expandTextButton.setOnClickListener { - expandTextView.expandHeightSpring() + expandTextView.expandHeight() } expandCollapseTextButton.setOnClickListener { - expandTextView.collapseHeightSpring() + expandTextView.collapseHeight() } expandWidthTextButton.setOnClickListener { - expandWidthTextView.expandWidthSpring() + expandWidthTextView.expandWidth() } expandCollapseWidthTextButton.setOnClickListener { - expandWidthTextView.collapseWidthSpring() + expandWidthTextView.collapseWidth() } expandFixedTextButton.setOnClickListener { - expandFixedTextView.expandHeightSpring() + expandFixedTextView.expandHeight() } expandCollapseFixedTextButton.setOnClickListener { - expandFixedTextView.collapseHeightSpring() + expandFixedTextView.collapseHeight() } expandWidthTextButton.setOnClickListener { - expandWidthTextView.expandWidthSpring() + expandWidthTextView.expandWidth() } expandCollapseWidthTextButton.setOnClickListener { - expandWidthTextView.collapseWidthSpring() + expandWidthTextView.collapseWidth() } expandMatchWidthButton.setOnClickListener { - expandMatchWidthView.expandWidthSpring() + expandMatchWidthView.expandWidth() } expandCollapseMatchWidthButton.setOnClickListener { - expandMatchWidthView.collapseWidthSpring() + expandMatchWidthView.collapseWidth() } expand0dpWidthButton.setOnClickListener { - expand0dpWidthView.expandWidthSpring() + expand0dpWidthView.expandWidth() } expandCollapse0dpWidthButton.setOnClickListener { - expand0dpWidthView.collapseWidthSpring() + expand0dpWidthView.collapseWidth() } expandHalf0dpWidthButton.setOnClickListener { - expandHalf0dpWidthView.expandWidthSpring() + expandHalf0dpWidthView.expandWidth() } expandCollapseHalf0dpWidthButton.setOnClickListener { - expandHalf0dpWidthView.collapseWidthSpring() + expandHalf0dpWidthView.collapseWidth() } expandFixedWidthButton.setOnClickListener { - expandFixedWidthView.expandWidthSpring() + expandFixedWidthView.expandWidth() } expandCollapseFixedWidthButton.setOnClickListener { - expandFixedWidthView.collapseWidthSpring() + expandFixedWidthView.collapseWidth() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt index 42e9f6c..4985cd8 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingAnimationActivity.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring -import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring -import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring -import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring +import br.alexandregpereira.jerry.expandable.collapseHeightFading +import br.alexandregpereira.jerry.expandable.collapseWidthFading +import br.alexandregpereira.jerry.expandable.expandHeightFading +import br.alexandregpereira.jerry.expandable.expandWidthFading import kotlinx.android.synthetic.main.activity_expand_fading_animation.* class ExpandFadingAnimationActivity : AppCompatActivity() { @@ -24,51 +24,51 @@ class ExpandFadingAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_expand_fading_animation) expandFadingTextButton.setOnClickListener { - expandFadingTextView.expandHeightFadingSpring() + expandFadingTextView.expandHeightFading() } expandCollapseFadingTextButton.setOnClickListener { - expandFadingTextView.collapseHeightFadingSpring() + expandFadingTextView.collapseHeightFading() } expandWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.expandWidthFadingSpring() + expandWidthFadingTextView.expandWidthFading() } expandCollapseWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.collapseWidthFadingSpring() + expandWidthFadingTextView.collapseWidthFading() } expandFixedFadingTextButton.setOnClickListener { - expandFixedFadingTextView.expandHeightFadingSpring() + expandFixedFadingTextView.expandHeightFading() } expandCollapseFixedFadingTextButton.setOnClickListener { - expandFixedFadingTextView.collapseHeightFadingSpring() + expandFixedFadingTextView.collapseHeightFading() } expandWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.expandWidthFadingSpring() + expandWidthFadingTextView.expandWidthFading() } expandCollapseWidthFadingTextButton.setOnClickListener { - expandWidthFadingTextView.collapseWidthFadingSpring() + expandWidthFadingTextView.collapseWidthFading() } expandMatchWidthFadingButton.setOnClickListener { - expandMatchWidthFadingView.expandWidthFadingSpring() + expandMatchWidthFadingView.expandWidthFading() } expandCollapseMatchWidthFadingButton.setOnClickListener { - expandMatchWidthFadingView.collapseWidthFadingSpring() + expandMatchWidthFadingView.collapseWidthFading() } expandFixedWidthFadingButton.setOnClickListener { - expandFixedWidthFadingView.expandWidthFadingSpring() + expandFixedWidthFadingView.expandWidthFading() } expandCollapseFixedWidthFadingButton.setOnClickListener { - expandFixedWidthFadingView.collapseWidthFadingSpring() + expandFixedWidthFadingView.collapseWidthFading() } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingSpringAnimationActivity.kt new file mode 100644 index 0000000..149d487 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandFadingSpringAnimationActivity.kt @@ -0,0 +1,76 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.expandable.collapseHeightFadingSpring +import br.alexandregpereira.jerry.expandable.collapseWidthFadingSpring +import br.alexandregpereira.jerry.expandable.expandHeightFadingSpring +import br.alexandregpereira.jerry.expandable.expandWidthFadingSpring +import kotlinx.android.synthetic.main.activity_expand_fading_animation.* + +class ExpandFadingSpringAnimationActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, ExpandFadingSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_expand_fading_animation) + + expandFadingLabel.setText(R.string.expand_fading_spring) + + expandFadingTextButton.setOnClickListener { + expandFadingTextView.expandHeightFadingSpring() + } + + expandCollapseFadingTextButton.setOnClickListener { + expandFadingTextView.collapseHeightFadingSpring() + } + + expandWidthFadingTextButton.setOnClickListener { + expandWidthFadingTextView.expandWidthFadingSpring() + } + + expandCollapseWidthFadingTextButton.setOnClickListener { + expandWidthFadingTextView.collapseWidthFadingSpring() + } + + expandFixedFadingTextButton.setOnClickListener { + expandFixedFadingTextView.expandHeightFadingSpring() + } + + expandCollapseFixedFadingTextButton.setOnClickListener { + expandFixedFadingTextView.collapseHeightFadingSpring() + } + + expandWidthFadingTextButton.setOnClickListener { + expandWidthFadingTextView.expandWidthFadingSpring() + } + + expandCollapseWidthFadingTextButton.setOnClickListener { + expandWidthFadingTextView.collapseWidthFadingSpring() + } + + expandMatchWidthFadingButton.setOnClickListener { + expandMatchWidthFadingView.expandWidthFadingSpring() + } + + expandCollapseMatchWidthFadingButton.setOnClickListener { + expandMatchWidthFadingView.collapseWidthFadingSpring() + } + + expandFixedWidthFadingButton.setOnClickListener { + expandFixedWidthFadingView.expandWidthFadingSpring() + } + + expandCollapseFixedWidthFadingButton.setOnClickListener { + expandFixedWidthFadingView.collapseWidthFadingSpring() + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandSpringAnimationActivity.kt new file mode 100644 index 0000000..d836eea --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/ExpandSpringAnimationActivity.kt @@ -0,0 +1,92 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.expandable.collapseHeightSpring +import br.alexandregpereira.jerry.expandable.collapseWidthSpring +import br.alexandregpereira.jerry.expandable.expandHeightSpring +import br.alexandregpereira.jerry.expandable.expandWidthSpring +import kotlinx.android.synthetic.main.activity_expand_animation.* + +class ExpandSpringAnimationActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, ExpandSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_expand_animation) + + expandLabel.setText(R.string.expand_spring) + + expandTextButton.setOnClickListener { + expandTextView.expandHeightSpring() + } + + expandCollapseTextButton.setOnClickListener { + expandTextView.collapseHeightSpring() + } + + expandWidthTextButton.setOnClickListener { + expandWidthTextView.expandWidthSpring() + } + + expandCollapseWidthTextButton.setOnClickListener { + expandWidthTextView.collapseWidthSpring() + } + + expandFixedTextButton.setOnClickListener { + expandFixedTextView.expandHeightSpring() + } + + expandCollapseFixedTextButton.setOnClickListener { + expandFixedTextView.collapseHeightSpring() + } + + expandWidthTextButton.setOnClickListener { + expandWidthTextView.expandWidthSpring() + } + + expandCollapseWidthTextButton.setOnClickListener { + expandWidthTextView.collapseWidthSpring() + } + + expandMatchWidthButton.setOnClickListener { + expandMatchWidthView.expandWidthSpring() + } + + expandCollapseMatchWidthButton.setOnClickListener { + expandMatchWidthView.collapseWidthSpring() + } + + expand0dpWidthButton.setOnClickListener { + expand0dpWidthView.expandWidthSpring() + } + + expandCollapse0dpWidthButton.setOnClickListener { + expand0dpWidthView.collapseWidthSpring() + } + + expandHalf0dpWidthButton.setOnClickListener { + expandHalf0dpWidthView.expandWidthSpring() + } + + expandCollapseHalf0dpWidthButton.setOnClickListener { + expandHalf0dpWidthView.collapseWidthSpring() + } + + expandFixedWidthButton.setOnClickListener { + expandFixedWidthView.expandWidthSpring() + } + + expandCollapseFixedWidthButton.setOnClickListener { + expandFixedWidthView.collapseWidthSpring() + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt index 9b8209c..76dcd35 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeAnimationActivity.kt @@ -5,9 +5,9 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.fadeOutSpring -import br.alexandregpereira.jerry.textview.setTextFadeSpring -import br.alexandregpereira.jerry.fadeInSpring +import br.alexandregpereira.jerry.fadeOut +import br.alexandregpereira.jerry.textview.setTextFade +import br.alexandregpereira.jerry.fadeIn import kotlinx.android.synthetic.main.activity_fade_animation.* class FadeAnimationActivity : AppCompatActivity() { @@ -30,39 +30,23 @@ class FadeAnimationActivity : AppCompatActivity() { "Fade text change 1", ).circularIterator() fadeTextButton.setOnClickListener { - fadeTextView.setTextFadeSpring(list.next()) + fadeTextView.setTextFade(list.next()) } goneTextButton.setOnClickListener { - goneTextView.fadeOutSpring() + goneTextView.fadeOut() } goneVisibleTextButton.setOnClickListener { - goneTextView.fadeInSpring() + goneTextView.fadeIn() } visibleTextButton.setOnClickListener { - visibleTextView.fadeInSpring() + visibleTextView.fadeIn() } visibleInvisibleTextButton.setOnClickListener { - visibleTextView.fadeOutSpring() + visibleTextView.fadeOut() } } } - -fun List.circularIterator(): Iterator { - val size = this.size - return object : MutableIterator { - var i = 0 - override fun hasNext(): Boolean { - return i < size - } - - override fun next(): T { - return this@circularIterator[i++ % size] - } - - override fun remove() {} - } -} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeSpringAnimationActivity.kt new file mode 100644 index 0000000..87124ea --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/FadeSpringAnimationActivity.kt @@ -0,0 +1,56 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.fadeInSpring +import br.alexandregpereira.jerry.fadeOutSpring +import br.alexandregpereira.jerry.textview.setTextFadeSpring +import kotlinx.android.synthetic.main.activity_fade_animation.* + +class FadeSpringAnimationActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, FadeSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_fade_animation) + + fadeLabel.setText(R.string.fade_spring) + + val list = listOf( + "Fade text changeasaaaaa 2", + "Fade text change asda sdas 3", + "asdasd asdasdasd 4", + "Fade asdasdasddas change 5", + "Fade text change 1", + ).circularIterator() + fadeTextButton.setOnClickListener { + fadeTextView.setTextFadeSpring(list.next()) + } + + goneTextButton.setOnClickListener { + goneTextView.fadeOutSpring() + } + + goneVisibleTextButton.setOnClickListener { + goneTextView.fadeInSpring() + } + + visibleTextButton.setOnClickListener { + visibleTextView.fadeInSpring() + } + + visibleInvisibleTextButton.setOnClickListener { + visibleTextView.fadeOutSpring() + } + } +} + + diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/Iterator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/Iterator.kt new file mode 100644 index 0000000..3a390b1 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/Iterator.kt @@ -0,0 +1,17 @@ +package br.alexandregpereira.jerry.app.animation + +fun List.circularIterator(): Iterator { + val size = this.size + return object : MutableIterator { + var i = 0 + override fun hasNext(): Boolean { + return i < size + } + + override fun next(): T { + return this@circularIterator[i++ % size] + } + + override fun remove() {} + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt index ab085e5..bd136a5 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableAnimationActivity.kt @@ -7,7 +7,7 @@ import android.os.Handler import android.os.Looper import androidx.appcompat.app.AppCompatActivity import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.textview.setTextExpandableSpring +import br.alexandregpereira.jerry.textview.setTextExpandable import kotlinx.android.synthetic.main.activity_text_expandable_animation.* class TextExpandableAnimationActivity : AppCompatActivity() { @@ -23,39 +23,39 @@ class TextExpandableAnimationActivity : AppCompatActivity() { setContentView(R.layout.activity_text_expandable_animation) expandCancelTextButton.setOnClickListener { - expandCancelTextView.setTextExpandableSpring( + expandCancelTextView.setTextExpandable( getString(R.string.expand) ) Handler(Looper.getMainLooper()).postDelayed({ - expandCancelTextView.setTextExpandableSpring(null) + expandCancelTextView.setTextExpandable(null) }, 100) } collapseCancelTextButton.setOnClickListener { - collapseCancelTextView.setTextExpandableSpring(null) + collapseCancelTextView.setTextExpandable(null) Handler(Looper.getMainLooper()).postDelayed({ - collapseCancelTextView.setTextExpandableSpring( + collapseCancelTextView.setTextExpandable( getString(R.string.collapse) ) }, 100) } expandFadingTextButton.setOnClickListener { - expandFadingTextView.setTextExpandableSpring( + expandFadingTextView.setTextExpandable( getString(R.string.expand_collapse_animation) ) } expandCollapseFadingTextButton.setOnClickListener { - expandFadingTextView.setTextExpandableSpring(null) + expandFadingTextView.setTextExpandable(null) } collapseFadingTextButton.setOnClickListener { - collapseFadingTextView.setTextExpandableSpring(null) + collapseFadingTextView.setTextExpandable(null) } collapseExpandFadingTextButton.setOnClickListener { - collapseFadingTextView.setTextExpandableSpring( + collapseFadingTextView.setTextExpandable( getString(R.string.collapse_expand_animation) ) } @@ -68,7 +68,7 @@ class TextExpandableAnimationActivity : AppCompatActivity() { "Fade text change 1", ).circularIterator() textFadeTextButton.setOnClickListener { - textFadeTextView.setTextExpandableSpring(list.next()) + textFadeTextView.setTextExpandable(list.next()) } } } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableSpringAnimationActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableSpringAnimationActivity.kt new file mode 100644 index 0000000..6e79e92 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/animation/TextExpandableSpringAnimationActivity.kt @@ -0,0 +1,76 @@ +package br.alexandregpereira.jerry.app.animation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import androidx.appcompat.app.AppCompatActivity +import br.alexandregpereira.jerry.app.R +import br.alexandregpereira.jerry.textview.setTextExpandableSpring +import kotlinx.android.synthetic.main.activity_text_expandable_animation.* + +class TextExpandableSpringAnimationActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, TextExpandableSpringAnimationActivity::class.java) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_text_expandable_animation) + + textExpandableLabel.setText(R.string.text_expandable_spring) + + expandCancelTextButton.setOnClickListener { + expandCancelTextView.setTextExpandableSpring( + getString(R.string.expand) + ) + Handler(Looper.getMainLooper()).postDelayed({ + expandCancelTextView.setTextExpandableSpring(null) + }, 100) + } + + collapseCancelTextButton.setOnClickListener { + collapseCancelTextView.setTextExpandableSpring(null) + Handler(Looper.getMainLooper()).postDelayed({ + collapseCancelTextView.setTextExpandableSpring( + getString(R.string.collapse) + ) + }, 100) + } + + expandFadingTextButton.setOnClickListener { + expandFadingTextView.setTextExpandableSpring( + getString(R.string.expand_collapse_animation) + ) + } + + expandCollapseFadingTextButton.setOnClickListener { + expandFadingTextView.setTextExpandableSpring(null) + } + + collapseFadingTextButton.setOnClickListener { + collapseFadingTextView.setTextExpandableSpring(null) + } + + collapseExpandFadingTextButton.setOnClickListener { + collapseFadingTextView.setTextExpandableSpring( + getString(R.string.collapse_expand_animation) + ) + } + + val list = listOf( + "Fade text changeasaaaaa 2", + "Fade text change asda sdas 3", + "asdasd asdasdasd 4", + "Fade asdasdasddas change 5", + "Fade text change 1", + ).circularIterator() + textFadeTextButton.setOnClickListener { + textFadeTextView.setTextExpandableSpring(list.next()) + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt similarity index 51% rename from app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt rename to app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt index b1cd9ad..4602906 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/RecyclerViewActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt @@ -1,100 +1,24 @@ -package br.alexandregpereira.jerry.app +package br.alexandregpereira.jerry.app.recyclerview -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Bundle import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import br.alexandregpereira.jerry.app.R import br.alexandregpereira.jerry.app.widgets.setMaterialShapeDrawable import br.alexandregpereira.jerry.dpToPx -import kotlinx.android.synthetic.main.activity_recycler_view.* -import java.util.* - -class RecyclerViewActivity : AppCompatActivity() { - - companion object { - fun getStartIntent(context: Context): Intent { - return Intent(context, RecyclerViewActivity::class.java) - } - } - - private val list = (0..4).map { - Item(id = it, value = it.toString()) - } - - private val list2 = listOf(list[0]) + list.subList(2, list.size) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_recycler_view) - - val adapter = ExampleAdapter() - recyclerView.layoutManager = LinearLayoutManager(this) - recyclerView.adapter = adapter - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - recyclerView.itemAnimator = SpringItemAnimator() - } -// recyclerView.itemAnimator?.addDuration = 2000 -// recyclerView.itemAnimator?.removeDuration = 2000 -// recyclerView.itemAnimator?.changeDuration = 2000 -// recyclerView.itemAnimator?.moveDuration = 2000 - - button.setOnClickListener { - adapter.setData(list2) -// adapter.notifyItemRangeInserted(0, list2.size) - } - - button2.setOnClickListener { - adapter.setData(list2.map { - it.copy(value = "${UUID.randomUUID().toString().substring(0..10)} ${it.value}") - }) - } - - button3.setOnClickListener { - adapter.setData(listOf()) - } - - button4.setOnClickListener { - adapter.setData(list) -// adapter.notifyItemInserted(1) - } - - button5.setOnClickListener { - adapter.setData(adapter.currentList.reversed()) - } - - button6.setOnClickListener { - adapter.setData(list.subList(0, 3) + list[4]) - } - } -} +import androidx.recyclerview.widget.DiffUtil class ExampleAdapter : ListAdapter(DiffUtil) { -// var currentList: List = listOf() -// private set - fun setData(list: List) { -// currentList = list submitList(list) } -// override fun getItemCount(): Int = currentList.size -// -// private fun getItem(position: Int): Item { -// return currentList[position] -// } - inner class ExampleViewHolder( private val view: View ) : RecyclerView.ViewHolder(view) { @@ -123,7 +47,9 @@ class ExampleAdapter : ListAdapter(DiffU setPadding(padding, padding, padding, padding) gravity = Gravity.CENTER_VERTICAL setTextColor(ContextCompat.getColor(context, R.color.textSecondaryColor)) - setMaterialShapeDrawable(ContextCompat.getColor(context, R.color.backgroundHelperColor)) + setMaterialShapeDrawable(ContextCompat.getColor(context, + R.color.backgroundHelperColor + )) } ) } @@ -149,10 +75,9 @@ object DiffUtil : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { return oldItem == newItem } - } data class Item( val id: Int, val value: String -) +) \ No newline at end of file diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewActivity.kt new file mode 100644 index 0000000..f438e35 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewActivity.kt @@ -0,0 +1,64 @@ +package br.alexandregpereira.jerry.app.recyclerview + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import br.alexandregpereira.jerry.app.R +import kotlinx.android.synthetic.main.activity_recycler_view.* +import java.util.* + +class RecyclerViewActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, RecyclerViewActivity::class.java) + } + } + + private val list = (0..4).map { + Item(id = it, value = it.toString()) + } + + private val list2 = listOf(list[0]) + list.subList(2, list.size) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_recycler_view) + + val adapter = ExampleAdapter() + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter +// recyclerView.itemAnimator?.addDuration = 2000 +// recyclerView.itemAnimator?.removeDuration = 2000 +// recyclerView.itemAnimator?.changeDuration = 2000 +// recyclerView.itemAnimator?.moveDuration = 2000 + + button.setOnClickListener { + adapter.setData(list2) + } + + button2.setOnClickListener { + adapter.setData(list2.map { + it.copy(value = "${UUID.randomUUID().toString().substring(0..10)} ${it.value}") + }) + } + + button3.setOnClickListener { + adapter.setData(listOf()) + } + + button4.setOnClickListener { + adapter.setData(list) + } + + button5.setOnClickListener { + adapter.setData(adapter.currentList.reversed()) + } + + button6.setOnClickListener { + adapter.setData(list.subList(0, 3) + list[4]) + } + } +} diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt new file mode 100644 index 0000000..a9d2092 --- /dev/null +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt @@ -0,0 +1,66 @@ +package br.alexandregpereira.jerry.app.recyclerview + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import br.alexandregpereira.jerry.app.R +import kotlinx.android.synthetic.main.activity_recycler_view.* +import java.util.* + +class RecyclerViewSpringActivity : AppCompatActivity() { + + companion object { + fun getStartIntent(context: Context): Intent { + return Intent(context, RecyclerViewSpringActivity::class.java) + } + } + + private val list = (0..4).map { + Item(id = it, value = it.toString()) + } + + private val list2 = listOf(list[0]) + list.subList(2, list.size) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_recycler_view) + + val adapter = ExampleAdapter() + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + recyclerView.itemAnimator = SpringItemAnimator() + } + + button.setOnClickListener { + adapter.setData(list2) + } + + button2.setOnClickListener { + adapter.setData(list2.map { + it.copy(value = "${UUID.randomUUID().toString().substring(0..10)} ${it.value}") + }) + } + + button3.setOnClickListener { + adapter.setData(listOf()) + } + + button4.setOnClickListener { + adapter.setData(list) + } + + button5.setOnClickListener { + adapter.setData(adapter.currentList.reversed()) + } + + button6.setOnClickListener { + adapter.setData(list.subList(0, 3) + list[4]) + } + } +} + + diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt similarity index 99% rename from app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt rename to app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt index 6a69cf1..56a01a9 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/SpringItemAnimator.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt @@ -1,4 +1,4 @@ -package br.alexandregpereira.jerry.app +package br.alexandregpereira.jerry.app.recyclerview import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c241ba0..d25512f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,27 +1,37 @@ - - + android:background="@android:color/white"> + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recycler_view.xml b/app/src/main/res/layout/activity_recycler_view.xml index 1324f26..c8dcba6 100644 --- a/app/src/main/res/layout/activity_recycler_view.xml +++ b/app/src/main/res/layout/activity_recycler_view.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".RecyclerViewActivity"> + tools:context=".recyclerview.RecyclerViewActivity"> Jerry Fade + Fade Spring Fade text change 1 Fade text change Fade out/Fade in @@ -9,6 +10,7 @@ Fade in Fade in/Fade out Collapse Fading + Collapse Fading Spring Collapse/expand animation Collapse/Expand animation Collapse animation @@ -28,20 +30,15 @@ Collapse fixed Width animation Expand fixed Width animation Expand Fading + Expand Fading Spring Text Expandable + Text Expandable Spring Expand/collapse animation Collapse + Collapse Spring Expand - Measures - Change text size - Change top margin - Scale view - Colors - Change background color - Change text color + Expand Spring This text needs to show entirely - Animation stiffness - 600 Add All Remove All diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt index def2666..9e05ce1 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeAnimation.kt @@ -15,7 +15,7 @@ fun View.fadeOut( duration: Long = ANIMATION_SHORT_TIME, onAnimationEnd: (() -> Unit)? = null ) { - hideFadeOut(duration, ::gone, onAnimationEnd = onAnimationEnd) + hideFadeOut(duration, hide = { gone() }, onAnimationEnd = onAnimationEnd) } /** diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index 9e6c260..4bf58e4 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -4,6 +4,10 @@ import android.view.View import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation +/** + * Animates the View visibility depending of the [visible] flag. If [visible] is true, the + * [fadeInSpring] is called, else the [fadeOutSpring] is called. + */ fun View.animateAlphaVisibility( visible: Boolean, stiffness: Float = ANIMATION_STIFFNESS, @@ -18,7 +22,7 @@ fun View.animateAlphaVisibility( /** * Change the visibility to GONE of the view using fade out animation. This method can be - * reverted in the middle of the animation if the [fadeIn] method is called. + * reverted in the middle of the animation if the [fadeInSpring] method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to * the object attached when the spring is not at the final position. Default stiffness is @@ -31,27 +35,12 @@ fun View.fadeOutSpring( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { - if (isVisible().not() || isFadeOutRunning()) { - if (isFadeOutRunning().not()) { - onAnimationEnd?.invoke(false) - } - return - } - startFadeOutRunning() - - startFadeSpringAnimation( - targetValue = 0f, - stiffness = stiffness, - onAnimationEnd = { canceled -> - gone() - onAnimationEnd?.invoke(canceled) - } - ) + hideFadeOutSpring(stiffness, hide = { gone() }, onAnimationEnd = onAnimationEnd) } /** * Change the visibility to VISIBLE of the view using fade in animation. This method can be - * reverted in the middle of the animation if the [fadeOut] + * reverted in the middle of the animation if the [fadeOutSpring] * method is called. * * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to @@ -82,6 +71,40 @@ fun View.fadeInSpring( ) } +/** + * Start the fade out animation without changing the visibility status. The changes in the + * visibility status is delegate to the function [hide]. + * + * @param stiffness Stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * [ANIMATION_STIFFNESS]. + * @param onAnimationEnd The function to call when the animation is finished. + * + * @see [SpringAnimation] + */ +internal fun View.hideFadeOutSpring( + stiffness: Float, + hide: (() -> Unit)? = null, + onAnimationEnd: ((canceled: Boolean) -> Unit)? +) { + if (isVisible().not() || isFadeOutRunning()) { + if (isFadeOutRunning().not()) { + onAnimationEnd?.invoke(false) + } + return + } + startFadeOutRunning() + + startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = { canceled -> + hide?.invoke() + onAnimationEnd?.invoke(canceled) + } + ) +} + fun View.fadeSpring( stiffness: Float = ANIMATION_STIFFNESS ) = spring( diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt index a05a02a..74070b1 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableFadingSpringAnimation.kt @@ -5,6 +5,7 @@ import androidx.dynamicanimation.animation.SpringAnimation import br.alexandregpereira.jerry.ANIMATION_STIFFNESS import br.alexandregpereira.jerry.fadeInSpring import br.alexandregpereira.jerry.fadeOutSpring +import br.alexandregpereira.jerry.hideFadeOutSpring import br.alexandregpereira.jerry.isVisible /** @@ -91,7 +92,7 @@ fun View.collapseHeightFadingSpring( ) = collapseFadingSpring(stiffness, isHeight = true, onAnimationEnd = onAnimationEnd) /** - * Uses the [fadeOutSpring] and [collapseWidthSpring] animations in sequence. This animation + * Uses the [hideFadeOutSpring] and [collapseWidthSpring] animations in sequence. This animation * handles double click. This method can be reverted in the middle of the animation if the * [expandWidthFadingSpring] method is called. * @@ -117,7 +118,7 @@ private fun View.collapseFadingSpring( return } - fadeOutSpring(stiffness = stiffness * 2f) { + hideFadeOutSpring(stiffness = stiffness * 2f) { collapseSpring( stiffness = stiffness * 2f, isHeight = isHeight, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt index b672a43..ee315f3 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/textview/TextViewAnimation.kt @@ -24,7 +24,7 @@ import br.alexandregpereira.jerry.onAnimationEnd * @param onFirstFadeEnd The function to call when the first fade of the [setTextFade] is finished * @param onAnimationEnd The function to call when the animation is finished */ -fun TextView.setTextExpandableAnimation( +fun TextView.setTextExpandable( text: String?, duration: Long = ANIMATION_SHORT_TIME, onAnimationEnd: (() -> Unit)? = null, From 7ad17158beb4972f90091824ba0b847a5adbc6ca Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Wed, 14 Oct 2020 23:39:59 -0300 Subject: [PATCH 22/33] Adjust original value code of the expandable --- .../jerry/app/recyclerview/ExampleAdapter.kt | 59 ++++++++------- .../jerry/ElevationSpringAnimation.kt | 72 ++++++++++++++----- .../jerry/FadeSpringAnimation.kt | 32 +++++++-- .../jerry/OriginalValueKey.kt | 9 +++ .../jerry/TransalationSpringAnimation.kt | 8 +-- .../alexandregpereira/jerry/ViewAnimation.kt | 14 ++-- .../jerry/expandable/ExpandableAnimation.kt | 8 +-- .../expandable/ExpandableAnimationCommon.kt | 39 ++++++---- .../expandable/ExpandableSpingAnimation.kt | 21 +++--- jerry/src/main/res/values/strings.xml | 1 + 10 files changed, 171 insertions(+), 92 deletions(-) create mode 100644 jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt index 4602906..6fbee2a 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/ExampleAdapter.kt @@ -1,70 +1,67 @@ package br.alexandregpereira.jerry.app.recyclerview import android.view.Gravity -import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.widget.AppCompatTextView +import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.app.R -import br.alexandregpereira.jerry.app.widgets.setMaterialShapeDrawable import br.alexandregpereira.jerry.dpToPx -import androidx.recyclerview.widget.DiffUtil -class ExampleAdapter : ListAdapter(DiffUtil) { +class ExampleAdapter( + private val itemWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT, + private val itemHeight: Int? = null +) : ListAdapter(DiffUtil) { fun setData(list: List) { submitList(list) } inner class ExampleViewHolder( - private val view: View - ) : RecyclerView.ViewHolder(view) { + private val viewGroup: ViewGroup + ) : RecyclerView.ViewHolder(viewGroup) { - fun bind(item: Item) { - if (view is TextView) { - view.text = item.value - } + fun bind(item: Item) = viewGroup.runCatching { getChildAt(0) }.getOrNull()?.run { + this as? TextView + }?.let { textView -> + textView.text = item.value } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder { - val height = when (viewType) { - ViewType.TYPE_2.ordinal -> ViewType.TYPE_2.height - else -> ViewType.TYPE_1.height - } return ExampleViewHolder( - AppCompatTextView(parent.context).apply { + CardView(parent.context).apply { val padding = resources.getDimensionPixelOffset(R.dimen.text_padding) layoutParams = ViewGroup.MarginLayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - height.dpToPx(resources) + itemWidth, + itemHeight ?: 60.dpToPx(parent.resources) ).apply { setMargins(padding / 2, padding / 2, padding / 2, padding / 2) } - setPadding(padding, padding, padding, padding) - gravity = Gravity.CENTER_VERTICAL - setTextColor(ContextCompat.getColor(context, R.color.textSecondaryColor)) - setMaterialShapeDrawable(ContextCompat.getColor(context, - R.color.backgroundHelperColor - )) +// setCardBackgroundColor(ContextCompat.getColor(context, R.color.backgroundHelperColor)) + radius = resources.getDimension(R.dimen.corner_size) + addView( + AppCompatTextView(parent.context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + setPadding(padding, padding, padding, padding) + gravity = Gravity.CENTER_VERTICAL + setTextColor(ContextCompat.getColor(context, R.color.textSecondaryColor)) + } + ) } ) } - override fun getItemViewType(position: Int): Int { - return (if (position == 1) ViewType.TYPE_2 else ViewType.TYPE_1).ordinal - } - override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) { holder.bind(getItem(position)) } - - enum class ViewType(val height: Int) { - TYPE_1(60), TYPE_2(100) - } } object DiffUtil : DiffUtil.ItemCallback() { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt index baa9d2e..d32e580 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -5,42 +5,80 @@ import androidx.annotation.RequiresApi import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce -@RequiresApi(21) -internal fun elevationViewProperty() = object : FloatPropertyCompat( - "viewProperty" -) { - override fun setValue(view: View?, value: Float) { - view?.elevation = value - } - - override fun getValue(view: View?): Float { - return view?.elevation ?: 0f - } - -} - @RequiresApi(21) fun View.elevationSpring( stiffness: Float = SpringForce.STIFFNESS_LOW ) = spring( - key = SpringAnimationPropertyKey.ELEVATION, + key = SpringAnimationPropertyKey.ELEVATION.id, property = elevationViewProperty(), stiffness = stiffness ) +@RequiresApi(21) +fun View.startElevationInSpringAnimation( + stiffness: Float = SpringForce.STIFFNESS_LOW, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = startElevationSpringAnimation( + targetValue = getOrStoreElevationOriginalValue(), + stiffness = stiffness, + onAnimationEnd = onAnimationEnd +) + +@RequiresApi(21) +fun View.startElevationOutSpringAnimation( + stiffness: Float = SpringForce.STIFFNESS_LOW, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = startElevationSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd +) + @RequiresApi(21) fun View.startElevationSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { + getOrStoreElevationOriginalValue() startSpringAnimation( - key = SpringAnimationPropertyKey.ELEVATION, + key = SpringAnimationPropertyKey.ELEVATION.id, property = elevationViewProperty(), targetValue = targetValue, stiffness = stiffness, endListenerPair = onAnimationEnd?.let { - R.string.elevation_end_listener_key to onAnimationEnd + R.string.elevation_end_listener_key to it } ) } + +@RequiresApi(21) +internal fun View.getOrStoreElevationOriginalValue(): Float { + val key = OriginalValueKey.ELEVATION.id + return runCatching { + getTag(key) as Float + }.getOrElse { + elevation.apply { + setTag(key, this) + } + } +} + +fun View.clearElevationOriginalValue() { + runCatching { + setTag(OriginalValueKey.ELEVATION.id, null) + } +} + +@RequiresApi(21) +fun elevationViewProperty() = object : FloatPropertyCompat( + "viewProperty" +) { + override fun setValue(view: View?, value: Float) { + view?.elevation = value + } + + override fun getValue(view: View?): Float { + return view?.elevation ?: 0f + } +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt index 4bf58e4..7d338df 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/FadeSpringAnimation.kt @@ -64,13 +64,21 @@ fun View.fadeInSpring( if (alpha == 1f) alpha = 0f visible() - startFadeSpringAnimation( - targetValue = 1f, + startFadeInSpringAnimation( stiffness = stiffness, onAnimationEnd = onAnimationEnd ) } +fun View.startFadeInSpringAnimation( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = startFadeSpringAnimation( + targetValue = 1f, + stiffness = stiffness, + onAnimationEnd = onAnimationEnd +) + /** * Start the fade out animation without changing the visibility status. The changes in the * visibility status is delegate to the function [hide]. @@ -82,7 +90,7 @@ fun View.fadeInSpring( * * @see [SpringAnimation] */ -internal fun View.hideFadeOutSpring( +fun View.hideFadeOutSpring( stiffness: Float, hide: (() -> Unit)? = null, onAnimationEnd: ((canceled: Boolean) -> Unit)? @@ -95,8 +103,7 @@ internal fun View.hideFadeOutSpring( } startFadeOutRunning() - startFadeSpringAnimation( - targetValue = 0f, + startFadeOutSpringAnimation( stiffness = stiffness, onAnimationEnd = { canceled -> hide?.invoke() @@ -105,10 +112,21 @@ internal fun View.hideFadeOutSpring( ) } +fun View.startFadeOutSpringAnimation( + stiffness: Float = ANIMATION_STIFFNESS, + onAnimationEnd: ((canceled: Boolean) -> Unit)? = null +) = startFadeSpringAnimation( + targetValue = 0f, + stiffness = stiffness, + onAnimationEnd = { canceled -> + onAnimationEnd?.invoke(canceled) + } +) + fun View.fadeSpring( stiffness: Float = ANIMATION_STIFFNESS ) = spring( - key = SpringAnimationPropertyKey.ALPHA, + key = SpringAnimationPropertyKey.ALPHA.id, property = DynamicAnimation.ALPHA, stiffness = stiffness ) @@ -118,7 +136,7 @@ fun View.startFadeSpringAnimation( stiffness: Float = ANIMATION_STIFFNESS, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null, ) = startSpringAnimation( - key = SpringAnimationPropertyKey.ALPHA, + key = SpringAnimationPropertyKey.ALPHA.id, property = DynamicAnimation.ALPHA, targetValue = targetValue, stiffness = stiffness, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt b/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt new file mode 100644 index 0000000..38dceb9 --- /dev/null +++ b/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt @@ -0,0 +1,9 @@ +package br.alexandregpereira.jerry + +import androidx.annotation.StringRes + +enum class OriginalValueKey(@StringRes val id: Int) { + HEIGHT(R.string.expanding_collapsing_height_original_value_key), + WIDTH(R.string.expanding_collapsing_width_original_value_key), + ELEVATION(R.string.elevation_original_value_key) +} diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt index 3bc8d40..3f710cc 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/TransalationSpringAnimation.kt @@ -7,7 +7,7 @@ import androidx.dynamicanimation.animation.SpringForce fun View.translationXSpring( stiffness: Float = SpringForce.STIFFNESS_LOW ) = spring( - key = SpringAnimationPropertyKey.TRANSLATION_X, + key = SpringAnimationPropertyKey.TRANSLATION_X.id, property = DynamicAnimation.TRANSLATION_X, stiffness = stiffness ) @@ -18,7 +18,7 @@ fun View.startTranslationXSpringAnimation( onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { startSpringAnimation( - key = SpringAnimationPropertyKey.TRANSLATION_X, + key = SpringAnimationPropertyKey.TRANSLATION_X.id, property = DynamicAnimation.TRANSLATION_X, targetValue = targetValue, stiffness = stiffness, @@ -31,7 +31,7 @@ fun View.startTranslationXSpringAnimation( fun View.translationYSpring( stiffness: Float = SpringForce.STIFFNESS_LOW ) = spring( - key = SpringAnimationPropertyKey.TRANSLATION_Y, + key = SpringAnimationPropertyKey.TRANSLATION_Y.id, property = DynamicAnimation.TRANSLATION_Y, stiffness = stiffness ) @@ -42,7 +42,7 @@ fun View.startTranslationYSpringAnimation( onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { startSpringAnimation( - key = SpringAnimationPropertyKey.TRANSLATION_Y, + key = SpringAnimationPropertyKey.TRANSLATION_Y.id, property = DynamicAnimation.TRANSLATION_Y, targetValue = targetValue, stiffness = stiffness, diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt index 0f009ea..21fabcb 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ViewAnimation.kt @@ -28,18 +28,18 @@ enum class AnimationMode { fun View.cancelSpringAnimation() { SpringAnimationPropertyKey.values().forEach { - getSpringAnimation(it)?.cancel() + getSpringAnimation(it.id)?.cancel() } } fun View.skipToEndSpringAnimation() { SpringAnimationPropertyKey.values().forEach { property -> - getSpringAnimation(property)?.takeIf { it.canSkipToEnd() }?.skipToEnd() + getSpringAnimation(property.id)?.takeIf { it.canSkipToEnd() }?.skipToEnd() } } fun View.spring( - key: SpringAnimationPropertyKey, + key: Int, property: FloatPropertyCompat, dampingRatio: Float = SpringForce.DAMPING_RATIO_NO_BOUNCY, stiffness: Float = SpringForce.STIFFNESS_LOW @@ -52,13 +52,13 @@ fun View.spring( this.stiffness = stiffness } } - setTag(key.id, springAnimation) + setTag(key, springAnimation) } return springAnimation } fun View.startSpringAnimation( - key: SpringAnimationPropertyKey, + key: Int, property: FloatPropertyCompat, targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, @@ -102,8 +102,8 @@ internal fun SpringAnimation.addSpringEndListener( } } -fun View.getSpringAnimation(key: SpringAnimationPropertyKey): SpringAnimation? { - return getTag(key.id) as? SpringAnimation +fun View.getSpringAnimation(key: Int): SpringAnimation? { + return getTag(key) as? SpringAnimation } internal fun View.getSpringEndListener(key: Int): DynamicAnimation.OnAnimationEndListener? { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt index e895214..b7b8351 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimation.kt @@ -104,6 +104,7 @@ internal fun View.collapse( val animation = object : Animation() { override fun applyTransformation(interpolatedTime: Float, t: Transformation) { if (interpolatedTime == 1f) { + gone() setLayoutParamSize(originalValue, isHeight) } else { val value = initialValue - (initialValue * interpolatedTime).toInt() @@ -121,8 +122,7 @@ internal fun View.collapse( } animation.setAnimationListener( onEnd = { - gone() - finishExpandingCollapsingAnimation(onAnimationEnd) + finishExpandingCollapsingAnimation(isHeight, onAnimationEnd) } ) @@ -147,7 +147,7 @@ internal fun View.expand( val targetValue = getTargetValue(originalValue, isHeight) if (targetValue == null) { - finishExpandingCollapsingAnimation(onAnimationEnd) + finishExpandingCollapsingAnimation(isHeight, onAnimationEnd) return } startExpandingRunning() @@ -179,7 +179,7 @@ internal fun View.expand( } animation.setAnimationListener( onEnd = { - finishExpandingCollapsingAnimation(onAnimationEnd) + finishExpandingCollapsingAnimation(isHeight, onAnimationEnd) } ) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt index a772ec9..765297e 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableAnimationCommon.kt @@ -4,17 +4,18 @@ import android.view.View import android.view.View.MeasureSpec import android.view.ViewGroup import br.alexandregpereira.jerry.AnimationMode +import br.alexandregpereira.jerry.OriginalValueKey import br.alexandregpereira.jerry.R import br.alexandregpereira.jerry.SpringAnimationPropertyKey import br.alexandregpereira.jerry.isAnimationRunning import br.alexandregpereira.jerry.setAnimationRunning import br.alexandregpereira.jerry.visible -internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): SpringAnimationPropertyKey { +internal fun getExpandingCollapsingSpringKey(isHeight: Boolean): Int { return if (isHeight) { - SpringAnimationPropertyKey.HEIGHT + SpringAnimationPropertyKey.HEIGHT.id } else { - SpringAnimationPropertyKey.WIDTH + SpringAnimationPropertyKey.WIDTH.id } } @@ -26,17 +27,21 @@ internal fun getExpandingCollapsingEndListenerKey(isHeight: Boolean): Int { } } -internal fun View.finishExpandingCollapsingAnimation(onAnimationEnd: (() -> Unit)?) { - clearOriginalValue() +internal fun View.finishExpandingCollapsingAnimation( + isHeight: Boolean, + onAnimationEnd: (() -> Unit)? +) { + clearWidthOrHeightOriginalValue(isHeight) clearExpandingCollapsingRunning() onAnimationEnd?.invoke() } internal fun View.finishExpandingCollapsingAnimation( + isHeight: Boolean, canceled: Boolean, onAnimationEnd: ((canceled: Boolean) -> Unit)? ) { - clearOriginalValue() + clearWidthOrHeightOriginalValue(isHeight) clearExpandingCollapsingRunning() onAnimationEnd?.invoke(canceled) } @@ -60,16 +65,20 @@ internal fun View.isExpandingCollapsingRunning(animationMode: AnimationMode): Bo internal fun View.setExpandingCollapsingRunning(animationMode: AnimationMode) = setAnimationRunning(R.string.is_expanding_collapsing_key, animationMode) -internal fun View.isExpandingRunning() = isExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) +internal fun View.isExpandingRunning() = + isExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) -internal fun View.isCollapsingRunning() = isExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) +internal fun View.isCollapsingRunning() = + isExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) internal fun View.clearExpandingCollapsingRunning() = setExpandingCollapsingRunning(AnimationMode.NONE_ANIMATION_MODE) -internal fun View.startExpandingRunning() = setExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) +internal fun View.startExpandingRunning() = + setExpandingCollapsingRunning(AnimationMode.ENTER_ANIMATION_MODE) -internal fun View.startCollapsingRunning() = setExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) +internal fun View.startCollapsingRunning() = + setExpandingCollapsingRunning(AnimationMode.POP_ANIMATION_MODE) internal fun View.getCollapsingInitialValue(isHeight: Boolean): Int { val value = getLayoutParamSize(isHeight) @@ -91,14 +100,16 @@ internal fun View.getOrStoreWidthOrHeightOriginalValue(isHeight: Boolean): Int { internal fun getWidthOrHeightOriginalValueKey(isHeight: Boolean): Int { return if (isHeight) { - R.string.expanding_collapsing_height_original_value_key + OriginalValueKey.HEIGHT.id } else { - R.string.expanding_collapsing_width_original_value_key + OriginalValueKey.WIDTH.id } } -internal fun View.clearOriginalValue() { - runCatching { setTag(id, null) } +internal fun View.clearWidthOrHeightOriginalValue(isHeight: Boolean) { + runCatching { + setTag(getWidthOrHeightOriginalValueKey(isHeight), null) + } } internal fun View.getTargetValue(originalValue: Int, isHeight: Boolean): Int? { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt index 5e00a4a..5738f2b 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/expandable/ExpandableSpingAnimation.kt @@ -152,9 +152,10 @@ internal fun View.collapseSpring( stiffness = stiffness, isHeight = isHeight, onProgressChange = onProgressChange, - onAnimationEnd = { + onAnimationEnd = { canceled -> gone() - onAnimationEnd?.invoke(it) + setLayoutParamSize(getOrStoreWidthOrHeightOriginalValue(isHeight), isHeight) + finishExpandingCollapsingAnimation(isHeight, canceled, onAnimationEnd) } ) } @@ -176,7 +177,11 @@ internal fun View.expandSpring( val targetValue = getTargetValue(originalValue, isHeight) if (targetValue == null) { - finishExpandingCollapsingAnimation(canceled = false, onAnimationEnd = onAnimationEnd) + finishExpandingCollapsingAnimation( + isHeight, + canceled = false, + onAnimationEnd + ) return } startExpandingRunning() @@ -191,7 +196,9 @@ internal fun View.expandSpring( stiffness = stiffness, isHeight = isHeight, onProgressChange = onProgressChange, - onAnimationEnd = onAnimationEnd + onAnimationEnd = { canceled -> + finishExpandingCollapsingAnimation(isHeight, canceled, onAnimationEnd) + } ) } @@ -200,13 +207,11 @@ private fun View.startExpandCollapseSpringAnimation( stiffness: Float, isHeight: Boolean, onProgressChange: ((progress: Float) -> Unit)?, - onAnimationEnd: ((canceled: Boolean) -> Unit)?, + onAnimationEnd: ((canceled: Boolean) -> Unit), ) = startSpringAnimation( key = getExpandingCollapsingSpringKey(isHeight), property = widthHeightViewProperty(isHeight, onProgressChange), targetValue = targetValue, stiffness = stiffness, - endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to { canceled -> - finishExpandingCollapsingAnimation(canceled, onAnimationEnd) - } + endListenerPair = getExpandingCollapsingEndListenerKey(isHeight) to onAnimationEnd ) diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index a3e11fd..32ece5c 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ elevation_spring_key elevation_end_listener_key + elevation_original_value_key translation_x_spring_key translation_x_end_listener_key From 5dc8bc175bdecffb21fef6cf3fb00618ad50d1f5 Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Thu, 15 Oct 2020 00:34:46 -0300 Subject: [PATCH 23/33] Organize elevation item animator --- .../RecyclerViewSpringActivity.kt | 3 +- .../jerry/ElevationSpringAnimation.kt | 39 ----- .../jerry/OriginalValueKey.kt | 3 +- .../animator/ElevationSpringItemAnimator.kt | 156 ++++++++++-------- jerry/src/main/res/values/strings.xml | 1 - 5 files changed, 93 insertions(+), 109 deletions(-) rename app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt => jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt (53%) diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt index a9d2092..7f372b9 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt +++ b/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/RecyclerViewSpringActivity.kt @@ -6,6 +6,7 @@ import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager +import br.alexandregpereira.jerry.animator.ElevationSpringItemAnimator import br.alexandregpereira.jerry.app.R import kotlinx.android.synthetic.main.activity_recycler_view.* import java.util.* @@ -32,7 +33,7 @@ class RecyclerViewSpringActivity : AppCompatActivity() { recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = adapter if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - recyclerView.itemAnimator = SpringItemAnimator() + recyclerView.itemAnimator = ElevationSpringItemAnimator() } button.setOnClickListener { diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt index d32e580..ec660f2 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/ElevationSpringAnimation.kt @@ -14,33 +14,12 @@ fun View.elevationSpring( stiffness = stiffness ) -@RequiresApi(21) -fun View.startElevationInSpringAnimation( - stiffness: Float = SpringForce.STIFFNESS_LOW, - onAnimationEnd: ((canceled: Boolean) -> Unit)? = null -) = startElevationSpringAnimation( - targetValue = getOrStoreElevationOriginalValue(), - stiffness = stiffness, - onAnimationEnd = onAnimationEnd -) - -@RequiresApi(21) -fun View.startElevationOutSpringAnimation( - stiffness: Float = SpringForce.STIFFNESS_LOW, - onAnimationEnd: ((canceled: Boolean) -> Unit)? = null -) = startElevationSpringAnimation( - targetValue = 0f, - stiffness = stiffness, - onAnimationEnd = onAnimationEnd -) - @RequiresApi(21) fun View.startElevationSpringAnimation( targetValue: Float, stiffness: Float = SpringForce.STIFFNESS_LOW, onAnimationEnd: ((canceled: Boolean) -> Unit)? = null ) { - getOrStoreElevationOriginalValue() startSpringAnimation( key = SpringAnimationPropertyKey.ELEVATION.id, property = elevationViewProperty(), @@ -52,24 +31,6 @@ fun View.startElevationSpringAnimation( ) } -@RequiresApi(21) -internal fun View.getOrStoreElevationOriginalValue(): Float { - val key = OriginalValueKey.ELEVATION.id - return runCatching { - getTag(key) as Float - }.getOrElse { - elevation.apply { - setTag(key, this) - } - } -} - -fun View.clearElevationOriginalValue() { - runCatching { - setTag(OriginalValueKey.ELEVATION.id, null) - } -} - @RequiresApi(21) fun elevationViewProperty() = object : FloatPropertyCompat( "viewProperty" diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt b/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt index 38dceb9..00e8a0b 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/OriginalValueKey.kt @@ -4,6 +4,5 @@ import androidx.annotation.StringRes enum class OriginalValueKey(@StringRes val id: Int) { HEIGHT(R.string.expanding_collapsing_height_original_value_key), - WIDTH(R.string.expanding_collapsing_width_original_value_key), - ELEVATION(R.string.elevation_original_value_key) + WIDTH(R.string.expanding_collapsing_width_original_value_key) } diff --git a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt b/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt similarity index 53% rename from app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt rename to jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt index 56a01a9..2ae88c2 100644 --- a/app/src/main/java/br/alexandregpereira/jerry/app/recyclerview/SpringItemAnimator.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt @@ -1,12 +1,14 @@ -package br.alexandregpereira.jerry.app.recyclerview +package br.alexandregpereira.jerry.animator +import android.view.View import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView import br.alexandregpereira.jerry.ANIMATION_STIFFNESS -import br.alexandregpereira.jerry.animator.BaseItemAnimator import br.alexandregpereira.jerry.dpToPx import br.alexandregpereira.jerry.fadeSpring import br.alexandregpereira.jerry.startElevationSpringAnimation +import br.alexandregpereira.jerry.startFadeInSpringAnimation +import br.alexandregpereira.jerry.startFadeOutSpringAnimation import br.alexandregpereira.jerry.startFadeSpringAnimation import br.alexandregpereira.jerry.startTranslationXSpringAnimation import br.alexandregpereira.jerry.startTranslationYSpringAnimation @@ -14,40 +16,31 @@ import br.alexandregpereira.jerry.translationXSpring import br.alexandregpereira.jerry.translationYSpring @RequiresApi(21) -class SpringItemAnimator : BaseItemAnimator() { - - private val elevationStiffness = ANIMATION_STIFFNESS * 2.5f - private val alphaStiffness = ANIMATION_STIFFNESS * 2f - private val elevationInitialValue = 0f - private val alphaInitialValue = 0f - private val elevationFinalValue = 4f - private val alphaFinalValue = 1f +class ElevationSpringItemAnimator( + private val elevation: Float? = null, + private val elevationStiffness: Float = ANIMATION_STIFFNESS * 2.5f, + private val alphaStiffness: Float = ANIMATION_STIFFNESS * 2f, + private val translationStiffness: Float = ANIMATION_STIFFNESS * 1.2f +) : BaseItemAnimator() { + + private val translationOrigin = 0f + private val elevationNone = 0f + private val alphaNone = 0f + private val alphaFull = 1f + private val View.elevationFull: Float + get() = this@ElevationSpringItemAnimator.elevation ?: 2f.dpToPx(resources) override fun preAnimateAdd(holder: RecyclerView.ViewHolder): Boolean { - holder.itemView.alpha = 0f - holder.itemView.elevation = 0f + holder.itemView.alpha = alphaNone + holder.itemView.elevation = elevationNone return true } override fun startAddAnimation( holder: RecyclerView.ViewHolder ): Boolean { - holder.itemView.startFadeSpringAnimation( - stiffness = alphaStiffness, - targetValue = alphaFinalValue - ) { canceled -> - - if (canceled) { - onAnimateAddFinished(holder) - return@startFadeSpringAnimation - } - - holder.itemView.startElevationSpringAnimation( - stiffness = elevationStiffness, - targetValue = elevationFinalValue.dpToPx(holder.itemView.resources) - ) { - onAnimateAddFinished(holder) - } + holder.itemView.startFadeElevationInAnimation { + onAnimateAddFinished(holder) } return true } @@ -55,24 +48,8 @@ class SpringItemAnimator : BaseItemAnimator() { override fun startRemoveAnimation( holder: RecyclerView.ViewHolder ): Boolean { - holder.itemView.startElevationSpringAnimation( - stiffness = elevationStiffness, - targetValue = elevationInitialValue.dpToPx(holder.itemView.resources) - ) { canceled -> - - if (canceled) { - onAnimateRemoveFinished(holder) - return@startElevationSpringAnimation - } - - holder.itemView.startFadeSpringAnimation( - stiffness = alphaStiffness, - targetValue = alphaInitialValue - ) { - holder.itemView.alpha = alphaFinalValue - holder.itemView.elevation = elevationFinalValue - onAnimateRemoveFinished(holder) - } + holder.itemView.startFadeElevationOutAnimation { + onAnimateRemoveFinished(holder) } return true } @@ -82,27 +59,33 @@ class SpringItemAnimator : BaseItemAnimator() { deltaX: Int, deltaY: Int ): Boolean { - val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> + val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> if (completed) { onAnimateMoveFinished(this) } } holder.itemView.apply { - val translationXTargetValue = if (deltaX != 0) 0f else translationX - val translationYTargetValue = if (deltaY != 0) 0f else translationY + val translationXTargetValue = if (deltaX != 0) translationOrigin else translationX + val translationYTargetValue = if (deltaY != 0) translationOrigin else translationY - startTranslationXSpringAnimation(targetValue = translationXTargetValue) { canceled -> + startTranslationXSpringAnimation( + stiffness = translationStiffness, + targetValue = translationXTargetValue + ) { canceled -> if (canceled) { - if (deltaX != 0) translationX = 0f + if (deltaX != 0) translationX = translationOrigin } holder.onAnimationEnd( translationYSpring().isRunning.not() ) } - startTranslationYSpringAnimation(targetValue = translationYTargetValue) { canceled -> + startTranslationYSpringAnimation( + stiffness = translationStiffness, + targetValue = translationYTargetValue + ) { canceled -> if (canceled) { - if (deltaY != 0) translationY = 0f + if (deltaY != 0) translationY = translationOrigin } holder.onAnimationEnd( translationXSpring().isRunning.not() @@ -118,7 +101,7 @@ class SpringItemAnimator : BaseItemAnimator() { translationY: Float ): Boolean { oldHolder.startChangeAnimation( - alphaTargetValue = alphaInitialValue, + alphaTargetValue = alphaNone, translationXTargetValue = translationX, translationYTargetValue = translationY, oldItem = true @@ -132,9 +115,9 @@ class SpringItemAnimator : BaseItemAnimator() { onNewViewAnimateChangeStarted(newHolder) newHolder.startChangeAnimation( - alphaTargetValue = alphaFinalValue, - translationXTargetValue = 0f, - translationYTargetValue = 0f, + alphaTargetValue = alphaFull, + translationXTargetValue = translationOrigin, + translationYTargetValue = translationOrigin, oldItem = false ) return true @@ -146,18 +129,18 @@ class SpringItemAnimator : BaseItemAnimator() { translationYTargetValue: Float, oldItem: Boolean ) { - val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> + val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> if (completed) { - itemView.alpha = alphaFinalValue - itemView.elevation = elevationFinalValue - itemView.translationX = 0f - itemView.translationY = 0f + itemView.alpha = alphaFull + itemView.elevation = itemView.elevationFull + itemView.translationX = translationOrigin + itemView.translationY = translationOrigin onAnimateChangeFinished(this, oldItem) } } this.itemView.apply { - elevation = elevationInitialValue + elevation = elevationNone startFadeSpringAnimation( stiffness = alphaStiffness, targetValue = alphaTargetValue @@ -168,13 +151,19 @@ class SpringItemAnimator : BaseItemAnimator() { ) } - startTranslationXSpringAnimation(targetValue = translationXTargetValue) { + startTranslationXSpringAnimation( + stiffness = translationStiffness, + targetValue = translationXTargetValue + ) { this@startChangeAnimation.onAnimationEnd( translationYSpring().isRunning.not() && fadeSpring().isRunning.not() ) } - startTranslationYSpringAnimation(targetValue = translationYTargetValue) { + startTranslationYSpringAnimation( + stiffness = translationStiffness, + targetValue = translationYTargetValue + ) { this@startChangeAnimation.onAnimationEnd( fadeSpring().isRunning.not() && translationXSpring().isRunning.not() @@ -182,4 +171,39 @@ class SpringItemAnimator : BaseItemAnimator() { } } } -} \ No newline at end of file + + private fun View.startFadeElevationInAnimation(onAnimationFinished: () -> Unit) { + startFadeInSpringAnimation(stiffness = alphaStiffness) { canceled -> + if (canceled) { + onAnimationFinished() + return@startFadeInSpringAnimation + } + + startElevationSpringAnimation( + stiffness = elevationStiffness, + targetValue = elevationFull + ) { + onAnimationFinished() + } + } + } + + private fun View.startFadeElevationOutAnimation(onAnimationFinished: () -> Unit) { + startElevationSpringAnimation( + stiffness = elevationStiffness, + targetValue = elevationNone + ) { canceled -> + + if (canceled) { + onAnimationFinished() + return@startElevationSpringAnimation + } + + startFadeOutSpringAnimation(stiffness = alphaStiffness) { + alpha = alphaFull + elevation = elevationFull + onAnimationFinished() + } + } + } +} diff --git a/jerry/src/main/res/values/strings.xml b/jerry/src/main/res/values/strings.xml index 32ece5c..a3e11fd 100644 --- a/jerry/src/main/res/values/strings.xml +++ b/jerry/src/main/res/values/strings.xml @@ -9,7 +9,6 @@ elevation_spring_key elevation_end_listener_key - elevation_original_value_key translation_x_spring_key translation_x_end_listener_key From b4892e2633e54647a297180527431f90be782d0e Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Thu, 15 Oct 2020 01:42:42 -0300 Subject: [PATCH 24/33] Fix change animation. Added elevation animation --- .../animator/ElevationSpringItemAnimator.kt | 92 ++++++++++++------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt b/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt index 2ae88c2..524fa01 100644 --- a/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt +++ b/jerry/src/main/java/br/alexandregpereira/jerry/animator/ElevationSpringItemAnimator.kt @@ -129,45 +129,71 @@ class ElevationSpringItemAnimator( translationYTargetValue: Float, oldItem: Boolean ) { - val onAnimationEnd: RecyclerView.ViewHolder.(completed: Boolean) -> Unit = { completed -> + val elevationFull = itemView.elevationFull + val onAnimationEnd: RecyclerView.ViewHolder.(canceled: Boolean, completed: Boolean) -> Unit = { canceled, completed -> if (completed) { - itemView.alpha = alphaFull - itemView.elevation = itemView.elevationFull - itemView.translationX = translationOrigin - itemView.translationY = translationOrigin - onAnimateChangeFinished(this, oldItem) + if (canceled) { + itemView.alpha = alphaFull + itemView.elevation = elevationFull + itemView.translationX = translationOrigin + itemView.translationY = translationOrigin + onAnimateChangeFinished(this, oldItem) + } else { + itemView.startElevationSpringAnimation( + stiffness = elevationStiffness, + targetValue = elevationFull + ) { + itemView.alpha = alphaFull + itemView.elevation = elevationFull + itemView.translationX = translationOrigin + itemView.translationY = translationOrigin + onAnimateChangeFinished(this, oldItem) + } + } } } this.itemView.apply { - elevation = elevationNone - startFadeSpringAnimation( - stiffness = alphaStiffness, - targetValue = alphaTargetValue - ) { - this@startChangeAnimation.onAnimationEnd( - translationYSpring().isRunning.not() - && translationXSpring().isRunning.not() - ) - } + startElevationSpringAnimation( + stiffness = elevationStiffness, + targetValue = elevationNone + ) { canceled -> + if (canceled) { + onAnimationEnd(true, true) + return@startElevationSpringAnimation + } - startTranslationXSpringAnimation( - stiffness = translationStiffness, - targetValue = translationXTargetValue - ) { - this@startChangeAnimation.onAnimationEnd( - translationYSpring().isRunning.not() - && fadeSpring().isRunning.not() - ) - } - startTranslationYSpringAnimation( - stiffness = translationStiffness, - targetValue = translationYTargetValue - ) { - this@startChangeAnimation.onAnimationEnd( - fadeSpring().isRunning.not() - && translationXSpring().isRunning.not() - ) + startFadeSpringAnimation( + stiffness = alphaStiffness, + targetValue = alphaTargetValue + ) { + this@startChangeAnimation.onAnimationEnd( + it, + translationYSpring().isRunning.not() + && translationXSpring().isRunning.not() + ) + } + + startTranslationXSpringAnimation( + stiffness = translationStiffness, + targetValue = translationXTargetValue + ) { + this@startChangeAnimation.onAnimationEnd( + it, + translationYSpring().isRunning.not() + && fadeSpring().isRunning.not() + ) + } + startTranslationYSpringAnimation( + stiffness = translationStiffness, + targetValue = translationYTargetValue + ) { + this@startChangeAnimation.onAnimationEnd( + it, + fadeSpring().isRunning.not() + && translationXSpring().isRunning.not() + ) + } } } } From ec0cba1d484186119b0291fa15dd73843402ef2e Mon Sep 17 00:00:00 2001 From: Alexandre Pereira Date: Thu, 15 Oct 2020 02:09:30 -0300 Subject: [PATCH 25/33] Fix theme --- app/src/main/res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 44cab38..82d936d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ -