Fix BottomNavigationMenu item selection via Menu object
- Move the logic of handling which item is checked to MenuBuilder instead of
BottomNavigationMenu itself by making all the MenuItems exclusively checked.
- Fixes MenuBuilder#startDispatchingItemsChanged to call onItemsChanged
with a correct value of structureChanged. It now keeps track if structure
changes while dispatching is prevented.
- Added a button to focus the next bottom navigation item to
BottomNavigationViewUsage
Bug: 32336752
Test: Added a new test to BottomNavigationViewTest
Change-Id: Ief3ff356b501373aaabdd5f4990ecf6f9c506f05
diff --git a/design/src/android/support/design/internal/BottomNavigationItemView.java b/design/src/android/support/design/internal/BottomNavigationItemView.java
index ea22399..5403ba1 100644
--- a/design/src/android/support/design/internal/BottomNavigationItemView.java
+++ b/design/src/android/support/design/internal/BottomNavigationItemView.java
@@ -16,6 +16,8 @@
package android.support.design.internal;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -35,8 +37,6 @@
import android.widget.ImageView;
import android.widget.TextView;
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
/**
* @hide
*/
@@ -131,8 +131,6 @@
@Override
public void setChecked(boolean checked) {
- mItemData.setChecked(checked);
-
ViewCompat.setPivotX(mLargeLabel, mLargeLabel.getWidth() / 2);
ViewCompat.setPivotY(mLargeLabel, mLargeLabel.getBaseline());
ViewCompat.setPivotX(mSmallLabel, mSmallLabel.getWidth() / 2);
diff --git a/design/src/android/support/design/internal/BottomNavigationMenu.java b/design/src/android/support/design/internal/BottomNavigationMenu.java
index 8832097..9862175 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenu.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenu.java
@@ -16,14 +16,15 @@
package android.support.design.internal;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
import android.content.Context;
import android.support.annotation.RestrictTo;
import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.view.menu.MenuItemImpl;
import android.view.MenuItem;
import android.view.SubMenu;
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
/**
* @hide
*/
@@ -47,6 +48,12 @@
"Maximum number of items supported by BottomNavigationView is " + MAX_ITEM_COUNT
+ ". Limit can be checked with BottomNavigationView#getMaxItemCount()");
}
- return super.addInternal(group, id, categoryOrder, title);
+ stopDispatchingItemsChanged();
+ final MenuItem item = super.addInternal(group, id, categoryOrder, title);
+ if (item instanceof MenuItemImpl) {
+ ((MenuItemImpl) item).setExclusiveCheckable(true);
+ }
+ startDispatchingItemsChanged();
+ return item;
}
}
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
index e6ae08f..ba43b5b 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenuView.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -287,6 +287,9 @@
}
for (int i = 0; i < menuSize; i++) {
mPresenter.setUpdateSuspended(true);
+ if (mMenu.getItem(i).isChecked()) {
+ mActiveButton = i;
+ }
mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0);
mPresenter.setUpdateSuspended(false);
}
@@ -297,10 +300,7 @@
mAnimationHelper.beginDelayedTransition(this);
- mPresenter.setUpdateSuspended(true);
- mButtons[mActiveButton].setChecked(false);
- mButtons[newButton].setChecked(true);
- mPresenter.setUpdateSuspended(false);
+ mMenu.getItem(newButton).setChecked(true);
mActiveButton = newButton;
}
diff --git a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
index b716630..09a9c35 100644
--- a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
@@ -47,7 +47,6 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.ViewGroup;
import org.junit.Before;
import org.junit.Test;
@@ -212,4 +211,27 @@
onView(allOf(withId(R.id.icon), isDescendantOfA(withId(R.id.destination_people)))).check(
matches(TestUtilsMatchers.drawable(blueFill, allowedComponentVariance)));
}
+
+ @UiThreadTest
+ @Test
+ @SmallTest
+ public void testItemChecking() throws Throwable {
+ final Menu menu = mBottomNavigation.getMenu();
+ assertTrue(menu.getItem(0).isChecked());
+ checkAndVerifyExclusiveItem(menu, R.id.destination_home);
+ checkAndVerifyExclusiveItem(menu, R.id.destination_profile);
+ checkAndVerifyExclusiveItem(menu, R.id.destination_people);
+ }
+
+ private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable {
+ menu.findItem(id).setChecked(true);
+ for (int i = 0; i < menu.size(); i++) {
+ final MenuItem item = menu.getItem(i);
+ if (item.getItemId() == id) {
+ assertTrue(item.isChecked());
+ } else {
+ assertFalse(item.isChecked());
+ }
+ }
+ }
}
diff --git a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
index 3add119..b2a4d59 100644
--- a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
+++ b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
@@ -52,11 +52,18 @@
android:text="@string/bottomnavigation_tint"
android:layout_below="@+id/button_remove"/>
+ <Button
+ android:id="@+id/button_select_next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bottomnavigation_select_next"
+ android:layout_below="@+id/button_tint"/>
+
<TextView
android:id="@+id/selected_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@+id/button_tint"/>
+ android:layout_below="@+id/button_select_next"/>
</RelativeLayout>
</ScrollView>
<android.support.design.widget.BottomNavigationView
diff --git a/samples/SupportDesignDemos/res/values/strings.xml b/samples/SupportDesignDemos/res/values/strings.xml
index 95900ca..c257f1d 100644
--- a/samples/SupportDesignDemos/res/values/strings.xml
+++ b/samples/SupportDesignDemos/res/values/strings.xml
@@ -127,4 +127,5 @@
<string name="bottomnavigation_add">Add an item</string>
<string name="bottomnavigation_remove">Remove an item</string>
<string name="bottomnavigation_tint">Toggle tint</string>
+ <string name="bottomnavigation_select_next">Select next item</string>
</resources>
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
index 3442218..fa9b0b6 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
@@ -76,6 +76,20 @@
}
}
});
+ Button buttonNext = (Button) findViewById(R.id.button_select_next);
+ buttonNext.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final int menuSize = bottom.getMenu().size();
+ int currentlySelected = 0;
+ for (int i = 0; i < menuSize; i++) {
+ if (bottom.getMenu().getItem(i).isChecked()) {
+ currentlySelected = i;
+ }
+ }
+ bottom.getMenu().getItem((currentlySelected + 1) % menuSize).setChecked(true);
+ }
+ });
final TextView selectedItem = (TextView) findViewById(R.id.selected_item);
bottom.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
index 2ee07d8..cff87c5 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
@@ -16,6 +16,8 @@
package android.support.v7.view.menu;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -47,8 +49,6 @@
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
/**
* Implementation of the {@link android.support.v4.internal.view.SupportMenu} interface for creating a
* standard menu UI.
@@ -165,6 +165,8 @@
private boolean mItemsChangedWhileDispatchPrevented = false;
+ private boolean mStructureChangedWhileDispatchPrevented = false;
+
private boolean mOptionalIconsVisible = false;
private boolean mIsClosing = false;
@@ -582,6 +584,7 @@
clearHeader();
mPreventDispatchingItemsChanged = false;
mItemsChangedWhileDispatchPrevented = false;
+ mStructureChangedWhileDispatchPrevented = false;
onItemsChanged(true);
}
@@ -599,6 +602,7 @@
final int group = item.getGroupId();
final int N = mItems.size();
+ stopDispatchingItemsChanged();
for (int i = 0; i < N; i++) {
MenuItemImpl curItem = mItems.get(i);
if (curItem.getGroupId() == group) {
@@ -609,6 +613,7 @@
curItem.setCheckedInt(curItem == item);
}
}
+ startDispatchingItemsChanged();
}
@Override
@@ -1042,6 +1047,9 @@
dispatchPresenterUpdate(structureChanged);
} else {
mItemsChangedWhileDispatchPrevented = true;
+ if (structureChanged) {
+ mStructureChangedWhileDispatchPrevented = true;
+ }
}
}
@@ -1054,6 +1062,7 @@
if (!mPreventDispatchingItemsChanged) {
mPreventDispatchingItemsChanged = true;
mItemsChangedWhileDispatchPrevented = false;
+ mStructureChangedWhileDispatchPrevented = false;
}
}
@@ -1062,7 +1071,7 @@
if (mItemsChangedWhileDispatchPrevented) {
mItemsChangedWhileDispatchPrevented = false;
- onItemsChanged(true);
+ onItemsChanged(mStructureChangedWhileDispatchPrevented);
}
}