Skip to content

Commit bef88ac

Browse files
author
Nicolas Gonzalez
authored
Merge pull request #1 from nico-gonzalez/feature/bottom-navigation-state-restore
Add state restoration
2 parents cad0aa3 + 0dcf580 commit bef88ac

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-2
lines changed

lib/src/android/support/design/internal/BottomNavigationPresenter.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,28 @@
1919
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
2020

2121
import android.content.Context;
22+
import android.os.Build;
23+
import android.os.Bundle;
2224
import android.os.Parcelable;
2325
import android.support.annotation.RestrictTo;
2426
import android.support.v7.view.menu.MenuBuilder;
2527
import android.support.v7.view.menu.MenuItemImpl;
2628
import android.support.v7.view.menu.MenuPresenter;
2729
import android.support.v7.view.menu.MenuView;
2830
import android.support.v7.view.menu.SubMenuBuilder;
31+
import android.util.SparseArray;
2932
import android.view.ViewGroup;
3033

3134
/** @hide */
3235
@RestrictTo(LIBRARY_GROUP)
3336
public class BottomNavigationPresenter implements MenuPresenter {
37+
38+
private static final String STATE_HIERARCHY = "android:menu:list";
39+
3440
private MenuBuilder mMenu;
3541
private BottomNavigationMenuView mMenuView;
3642
private boolean mUpdateSuspended = false;
43+
private int mId;
3744

3845
public void setBottomNavigationMenuView(BottomNavigationMenuView menuView) {
3946
mMenuView = menuView;
@@ -88,16 +95,40 @@ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
8895

8996
@Override
9097
public int getId() {
91-
return -1;
98+
return mId;
99+
}
100+
101+
public void setId(int id) {
102+
mId = id;
92103
}
93104

94105
@Override
95106
public Parcelable onSaveInstanceState() {
107+
if (Build.VERSION.SDK_INT >= 11) {
108+
// API 9-10 does not support ClassLoaderCreator, therefore things can crash if they're
109+
// loaded via different loaders. Rather than crash we just won't save state on those
110+
// platforms
111+
final Bundle state = new Bundle();
112+
if (mMenuView != null) {
113+
SparseArray<Parcelable> hierarchy = new SparseArray<>();
114+
mMenuView.saveHierarchyState(hierarchy);
115+
state.putSparseParcelableArray(STATE_HIERARCHY, hierarchy);
116+
}
117+
return state;
118+
}
96119
return null;
97120
}
98121

99122
@Override
100-
public void onRestoreInstanceState(Parcelable state) {}
123+
public void onRestoreInstanceState(Parcelable parcelable) {
124+
if (parcelable instanceof Bundle) {
125+
Bundle state = (Bundle) parcelable;
126+
SparseArray<Parcelable> hierarchy = state.getSparseParcelableArray(STATE_HIERARCHY);
127+
if (hierarchy != null) {
128+
mMenuView.restoreHierarchyState(hierarchy);
129+
}
130+
}
131+
}
101132

102133
public void setUpdateSuspended(boolean updateSuspended) {
103134
mUpdateSuspended = updateSuspended;

lib/src/android/support/design/widget/BottomNavigationView.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import android.content.Context;
2020
import android.content.res.ColorStateList;
2121
import android.os.Build;
22+
import android.os.Bundle;
23+
import android.os.Parcel;
24+
import android.os.Parcelable;
2225
import android.support.annotation.DrawableRes;
2326
import android.support.annotation.NonNull;
2427
import android.support.annotation.Nullable;
@@ -27,6 +30,9 @@
2730
import android.support.design.internal.BottomNavigationMenuView;
2831
import android.support.design.internal.BottomNavigationPresenter;
2932
import android.support.v4.content.ContextCompat;
33+
import android.support.v4.os.ParcelableCompat;
34+
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
35+
import android.support.v4.view.AbsSavedState;
3036
import android.support.v4.view.ViewCompat;
3137
import android.support.v7.content.res.AppCompatResources;
3238
import android.support.v7.view.SupportMenuInflater;
@@ -85,6 +91,9 @@ public class BottomNavigationView extends FrameLayout {
8591
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
8692
private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
8793

94+
private static final int PRESENTER_BOTTOM_NAVIGATION_VIEW_ID = 1;
95+
private static final String STATE_TAB = "tab";
96+
8897
private final MenuBuilder mMenu;
8998
private final BottomNavigationMenuView mMenuView;
9099
private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
@@ -116,6 +125,7 @@ public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAtt
116125
mMenuView.setLayoutParams(params);
117126

118127
mPresenter.setBottomNavigationMenuView(mMenuView);
128+
mPresenter.setId(PRESENTER_BOTTOM_NAVIGATION_VIEW_ID);
119129
mMenuView.setPresenter(mPresenter);
120130
mMenu.addMenuPresenter(mPresenter);
121131
mPresenter.initForMenu(getContext(), mMenu);
@@ -329,4 +339,69 @@ private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
329339
baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor
330340
});
331341
}
342+
343+
@Override
344+
protected Parcelable onSaveInstanceState() {
345+
Parcelable superState = super.onSaveInstanceState();
346+
SavedState state = new SavedState(superState);
347+
Bundle bundle = new Bundle();
348+
for (int i = 0; i < mMenu.size(); i++) {
349+
MenuItem item = mMenu.getItem(i);
350+
if (item.isChecked()) {
351+
bundle.putInt(STATE_TAB, item.getItemId());
352+
}
353+
}
354+
state.bottomNavigationState = bundle;
355+
mMenu.savePresenterStates(state.bottomNavigationState);
356+
357+
return state;
358+
}
359+
360+
@Override
361+
protected void onRestoreInstanceState(Parcelable savedState) {
362+
if (!(savedState instanceof SavedState)) {
363+
super.onRestoreInstanceState(savedState);
364+
return;
365+
}
366+
SavedState state = (SavedState) savedState;
367+
super.onRestoreInstanceState(state.getSuperState());
368+
mMenu.restorePresenterStates(state.bottomNavigationState);
369+
mMenu.findItem(state.bottomNavigationState.getInt(STATE_TAB)).setChecked(true);
370+
}
371+
372+
/**
373+
* User interface state that is stored by BottomNavigationView for implementing onSaveInstanceState().
374+
*/
375+
public static class SavedState extends AbsSavedState {
376+
public Bundle bottomNavigationState;
377+
378+
public SavedState(Parcel in, ClassLoader loader) {
379+
super(in, loader);
380+
bottomNavigationState = in.readBundle(loader);
381+
}
382+
383+
public SavedState(Parcelable superState) {
384+
super(superState);
385+
}
386+
387+
@Override
388+
public void writeToParcel(@NonNull Parcel dest, int flags) {
389+
super.writeToParcel(dest, flags);
390+
dest.writeBundle(bottomNavigationState);
391+
}
392+
393+
public static final Parcelable.Creator<SavedState> CREATOR =
394+
ParcelableCompat.newCreator(
395+
new ParcelableCompatCreatorCallbacks<SavedState>() {
396+
@Override
397+
public SavedState createFromParcel(Parcel parcel, ClassLoader loader) {
398+
return new SavedState(parcel, loader);
399+
}
400+
401+
@Override
402+
public SavedState[] newArray(int size) {
403+
return new SavedState[size];
404+
}
405+
});
406+
}
332407
}

lib/tests/src/android/support/design/testutils/TestUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package android.support.design.testutils;
1818

19+
import android.app.Activity;
1920
import android.content.Context;
21+
import android.content.pm.ActivityInfo;
22+
import android.content.res.Configuration;
2023
import android.content.res.TypedArray;
2124
import android.graphics.Bitmap;
2225
import android.graphics.Canvas;
@@ -25,6 +28,8 @@
2528
import android.graphics.drawable.Drawable;
2629
import android.support.annotation.ColorInt;
2730
import android.support.annotation.NonNull;
31+
import android.support.test.InstrumentationRegistry;
32+
2833
import junit.framework.Assert;
2934

3035
public class TestUtils {
@@ -161,4 +166,14 @@ public static int getThemeAttrColor(Context context, final int attr) {
161166
}
162167
}
163168
}
169+
public static void rotateScreen(Activity activity) {
170+
Context context = InstrumentationRegistry.getTargetContext();
171+
int orientation
172+
= context.getResources().getConfiguration().orientation;
173+
174+
activity.setRequestedOrientation(
175+
(orientation == Configuration.ORIENTATION_PORTRAIT) ?
176+
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
177+
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
178+
}
164179
}

lib/tests/src/android/support/design/widget/BaseBottomNavigationViewTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import static android.support.design.testutils.BottomNavigationViewActions.setIconForMenuItem;
3737
import static android.support.design.testutils.BottomNavigationViewActions.setItemIconTintList;
38+
import static android.support.design.testutils.TestUtils.rotateScreen;
3839
import static android.support.test.espresso.Espresso.onView;
3940
import static android.support.test.espresso.action.ViewActions.click;
4041
import static android.support.test.espresso.assertion.ViewAssertions.matches;
@@ -229,6 +230,24 @@ public void testIconTinting() {
229230
.check(matches(TestUtilsMatchers.drawable(blueFill, allowedComponentVariance)));
230231
}
231232

233+
@Test
234+
public void testSelectedTabState() {
235+
236+
// check profile item
237+
onView(
238+
allOf(
239+
withId(R.id.destination_profile),
240+
isDescendantOfA(withId(R.id.bottom_navigation)),
241+
isDisplayed()))
242+
.perform(click());
243+
244+
// rotate screen to trigger configuration change
245+
rotateScreen(mActivityTestRule.getActivity());
246+
247+
// Confirm that the state was restored
248+
assertTrue(mBottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked());
249+
}
250+
232251
@UiThreadTest
233252
@Test
234253
@SmallTest

0 commit comments

Comments
 (0)