Fix bottom nav sometimes not hide correctly

Replace homemade animation with StateListAnimator
This commit is contained in:
RikkaW 2022-01-17 18:13:25 +08:00 committed by John Wu
parent ff8f3e766e
commit bfe6bc3095
4 changed files with 149 additions and 42 deletions

View File

@ -11,6 +11,7 @@ import android.view.View
import android.view.WindowManager
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
@ -97,10 +98,8 @@ class MainActivity : BaseMainActivity<MainViewModel, ActivityMainMd2Binding>() {
getScreen(section)?.navigate()
if (savedInstanceState != null) {
if (!isRootFragment) {
requestNavigationHidden()
}
if (!isRootFragment) {
requestNavigationHidden(requiresAnimation = savedInstanceState == null)
}
}
@ -120,45 +119,13 @@ class MainActivity : BaseMainActivity<MainViewModel, ActivityMainMd2Binding>() {
}
}
@Suppress("UNCHECKED_CAST")
internal fun requestNavigationHidden(hide: Boolean = true) {
internal fun requestNavigationHidden(hide: Boolean = true, requiresAnimation: Boolean = true) {
val bottomView = binding.mainNavigation
// A copy of HideBottomViewOnScrollBehavior's animation
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
}
})
}
(bottomView.tag as? Animator)?.cancel()
bottomView.clearAnimation()
if (hide) {
animateTranslationY(
bottomView,
bottomView.measuredHeight,
175L,
FastOutLinearInInterpolator()
)
if (requiresAnimation) {
bottomView.isVisible = true
bottomView.isHidden = hide
} else {
animateTranslationY(
bottomView,
0,
225L,
LinearOutSlowInInterpolator()
)
bottomView.isGone = hide
}
}

View File

@ -0,0 +1,136 @@
package com.topjohnwu.magisk.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.StateListAnimator;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.topjohnwu.magisk.R;
public class ConcealableBottomNavigationView extends BottomNavigationView {
private static final int[] STATE_SET = {
R.attr.state_hidden
};
private boolean isHidden;
public ConcealableBottomNavigationView(@NonNull Context context) {
this(context, null);
}
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.bottomNavigationStyle);
}
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, R.style.Widget_Design_BottomNavigationView);
}
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private void recreateAnimator(int height) {
Animator toHidden = ObjectAnimator.ofFloat(this, "translationY", height);
toHidden.setDuration(175);
toHidden.setInterpolator(new FastOutLinearInInterpolator());
Animator toUnhidden = ObjectAnimator.ofFloat(this, "translationY", 0);
toHidden.setDuration(225);
toHidden.setInterpolator(new FastOutLinearInInterpolator());
StateListAnimator animator = new StateListAnimator();
animator.addState(STATE_SET, toHidden);
animator.addState(new int[]{}, toUnhidden);
setStateListAnimator(animator);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
recreateAnimator(getMeasuredHeight());
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isHidden()) {
mergeDrawableStates(drawableState, STATE_SET);
}
return drawableState;
}
public boolean isHidden() {
return isHidden;
}
public void setHidden(boolean raised) {
if (isHidden != raised) {
isHidden = raised;
refreshDrawableState();
}
}
@NonNull
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
state.isHidden = isHidden();
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isHidden) {
setHidden(isHidden);
}
}
static class SavedState extends View.BaseSavedState {
public boolean isHidden;
public SavedState(Parcel source) {
super(source);
isHidden = source.readByte() != 0;
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeByte(isHidden ? (byte) 1 : (byte) 0);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

View File

@ -48,7 +48,7 @@
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
<com.topjohnwu.magisk.widget.ConcealableBottomNavigationView
android:id="@+id/main_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -47,4 +47,8 @@
</attr>
</declare-styleable>
<declare-styleable name="ConcealableView">
<attr name="state_hidden" format="boolean" />
</declare-styleable>
</resources>