Skip to content

Commit

Permalink
Fix BottomNavigationView not saving state
Browse files Browse the repository at this point in the history
Bug: 32995862
Test: Added new test to BottomNavigationViewTest
Change-Id: Id66e262e8264c2d09c01580f4f0055d026300684

PiperOrigin-RevId: 149586534
  • Loading branch information
jdkoren authored and Travis Collins committed Mar 15, 2017
1 parent 5ec569f commit 9e2c201
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import android.support.v7.view.menu.MenuItemImpl;
import android.support.v7.view.menu.MenuView;
import android.util.AttributeSet;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

Expand All @@ -49,7 +50,8 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView {
private boolean mShiftingMode = true;

private BottomNavigationItemView[] mButtons;
private int mActiveButton = 0;
private int mSelectedItemId = 0;
private int mSelectedItemPosition = 0;
private ColorStateList mItemIconTint;
private ColorStateList mItemTextColor;
private int mItemBackgroundRes;
Expand Down Expand Up @@ -113,7 +115,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
int extra = width - activeWidth - inactiveWidth * inactiveCount;
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = (i == mActiveButton) ? activeWidth : inactiveWidth;
mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth;
if (extra > 0) {
mTempChildWidths[i]++;
extra--;
Expand Down Expand Up @@ -274,8 +276,8 @@ public void buildMenuView() {
child.setOnClickListener(mOnClickListener);
addView(child);
}
mActiveButton = Math.min(mMenu.size() - 1, mActiveButton);
mMenu.getItem(mActiveButton).setChecked(true);
mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition);
mMenu.getItem(mSelectedItemPosition).setChecked(true);
}

public void updateMenuView() {
Expand All @@ -287,22 +289,24 @@ public void updateMenuView() {
}
for (int i = 0; i < menuSize; i++) {
mPresenter.setUpdateSuspended(true);
if (mMenu.getItem(i).isChecked()) {
mActiveButton = i;
MenuItem item = mMenu.getItem(i);
if (item.isChecked()) {
mSelectedItemId = item.getItemId();
mSelectedItemPosition = i;
}
mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0);
mButtons[i].initialize((MenuItemImpl) item, 0);
mPresenter.setUpdateSuspended(false);
}
}

private void activateNewButton(int newButton) {
if (mActiveButton == newButton) return;
if (mSelectedItemPosition == newButton) {
return;
}

mAnimationHelper.beginDelayedTransition(this);

mMenu.getItem(newButton).setChecked(true);

mActiveButton = newButton;
mSelectedItemPosition = newButton;
}

private BottomNavigationItemView getNewItem() {
Expand All @@ -312,4 +316,21 @@ private BottomNavigationItemView getNewItem() {
}
return item;
}

int getSelectedItemId() {
return mSelectedItemId;
}

void tryRestoreSelectedItemId(int itemId) {
final int size = mMenu.size();
for (int i = 0; i < size; i++) {
MenuItem item = mMenu.getItem(i);
if (itemId == item.getItemId()) {
mSelectedItemId = itemId;
mSelectedItemPosition = i;
item.setChecked(true);
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import static android.support.annotation.RestrictTo.Scope.GROUP_ID;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuItemImpl;
Expand All @@ -34,6 +36,7 @@ public class BottomNavigationPresenter implements MenuPresenter {
private MenuBuilder mMenu;
private BottomNavigationMenuView mMenuView;
private boolean mUpdateSuspended = false;
private int mId;

public void setBottomNavigationMenuView(BottomNavigationMenuView menuView) {
mMenuView = menuView;
Expand Down Expand Up @@ -86,20 +89,63 @@ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
return false;
}

public void setId(int id) {
mId = id;
}

@Override
public int getId() {
return -1;
return mId;
}

@Override
public Parcelable onSaveInstanceState() {
return null;
SavedState savedState = new SavedState();
savedState.selectedItemId = mMenuView.getSelectedItemId();
return savedState;
}

@Override
public void onRestoreInstanceState(Parcelable state) {}
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof SavedState) {
mMenuView.tryRestoreSelectedItemId(((SavedState) state).selectedItemId);
}
}

public void setUpdateSuspended(boolean updateSuspended) {
mUpdateSuspended = updateSuspended;
}

static class SavedState implements Parcelable {
int selectedItemId;

SavedState() {}

SavedState(Parcel in) {
selectedItemId = in.readInt();
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(selectedItemId);
}

public static final Creator<SavedState> CREATOR =
new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
66 changes: 66 additions & 0 deletions lib/src/android/support/design/widget/BottomNavigationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
Expand All @@ -27,6 +30,9 @@
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.internal.BottomNavigationPresenter;
import android.support.v4.content.ContextCompat;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.view.SupportMenuInflater;
Expand Down Expand Up @@ -85,6 +91,8 @@ public class BottomNavigationView extends FrameLayout {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};

private static final int MENU_PRESENTER_ID = 1;

private final MenuBuilder mMenu;
private final BottomNavigationMenuView mMenuView;
private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
Expand Down Expand Up @@ -116,6 +124,7 @@ public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAtt
mMenuView.setLayoutParams(params);

mPresenter.setBottomNavigationMenuView(mMenuView);
mPresenter.setId(MENU_PRESENTER_ID);
mMenuView.setPresenter(mPresenter);
mMenu.addMenuPresenter(mPresenter);
mPresenter.initForMenu(getContext(), mMenu);
Expand Down Expand Up @@ -323,4 +332,61 @@ private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor
});
}

@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.menuPresenterState = new Bundle();
mMenu.savePresenterStates(savedState.menuPresenterState);
return savedState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mMenu.restorePresenterStates(savedState.menuPresenterState);
}

static class SavedState extends AbsSavedState {
Bundle menuPresenterState;

public SavedState(Parcelable superState) {
super(superState);
}

public SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
readFromParcel(source, loader);
}

@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeBundle(menuPresenterState);
}

private void readFromParcel(Parcel in, ClassLoader loader) {
menuPresenterState = in.readBundle(loader);
}

public static final Creator<SavedState> CREATOR =
ParcelableCompat.newCreator(
new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}

@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import static org.mockito.Mockito.when;

import android.content.res.Resources;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.design.testapp.BottomNavigationViewActivity;
import android.support.design.testapp.R;
Expand Down Expand Up @@ -253,6 +254,34 @@ public void testClearingMenu() throws Throwable {
assertEquals(3, mBottomNavigation.getMenu().size());
}

@Test
@SmallTest
public void testSavedState() throws Throwable {
// Select an item other than the first
onView(
allOf(
withText(mMenuStringContent.get(R.id.destination_profile)),
isDescendantOfA(withId(R.id.bottom_navigation)),
isDisplayed()))
.perform(click());
assertTrue(mBottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked());
// Save the state
final Parcelable state = mBottomNavigation.onSaveInstanceState();

// Restore the state into a fresh BottomNavigationView
activityTestRule.runOnUiThread(
new Runnable() {
@Override
public void run() {
BottomNavigationView testView =
new BottomNavigationView(activityTestRule.getActivity());
testView.inflateMenu(R.menu.bottom_navigation_view_content);
testView.onRestoreInstanceState(state);
assertTrue(testView.getMenu().findItem(R.id.destination_profile).isChecked());
}
});
}

private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable {
menu.findItem(id).setChecked(true);
for (int i = 0; i < menu.size(); i++) {
Expand Down

0 comments on commit 9e2c201

Please sign in to comment.