Refactor visibility animation for FabLayout
This commit is contained in:
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"
|
||||
|
||||
Reference in new issue
Block a user