Refactor visibility animation for FabLayout

This commit is contained in:
FooIbar committed 2023-11-10 16:41:42 +08:00
1 parent 7693b11e0a
commit 14c5b28cdc
6 files changed
+73 -138

No files matched your search

@@ -22,9 +22,11 @@ import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.view.animation.Interpolator
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.core.view.isVisible
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.hippo.ehviewer.R
import com.hippo.ehviewer.util.AnimationUtils
@@ -46,6 +48,7 @@ class FabLayout @JvmOverloads constructor(
private var mMainFabCenterY = -1f
private var mOnExpandListeners = arrayListOf<OnExpandListener>()
private var mOnClickFabListener: OnClickFabListener? = null
private var fabAnimator: ViewPropertyAnimator? = null
private val mOnBackPressedCallback = object : OnBackPressedCallback(false), OnExpandListener {
override fun handleOnBackPressed() {
@@ -56,6 +59,11 @@ class FabLayout @JvmOverloads constructor(
isEnabled = expanded
}
}
private val mFabAnimatorListener = object : SimpleAnimatorListener() {
override fun onAnimationEnd(animation: Animator) {
primaryFab.isVisible = false
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -85,8 +93,8 @@ class FabLayout @JvmOverloads constructor(
super.addView(child, index, params)
}
val primaryFab: FloatingActionButton?
get() = getChildAt(childCount - 1) as? FloatingActionButton
val primaryFab: FloatingActionButton
get() = getChildAt(childCount - 1) as FloatingActionButton
private val secondaryFabCount: Int
get() = 0.coerceAtLeast(childCount - 1)
@@ -414,6 +422,43 @@ class FabLayout @JvmOverloads constructor(
}
}
fun show(animation: Boolean = true, delay: Long = 0L) {
val fab = primaryFab
if (!fab.isVisible) {
fab.isVisible = true
if (animation) {
fabAnimator?.cancel()
fabAnimator = fab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
.setDuration(ANIMATE_TIME).setStartDelay(delay)
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).apply { start() }
} else {
fab.scaleX = 1.0f
fab.scaleY = 1.0f
}
}
}
fun hide(animation: Boolean = true): Long {
val fab = primaryFab
setExpanded(expanded = false, animation = true)
return if (!fab.isVisible) {
0L
} else {
if (animation) {
fabAnimator?.cancel()
fabAnimator = fab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mFabAnimatorListener)
.setDuration(ANIMATE_TIME).setStartDelay(0L)
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).apply { start() }
ANIMATE_TIME
} else {
fab.isVisible = false
fab.scaleX = 0.0f
fab.scaleY = 0.0f
0L
}
}
}
fun interface OnExpandListener {
fun onExpand(expanded: Boolean)
}
@@ -424,7 +469,7 @@ class FabLayout @JvmOverloads constructor(
}
companion object {
private const val ANIMATE_TIME = 300L
const val ANIMATE_TIME = 300L
private const val STATE_KEY_SUPER = "super"
private const val STATE_KEY_AUTO_CANCEL = "auto_cancel"
private const val STATE_KEY_EXPANDED = "expanded"
@@ -362,7 +362,7 @@ class DownloadsScene :
}
override fun onSearchViewExpanded() {
hideActionFab(true)
binding.fabLayout.hide()
super.onSearchViewExpanded()
}
@@ -239,7 +239,7 @@ class FavoritesScene : SearchBarScene() {
updateJumpFab()
val colorID = theme.resolveColor(com.google.android.material.R.attr.colorOnSurface)
val addDelete = AddDeleteDrawable(context, colorID)
primaryFab!!.setImageDrawable(addDelete)
primaryFab.setImageDrawable(addDelete)
setExpanded(expanded = false, animation = false)
setAutoCancel(true)
setHidePrimaryFab(false)
@@ -296,9 +296,9 @@ class FavoritesScene : SearchBarScene() {
})
addOnExpandListener {
if (it) {
addDelete.setDelete(ANIMATE_TIME)
addDelete.setDelete(FabLayout.ANIMATE_TIME)
} else {
addDelete.setAdd(ANIMATE_TIME)
addDelete.setAdd(FabLayout.ANIMATE_TIME)
if (tracker.isInCustomChoice) tracker.clearSelection()
}
}
@@ -475,13 +475,13 @@ class FavoritesScene : SearchBarScene() {
}
override fun onSearchViewExpanded() {
hideActionFab(true)
binding.fabLayout.hide()
super.onSearchViewExpanded()
}
override fun onSearchViewHidden() {
super.onSearchViewHidden()
showActionFab(true)
binding.fabLayout.show(delay = SEARCH_VIEW_ANIMATE_TIME)
}
private fun showGoToDialog() {
@@ -15,7 +15,6 @@
*/
package com.hippo.ehviewer.ui.scene
import android.animation.Animator
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
@@ -25,7 +24,6 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.consumeWindowInsets
@@ -93,9 +91,7 @@ import com.hippo.ehviewer.ui.legacy.LayoutManagerUtils.firstVisibleItemPosition
import com.hippo.ehviewer.ui.legacy.ViewTransition
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.util.AnimationUtils
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.SimpleAnimatorListener
import com.hippo.ehviewer.util.applyNavigationBarsPadding
import com.hippo.ehviewer.util.getParcelableCompat
import com.hippo.ehviewer.util.getValue
@@ -192,17 +188,11 @@ class GalleryListScene : SearchBarScene() {
}
private var _binding: SceneGalleryListBinding? = null
private val binding get() = _binding!!
private var fabAnimator: ViewPropertyAnimator? = null
private var mViewTransition: ViewTransition? = null
private var mAdapter: GalleryAdapter? = null
lateinit var mQuickSearchList: MutableList<QuickSearch>
private var mHideActionFabSlop = 0
private var mState = State.NORMAL
private val mSearchFabAnimatorListener = object : SimpleAnimatorListener() {
override fun onAnimationEnd(animation: Animator) {
binding.searchFab.visibility = View.INVISIBLE
}
}
override val fabLayout get() = binding.fabLayout
override val fastScroller get() = binding.fastScroller
@@ -310,13 +300,12 @@ class GalleryListScene : SearchBarScene() {
container.addView(ComposeWithMD3 { dialogState.Intercept() })
requireActivity().onBackPressedDispatcher.addCallback(stateBackPressedCallback)
mHideActionFabSlop = ViewConfiguration.get(requireContext()).scaledTouchSlop
mShowActionFab = true
ViewCompat.setWindowInsetsAnimationCallback(
binding.root,
WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
binding.fabLayout,
binding.searchFab.parent as View,
binding.searchFabLayout,
),
)
mViewTransition = BringOutTransition(binding.contentLayout.contentView, binding.searchLayout)
@@ -460,15 +449,15 @@ class GalleryListScene : SearchBarScene() {
binding.fabLayout.addOnExpandListener {
if (it) {
lockDrawer()
actionFabDrawable.setDelete(ANIMATE_TIME)
actionFabDrawable.setDelete(FabLayout.ANIMATE_TIME)
} else {
unlockDrawer()
actionFabDrawable.setAdd(ANIMATE_TIME)
actionFabDrawable.setAdd(FabLayout.ANIMATE_TIME)
}
}
binding.fabLayout.primaryFab!!.setImageDrawable(actionFabDrawable)
binding.fabLayout.primaryFab.setImageDrawable(actionFabDrawable)
binding.searchFab.setOnClickListener { onApplySearch() }
(binding.searchFab.parent as View).apply {
binding.searchFabLayout.apply {
bringToFront()
applyNavigationBarsPadding()
}
@@ -493,7 +482,6 @@ class GalleryListScene : SearchBarScene() {
stateBackPressedCallback.remove()
binding.recyclerView.stopScroll()
_binding = null
fabAnimator = null
mAdapter = null
mViewTransition = null
}
@@ -692,52 +680,21 @@ class GalleryListScene : SearchBarScene() {
}
}
private fun showSearchFab(animation: Boolean, delay: Long = 0) {
if (animation) {
binding.searchFab.visibility = View.VISIBLE
binding.searchFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
.setDuration(ANIMATE_TIME).setStartDelay(delay)
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
} else {
binding.searchFab.visibility = View.VISIBLE
binding.searchFab.scaleX = 1.0f
binding.searchFab.scaleY = 1.0f
}
}
private fun hideSearchFab(animation: Boolean): Long {
return if (View.INVISIBLE == binding.searchFab.visibility) {
0L
} else {
if (animation) {
binding.searchFab.animate().scaleX(0.0f).scaleY(0.0f)
.setListener(mSearchFabAnimatorListener)
.setDuration(ANIMATE_TIME).setStartDelay(0L)
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
} else {
binding.searchFab.visibility = View.INVISIBLE
binding.searchFab.scaleX = 0.0f
binding.searchFab.scaleY = 0.0f
}
ANIMATE_TIME
}
}
private fun selectSearchFab(animation: Boolean) {
val delay = hideActionFab(animation)
showSearchFab(animation, delay)
val delay = binding.fabLayout.hide(animation)
binding.searchFabLayout.show(animation, delay)
}
private fun selectActionFab(animation: Boolean) {
val delay = hideSearchFab(animation)
showActionFab(animation, delay)
val delay = binding.searchFabLayout.hide(animation)
binding.fabLayout.show(animation, delay)
}
override fun onSearchViewExpanded() {
if (mState == State.NORMAL) {
hideActionFab(true)
binding.fabLayout.hide()
} else {
hideSearchFab(true)
binding.searchFabLayout.hide()
}
super.onSearchViewExpanded()
}
@@ -745,42 +702,23 @@ class GalleryListScene : SearchBarScene() {
override fun onSearchViewHidden() {
super.onSearchViewHidden()
if (mState == State.NORMAL) {
showActionFab(true)
binding.fabLayout.show(delay = SEARCH_VIEW_ANIMATE_TIME)
} else {
showSearchFab(true)
binding.searchFabLayout.show(delay = SEARCH_VIEW_ANIMATE_TIME)
}
}
private fun showActionFab() {
_binding ?: return
if (State.NORMAL == mState && !mShowActionFab) {
mShowActionFab = true
val fab: View? = binding.fabLayout.primaryFab
if (fabAnimator != null) {
fabAnimator!!.cancel()
}
fab!!.visibility = View.VISIBLE
fab.rotation = -45.0f
fabAnimator = fab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
.setDuration(ANIMATE_TIME).setStartDelay(0L)
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR)
fabAnimator!!.start()
if (State.NORMAL == mState) {
binding.fabLayout.show()
}
}
private fun hideActionFab() {
_binding ?: return
if (State.NORMAL == mState && mShowActionFab) {
mShowActionFab = false
val fab: View? = binding.fabLayout.primaryFab
if (fabAnimator != null) {
fabAnimator!!.cancel()
}
fabAnimator =
fab!!.animate().scaleX(0.0f).scaleY(0.0f).setListener(mActionFabAnimatorListener)
.setDuration(ANIMATE_TIME).setStartDelay(0L)
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR)
fabAnimator!!.start()
if (State.NORMAL == mState) {
binding.fabLayout.hide()
}
}
@@ -1,6 +1,5 @@
package com.hippo.ehviewer.ui.scene
import android.animation.Animator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
@@ -43,8 +42,6 @@ import com.hippo.ehviewer.databinding.SceneSearchbarBinding
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
import com.hippo.ehviewer.ui.legacy.FabLayout
import com.hippo.ehviewer.ui.setMD3Content
import com.hippo.ehviewer.util.AnimationUtils
import com.hippo.ehviewer.util.SimpleAnimatorListener
import com.hippo.ehviewer.util.applyNavigationBarsPadding
import com.hippo.ehviewer.util.isAtLeastT
import com.jamal.composeprefs3.ui.ifNotNullThen
@@ -65,12 +62,6 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
private var allowEmptySearch = true
private val mSearchDatabase = searchDatabase.searchDao()
private var onApplySearch: (String) -> Unit = {}
protected var mShowActionFab = true
protected val mActionFabAnimatorListener = object : SimpleAnimatorListener() {
override fun onAnimationEnd(animation: Animator) {
fabLayout.primaryFab?.visibility = View.INVISIBLE
}
}
protected abstract val fabLayout: FabLayout
protected abstract val fastScroller: View
@@ -180,46 +171,6 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
}
}
fun showActionFab(animation: Boolean, delay: Long = ANIMATE_TIME) {
mShowActionFab = true
if (animation) {
val fab = fabLayout.primaryFab!!
fab.visibility = View.VISIBLE
fab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
.setDuration(ANIMATE_TIME).setStartDelay(delay)
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
} else {
fabLayout.setExpanded(expanded = false, animation = false)
val fab = fabLayout.primaryFab!!
fab.visibility = View.VISIBLE
fab.scaleX = 1.0f
fab.scaleY = 1.0f
}
}
fun hideActionFab(animation: Boolean): Long {
mShowActionFab = false
return if (animation) {
val fab = fabLayout.primaryFab!!
if (View.INVISIBLE == fab.visibility) {
0L
} else {
fabLayout.setExpanded(expanded = false, animation = true)
fab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mActionFabAnimatorListener)
.setDuration(ANIMATE_TIME).setStartDelay(0L)
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
ANIMATE_TIME
}
} else {
fabLayout.setExpanded(expanded = false, animation = false)
val fab = fabLayout.primaryFab!!
fab.visibility = View.INVISIBLE
fab.scaleX = 0.0f
fab.scaleY = 0.0f
0L
}
}
fun setSearchBarHint(hint: String?) {
binding.toolbar.hint = hint
}
@@ -403,4 +354,4 @@ fun wrapTagKeyword(keyword: String, translate: Boolean = false): String {
}
}
const val ANIMATE_TIME = 300L
const val SEARCH_VIEW_ANIMATE_TIME = 300L
@@ -61,6 +61,7 @@
</com.hippo.ehviewer.ui.legacy.FabLayout>
<com.hippo.ehviewer.ui.legacy.FabLayout
android:id="@+id/search_fab_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingEnd="@dimen/corner_fab_margin"