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 package com.topjohnwu.magisk.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowManager import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach 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 androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R 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.di.viewModel
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
@ -119,40 +122,44 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal fun requestNavigationHidden(hide: Boolean = true) { internal fun requestNavigationHidden(hide: Boolean = true) {
val topView = binding.mainToolbarWrapper val bottomView = binding.mainNavigation
val bottomView = binding.mainBottomBar
if (!binding.mainBottomBar.isAttachedToWindow) { // A copy of HideBottomViewOnScrollBehavior's animation
binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object :
ViewTreeObserver.OnWindowAttachListener {
init { fun animateTranslationY(
val listener = view: View, targetY: Int, duration: Long, interpolator: TimeInterpolator
binding.mainBottomBar.tag as? ViewTreeObserver.OnWindowAttachListener ) {
if (listener != null) { view.tag = view
binding.mainBottomBar.viewTreeObserver.removeOnWindowAttachListener(listener) .animate()
} .translationY(targetY.toFloat())
binding.mainBottomBar.tag = this .setInterpolator(interpolator)
} .setDuration(duration)
.setListener(
override fun onWindowAttached() { object : AnimatorListenerAdapter() {
requestNavigationHidden(hide) override fun onAnimationEnd(animation: Animator) {
} view.tag = null
}
override fun onWindowDetached() { })
}
})
return
} }
val topParams = topView.layoutParams as? CoordinatorLayout.LayoutParams (bottomView.tag as? Animator)?.cancel()
val bottomParams = bottomView.layoutParams as? CoordinatorLayout.LayoutParams bottomView.clearAnimation()
val topBehavior = topParams?.behavior as? HideableBehavior<View> if (hide) {
val bottomBehavior = bottomParams?.behavior as? HideableBehavior<View> animateTranslationY(
bottomView,
topBehavior?.setHidden(topView, hide = false, lockState = false) bottomView.measuredHeight,
bottomBehavior?.setHidden(bottomView, hide, hide) 175L,
FastOutLinearInInterpolator()
)
} else {
animateTranslationY(
bottomView,
0,
225L,
LinearOutSlowInInterpolator()
)
}
} }
fun invalidateToolbar() { 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" style="@style/WidgetFoundation.Appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_behavior="com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior"
app:fitsSystemWindowsInsets="top"> app:fitsSystemWindowsInsets="top">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
@ -49,51 +48,22 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<!-- <com.google.android.material.bottomnavigation.BottomNavigationView
todo(diareuse) this stupid-ass bottom menu doesn't fit with the fab-s, so either of those android:id="@+id/main_navigation"
things need to be removed, probably the fab-s. android:layout_width="match_parent"
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_height="wrap_content"
android:layout_gravity="bottom|center_horizontal" android:layout_gravity="bottom|center_horizontal"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
android:fitsSystemWindows="false" android:fitsSystemWindows="false"
app:layout_fitsSystemWindowsInsets="bottom" android:paddingBottom="0dp"
app:layout_behavior="com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior" app:fitsSystemWindowsInsets="start|end|bottom"
tools:layout_marginBottom="64dp"> app:itemHorizontalTranslationEnabled="false"
app:itemIconTint="@color/color_menu_tint"
<com.google.android.material.bottomnavigation.BottomNavigationView app:itemRippleColor="?colorPrimary"
android:id="@+id/main_navigation" app:itemTextAppearanceActive="@style/AppearanceFoundation.Tiny.Bold"
android:layout_width="match_parent" app:itemTextAppearanceInactive="@style/AppearanceFoundation.Tiny.Bold"
android:layout_height="wrap_content" app:itemTextColor="@color/color_menu_tint"
android:background="@android:color/transparent" app:labelVisibilityMode="labeled"
android:textStyle="bold" app:menu="@menu/menu_bottom_nav" />
android:fitsSystemWindows="false"
android:paddingBottom="0dp"
app:fitsSystemWindowsInsets="start|end"
app:elevation="0dp"
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:menu="@menu/menu_bottom_nav" />
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

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

View File

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

View File

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

View File

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