Animated insets for the fabs when keyboard shows

The reason I'm on android 11's sdk in the first place

Also gonna need a lot of checks because android just deprecated all the inset stuff for pre 11

Some of these changes actually optimzed the tab bar animations a lot
This commit is contained in:
Jays2Kings 2021-05-26 23:25:28 -04:00
parent 47c2f5f97f
commit 0fa3eef40e
6 changed files with 122 additions and 7 deletions

View File

@ -8,6 +8,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.TypedValue import android.util.TypedValue
@ -20,12 +21,16 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -80,6 +85,7 @@ import eu.kanade.tachiyomi.util.moveCategories
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getBottomGestureInsets import eu.kanade.tachiyomi.util.system.getBottomGestureInsets
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.isImeVisible
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.collapse
@ -218,6 +224,28 @@ class LibraryController(
) )
} }
val cb = object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onStart(
animation: WindowInsetsAnimationCompat,
bounds: WindowInsetsAnimationCompat.BoundsCompat
): WindowInsetsAnimationCompat.BoundsCompat {
updateHopperY()
return bounds
}
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
updateHopperY(insets.toWindowInsets())
return insets
}
override fun onEnd(animation: WindowInsetsAnimationCompat) {
updateHopperY()
}
}
private var scrollListener = object : RecyclerView.OnScrollListener() { private var scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
@ -536,6 +564,8 @@ class LibraryController(
} }
setSwipeRefresh() setSwipeRefresh()
ViewCompat.setWindowInsetsAnimationCallback(view, cb)
if (selectedMangas.isNotEmpty()) { if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded() createActionModeIfNeeded()
} }
@ -691,16 +721,21 @@ class LibraryController(
} }
} }
fun updateHopperY() { fun updateHopperY(windowInsets: WindowInsets? = null) {
val view = view ?: return val view = view ?: return
val insets = windowInsets ?: view.rootWindowInsets
val listOfYs = mutableListOf( val listOfYs = mutableListOf(
binding.filterBottomSheet.filterBottomSheet.y, binding.filterBottomSheet.filterBottomSheet.y,
activityBinding?.bottomNav?.y ?: binding.filterBottomSheet.filterBottomSheet.y activityBinding?.bottomNav?.y ?: binding.filterBottomSheet.filterBottomSheet.y
) )
val insetBottom = view.rootWindowInsets?.systemWindowInsetBottom ?: 0 val insetBottom = insets?.systemWindowInsetBottom ?: 0
if (!preferences.autohideHopper().get() || activityBinding?.bottomNav == null) { if (!preferences.autohideHopper().get() || activityBinding?.bottomNav == null) {
listOfYs.add(view.height - (insetBottom).toFloat()) listOfYs.add(view.height - (insetBottom).toFloat())
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && insets?.isImeVisible() == true) {
val insetKey = insets.getInsets(WindowInsets.Type.ime() or WindowInsets.Type.systemBars()).bottom
listOfYs.add(view.height - (insetKey).toFloat())
}
binding.categoryHopperFrame.y = -binding.categoryHopperFrame.height + binding.categoryHopperFrame.y = -binding.categoryHopperFrame.height +
(listOfYs.minOrNull() ?: binding.filterBottomSheet.filterBottomSheet.y) + (listOfYs.minOrNull() ?: binding.filterBottomSheet.filterBottomSheet.y) +
hopperOffset + hopperOffset +

View File

@ -767,11 +767,15 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
setFloatingToolbar(canShowFloatingToolbar(to)) setFloatingToolbar(canShowFloatingToolbar(to))
val onRoot = router.backstackSize == 1 val onRoot = router.backstackSize == 1
if (onRoot) { if (onRoot) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R && !isPush) {
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
}
binding.toolbar.navigationIcon = searchDrawable binding.toolbar.navigationIcon = searchDrawable
binding.cardToolbar.navigationIcon = searchDrawable binding.cardToolbar.navigationIcon = searchDrawable
} else { } else {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
}
binding.toolbar.navigationIcon = drawerArrow binding.toolbar.navigationIcon = drawerArrow
binding.cardToolbar.navigationIcon = drawerArrow binding.cardToolbar.navigationIcon = drawerArrow
} }

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.source.browse package eu.kanade.tachiyomi.ui.source.browse
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -35,6 +36,7 @@ import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.system.connectivityManager import eu.kanade.tachiyomi.util.system.connectivityManager
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.view.applyBottomAnimatedInsets
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
@ -177,11 +179,14 @@ open class BrowseSourceController(bundle: Bundle) :
recycler, recycler,
true, true,
afterInsets = { insets -> afterInsets = { insets ->
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
binding.fab.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.fab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx
} }
} }
}
) )
binding.fab.applyBottomAnimatedInsets(16.dpToPx)
recycler.addOnScrollListener( recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() { object : RecyclerView.OnScrollListener() {

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.system
import android.os.Build import android.os.Build
import android.view.WindowInsets import android.view.WindowInsets
import androidx.annotation.RequiresApi
fun WindowInsets.getBottomGestureInsets(): Int { fun WindowInsets.getBottomGestureInsets(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mandatorySystemGestureInsets.bottom return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mandatorySystemGestureInsets.bottom
@ -20,3 +21,6 @@ fun WindowInsets.hasSideInsets() = systemWindowInsetLeft > 0 || systemWindowInse
fun WindowInsets.hasSideNavBar() = fun WindowInsets.hasSideNavBar() =
(systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() && (systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() &&
systemWindowInsetBottom == 0 systemWindowInsetBottom == 0
@RequiresApi(Build.VERSION_CODES.R)
fun WindowInsets.isImeVisible() = isVisible(WindowInsets.Type.ime())

View File

@ -113,7 +113,11 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false)
val headerHeight = insets.systemWindowInsetTop + appBarHeight val headerHeight = insets.systemWindowInsetTop + appBarHeight
view.updatePaddingRelative( view.updatePaddingRelative(
top = headerHeight, top = headerHeight,
bottom = insets.systemWindowInsetBottom bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.ime() or WindowInsets.Type.systemBars()).bottom
} else {
insets.systemWindowInsetBottom
}
) )
} }
} else { } else {

View File

@ -25,6 +25,8 @@ import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.LinearSmoothScroller
@ -121,6 +123,57 @@ object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener {
} }
} }
fun View.applyBottomAnimatedInsets(bottomMargin: Int = 0) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return
val setInsets: ((WindowInsets) -> Unit) = { insets ->
updateLayoutParams<ViewGroup.MarginLayoutParams> {
val bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars() or WindowInsets.Type.ime()).bottom
} else {
insets.systemWindowInsetBottom
}
this.bottomMargin = bottom + bottomMargin
}
}
var handleInsets = true
doOnApplyWindowInsets { _, insets, _ ->
if (handleInsets) {
setInsets(insets)
}
}
ViewCompat.setWindowInsetsAnimationCallback(
this,
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
handleInsets = false
super.onPrepare(animation)
}
override fun onStart(
animation: WindowInsetsAnimationCompat,
bounds: WindowInsetsAnimationCompat.BoundsCompat
): WindowInsetsAnimationCompat.BoundsCompat {
handleInsets = false
rootWindowInsets?.let { insets -> setInsets(insets) }
return super.onStart(animation, bounds)
}
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
insets.toWindowInsets()?.let { setInsets(it) }
return insets
}
override fun onEnd(animation: WindowInsetsAnimationCompat) {
handleInsets = true
rootWindowInsets?.let { insets -> setInsets(insets) }
}
}
)
}
object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener { object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener {
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
v.updateLayoutParams<FrameLayout.LayoutParams> { v.updateLayoutParams<FrameLayout.LayoutParams> {
@ -155,6 +208,16 @@ fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit
requestApplyInsetsWhenAttached() requestApplyInsetsWhenAttached()
} }
fun View.doOnApplyWindowInsetsCompat(f: (View, WindowInsetsCompat, ViewPaddingState) -> Unit) {
// Create a snapshot of the view's padding state
val paddingState = createStateForView(this)
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
f(v, insets, paddingState)
insets
}
requestApplyInsetsWhenAttached()
}
fun View.applyWindowInsetsForController() { fun View.applyWindowInsetsForController() {
setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener) setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener)
requestApplyInsetsWhenAttached() requestApplyInsetsWhenAttached()