Use standard BottomNav & Remove hide on scroll for AppBar and BottomNav

This commit is contained in:
RikkaW 2021-09-03 18:38:02 +08:00 committed by John Wu
parent 605189bc6e
commit 383192784d
8 changed files with 59 additions and 349 deletions

View File

@ -1,15 +1,19 @@
package com.topjohnwu.magisk.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach
import androidx.core.view.isGone
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
@ -21,7 +25,6 @@ import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
@ -119,40 +122,44 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
@Suppress("UNCHECKED_CAST")
internal fun requestNavigationHidden(hide: Boolean = true) {
val topView = binding.mainToolbarWrapper
val bottomView = binding.mainBottomBar
val bottomView = binding.mainNavigation
if (!binding.mainBottomBar.isAttachedToWindow) {
binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object :
ViewTreeObserver.OnWindowAttachListener {
// A copy of HideBottomViewOnScrollBehavior's animation
init {
val listener =
binding.mainBottomBar.tag as? ViewTreeObserver.OnWindowAttachListener
if (listener != null) {
binding.mainBottomBar.viewTreeObserver.removeOnWindowAttachListener(listener)
}
binding.mainBottomBar.tag = this
}
override fun onWindowAttached() {
requestNavigationHidden(hide)
}
override fun onWindowDetached() {
fun animateTranslationY(
view: View, targetY: Int, duration: Long, interpolator: TimeInterpolator
) {
view.tag = view
.animate()
.translationY(targetY.toFloat())
.setInterpolator(interpolator)
.setDuration(duration)
.setListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
view.tag = null
}
})
return
}
val topParams = topView.layoutParams as? CoordinatorLayout.LayoutParams
val bottomParams = bottomView.layoutParams as? CoordinatorLayout.LayoutParams
(bottomView.tag as? Animator)?.cancel()
bottomView.clearAnimation()
val topBehavior = topParams?.behavior as? HideableBehavior<View>
val bottomBehavior = bottomParams?.behavior as? HideableBehavior<View>
topBehavior?.setHidden(topView, hide = false, lockState = false)
bottomBehavior?.setHidden(bottomView, hide, hide)
if (hide) {
animateTranslationY(
bottomView,
bottomView.measuredHeight,
175L,
FastOutLinearInInterpolator()
)
} else {
animateTranslationY(
bottomView,
0,
225L,
LinearOutSlowInInterpolator()
)
}
}
fun invalidateToolbar() {

View File

@ -1,127 +0,0 @@
package com.topjohnwu.magisk.utils
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import kotlin.math.roundToInt
class HideBottomViewOnScrollBehavior<V : View>(context: Context, attrs: AttributeSet) :
HideBottomViewOnScrollBehavior<V>(), HideableBehavior<V> {
private var lockState: Boolean = false
private var isLaidOut = false
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View) =
super.layoutDependsOn(parent, child, dependency) or (dependency is Snackbar.SnackbarLayout)
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
isLaidOut = true
return super.onLayoutChild(parent, child, layoutDirection)
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: V,
dependency: View
) = when (dependency) {
is Snackbar.SnackbarLayout -> onDependentViewChanged(parent, child, dependency)
else -> super.onDependentViewChanged(parent, child, dependency)
}
override fun onDependentViewRemoved(
parent: CoordinatorLayout,
child: V,
dependency: View
) = when (dependency) {
is Snackbar.SnackbarLayout -> onDependentViewRemoved(parent, child, dependency)
else -> super.onDependentViewRemoved(parent, child, dependency)
}
//---
private fun onDependentViewChanged(
parent: CoordinatorLayout,
child: V,
dependency: Snackbar.SnackbarLayout
): Boolean {
val viewMargin = (child.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
val additionalMargin = dependency.resources.getDimension(R.dimen.l1).roundToInt()
val translation = dependency.height + additionalMargin
dependency.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = viewMargin
}
// checks whether the navigation is not hidden via scroll
if (child.isVisible && child.translationY <= 0) {
child.translationY(-translation.toFloat())
}
return false
}
private fun onDependentViewRemoved(
parent: CoordinatorLayout,
child: V,
dependency: Snackbar.SnackbarLayout
) {
// checks whether the navigation is not hidden via scroll
if (child.isVisible && child.translationY <= 0) {
child.translationY(0f)
}
}
//---
override fun slideUp(child: V) {
if (lockState) return
super.slideUp(child)
}
override fun slideDown(child: V) {
if (lockState) return
super.slideDown(child)
}
override fun setHidden(
view: V,
hide: Boolean,
lockState: Boolean
) {
if (!lockState) {
this.lockState = lockState
}
if (hide || !Info.env.isActive) {
// view is not laid out and drawn yet properly, so animation will not be attached
// hence we just simply hide the view
if (!isLaidOut) {
view.isGone = true
} else {
slideDown(view)
}
} else {
view.isVisible = Info.env.isActive
slideUp(view)
}
this.lockState = lockState
}
//---
private fun View.translationY(destination: Float) = animate()
.translationY(destination)
.setInterpolator(FastOutSlowInInterpolator())
.start()
}

View File

@ -1,143 +0,0 @@
package com.topjohnwu.magisk.utils
import android.animation.TimeInterpolator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import com.google.android.material.animation.AnimationUtils
class HideTopViewOnScrollBehavior<V : View>(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<V>(), HideableBehavior<V> {
companion object {
private const val STATE_SCROLLED_DOWN = 1
private const val STATE_SCROLLED_UP = 2
private const val ENTER_ANIMATION_DURATION = 225
private const val EXIT_ANIMATION_DURATION = 175
}
private var height = 0
private var currentState = STATE_SCROLLED_UP
private var currentAnimator: ViewPropertyAnimator? = null
private var lockState: Boolean = false
override fun onLayoutChild(
parent: CoordinatorLayout,
child: V,
layoutDirection: Int
): Boolean {
val paramsCompat = child.layoutParams as ViewGroup.MarginLayoutParams
height = child.measuredHeight + paramsCompat.topMargin
return super.onLayoutChild(parent, child, layoutDirection)
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int
) = nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray
) {
// when initiating scroll while the view is at the bottom or at the top and pushing it
// further, the parent will report consumption of 0
if (dyConsumed == 0) return
setHidden(child, dyConsumed > 0, false)
}
@Suppress("UNCHECKED_CAST")
override fun setHidden(
view: V,
hide: Boolean,
lockState: Boolean
) {
if (!lockState) {
this.lockState = lockState
}
if (hide) {
slideUp(view)
} else {
slideDown(view)
}
this.lockState = lockState
}
/**
* Perform an animation that will slide the child from it's current position to be totally on the
* screen.
*/
private fun slideDown(child: V) {
if (currentState == STATE_SCROLLED_UP || lockState) {
return
}
currentAnimator?.let {
it.cancel()
child.clearAnimation()
}
currentState = STATE_SCROLLED_UP
animateChildTo(
child,
0,
ENTER_ANIMATION_DURATION.toLong(),
AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR
)
}
/**
* Perform an animation that will slide the child from it's current position to be totally off the
* screen.
*/
private fun slideUp(child: V) {
if (currentState == STATE_SCROLLED_DOWN || lockState) {
return
}
currentAnimator?.let {
it.cancel()
child.clearAnimation()
}
currentState = STATE_SCROLLED_DOWN
animateChildTo(
child,
-height,
EXIT_ANIMATION_DURATION.toLong(),
AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
)
}
private fun animateChildTo(
child: V,
targetY: Int,
duration: Long,
interpolator: TimeInterpolator
) = child
.animate()
.translationY(targetY.toFloat())
.setInterpolator(interpolator)
.setDuration(duration)
.withEndAction { currentAnimator = null }
.let { currentAnimator = it }
}

View File

@ -35,7 +35,6 @@
style="@style/WidgetFoundation.Appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior"
app:fitsSystemWindowsInsets="top">
<com.google.android.material.appbar.MaterialToolbar
@ -49,52 +48,23 @@
</com.google.android.material.appbar.AppBarLayout>
<!--
todo(diareuse) this stupid-ass bottom menu doesn't fit with the fab-s, so either of those
things need to be removed, probably the fab-s.
Using the BottomAppBar is not viable as it looks terrible with the blank space in the
middle and / or with the "animation" which BottomNavigationView performs when it's
reinflating its menu items. This bullshit cockblocks the entire feature. Terrific.
Possibility is then to keep the middle fab and assign it some other functionality on all
screens. Which -ha-ha- good luck finding something on superuser and homepage...
-->
<com.google.android.material.card.MaterialCardView
android:id="@+id/main_bottom_bar"
style="@style/WidgetFoundation.Card.Elevated"
android:layout_width="224dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:fitsSystemWindows="false"
app:layout_fitsSystemWindowsInsets="bottom"
app:layout_behavior="com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior"
tools:layout_marginBottom="64dp">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:textStyle="bold"
android:layout_gravity="bottom|center_horizontal"
android:fitsSystemWindows="false"
android:paddingBottom="0dp"
app:fitsSystemWindowsInsets="start|end"
app:elevation="0dp"
app:fitsSystemWindowsInsets="start|end|bottom"
app:itemHorizontalTranslationEnabled="false"
app:itemIconTint="@color/color_menu_tint"
app:itemRippleColor="?colorPrimary"
app:itemTextAppearanceActive="@style/AppearanceFoundation.Tiny.Bold"
app:itemTextAppearanceInactive="@style/AppearanceFoundation.Tiny.Bold"
app:itemTextColor="@color/color_menu_tint"
app:labelVisibilityMode="unlabeled"
app:labelVisibilityMode="labeled"
app:menu="@menu/menu_bottom_nav" />
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -29,7 +29,7 @@
android:layout_gravity="bottom|end"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:layout_marginBottom="72dp"
app:layout_fitsSystemWindowsInsets="bottom"
app:backgroundTint="?colorSurfaceSurfaceVariant"
app:srcCompat="@drawable/ic_folder_list"

View File

@ -39,6 +39,7 @@
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="@dimen/internal_action_bar_size"
android:paddingBottom="56dp"
app:fitsSystemWindowsInsets="top|bottom"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_module_md2" />
@ -50,9 +51,9 @@
android:layout_gravity="bottom|end"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
app:layout_fitsSystemWindowsInsets="bottom"
android:layout_marginBottom="72dp"
app:backgroundTint="?colorSurfaceSurfaceVariant"
app:layout_fitsSystemWindowsInsets="bottom"
app:srcCompat="@drawable/ic_search_md2"
app:tint="?colorPrimary"
tools:layout_marginBottom="64dp" />
@ -60,11 +61,11 @@
<com.google.android.material.circularreveal.cardview.CircularRevealCardView
android:id="@+id/module_filter"
style="@style/WidgetFoundation.Card"
app:cardBackgroundColor="?colorSurface"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:visibility="invisible"
app:cardBackgroundColor="?colorSurface"
app:cardCornerRadius="0dp">
<include

View File

@ -28,6 +28,7 @@
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="@dimen/internal_action_bar_size"
android:paddingBottom="56dp"
app:fitsSystemWindowsInsets="top|bottom"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_policy_md2" />

View File

@ -34,6 +34,7 @@
android:textAppearance="@style/AppearanceFoundation.Caption"
android:textSize="10sp"
android:paddingTop="@dimen/internal_action_bar_size"
android:paddingBottom="120dp"
app:layout_fitsSystemWindowsInsets="top|bottom"
tools:text="@tools:sample/lorem/random" />