Set initially selected item index/id in BottomNavigationView - android

I have implemented BottomNavigationView and have no idea how to set selection index or MenuItem id (in my case, middle item should be selected by default).
I'm afraid there's no such possibility for now as far as it's too raw yet, but anyways any help will be appreciated. Thanks!

Set the selected menu item ID using setSelectedItemId:
bottomNavigationView.setSelectedItemId(R.id.item_id);
This method started being available from Android Support Library 25.3.0.

The only solution that worked for me is:
View view = bottomNavigationView.findViewById(R.id.menu_action_dashboard);
view.performClick();
Simply performing click does the trick. Hope we'll get extra methods/properties in future releases.
UPD:
As user5968678 mentioned, a new method was added since Android Support Library v25.3.0:
bottomNavigationView.setSelectedItemId(R.id.item_id);
so use this instead :)

I think this solution my be slightly more elegant than accepted answer:
bottomNavigationView.getMenu().getItem(menuItemIndex).setChecked(true)
where menuItemIndex is index of the selected element.

Here's what the documentation says about it:
Menu items can also be used for programmatically selecting which destination is currently active. It can be done using MenuItem#setChecked(true)
As an alternative to what Jan posted, you can also find the item by id:
Menu menu = findViewById(R.id.navigation).getMenu();
menu.findItem(R.id.navigation_home).setChecked(true);
Also, in general, I can recommend calling .callOnClick() instead of .performClick().

If you're using listener, like default implementation in android studio, try this:
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
Integer indexItem = 4;
navigation.getMenu().getItem(indexItem).setChecked(true);
mOnNavigationItemSelectedListener.onNavigationItemSelected(navigation.getMenu().getItem(indexItem));

I believe the question in this context is being viewed in different contexts basing on answers here.
According to assessment, whats required is ability to focus on specific BottomNavigationView item (definitely in new class holding different fragments).
Now, you could have BottomNavigationView OR Buttons or Anything clickable to launch new activity on intent : -
i.e
Intent intent = new Intent(getActivity(), New_Activity.class);
intent.putExtra("EXTRA_PAGE, 1);
startActivityForResult(intent, 30);
Then
-in our New_Activity, we receive the intent-
Intent intent = getIntent();
int page = intent.getExtras().getInt("EXTRA_PAGE);
We then loop over the page variable to find the number/Index for which the current BottomNavigationView is reflecting , THEN we set our focus menu item (assuming your BottomNavigationView has Menu Item for its display)
if(page == 1) {
currentselect = new Pending();
bottomNavigationView.getMenu().getItem(0).setChecked(true);
}
This answers the question above. The rest of Fragment switch is handled well by number of posts above by invoking :
bottomNavigationView.setOnNavigationItemSelectedListener(navListener);
Then something like :
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener(){
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFrag = null;
switch (item.getItemId()) {
case R.id.pending:
selectedFrag = new Pending();
break;
case R.id.onTransmit:
selectedFrag = new inTransmit();
break;
case R.id.complete:
selectedFrag = new Complete();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.select_field, selectedFrag).commit();
return true;
}
};
NOTE:
Using BottomNavigationView and ContentFrameLayout is soo economical and will slash down your code to over 50 % unlike using likes of ViewPager and Tablayout

Kotlin Code for initial selected item in bottomnavigation.BottomNavigationView :
bottom_navigation_view.selectedItemId = R.id.navigation_item_messages

Stop using Reflection! It is bad!
Well, while the support library does not gives us the option to select the item from the BottomNavigationView to be displayed on the first time when it is visible, we have two possibilities:
First, using loop:
private void setupBottomNavigationView() {
// Get the menu from our navigationBottomView.
Menu bottomNavigationViewMenu = bottomNavigationView.getMenu();
// Uncheck the first menu item (the default item which is always checked by the support library is at position 0).
bottomNavigationMenu.findItem(R.id.action_one).setChecked(false);
// Check the wished first menu item to be shown to the user.
bottomNavigationMenu.findItem(R.id.action_two).setChecked(true);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Menu bottomNavigationMenu = bottomNavigationView.getMenu();
for (int i = 0; i < bottomNavigationMenu.size(); i++) {
if (item.getItemId() != bottomNavigationMenu.getItem(i).getItemId()) {
bottomNavigationMenu.getItem(i).setChecked(false);
}
}
switch (item.getItemId()) {
case R.id.action_one :
replaceFragment(new OneFragment());
break;
case R.id.action_two :
replaceFragment(new TwoFragment());
break;
case R.id.action_three :
replaceFragment(new ThreeFragment());
break;
}
return false;
}
});
}
Second, without loop but with a class variable (because the logic is done from within inner class) :
private void setupBottomNavigationView() {
// Get the menu from our navigationBottomView.
Menu bottomNavigationViewMenu = bottomNavigationView.getMenu();
// Uncheck the first menu item (the default item which is always checked by the support library is at position 0).
bottomNavigationViewMenu.findItem(R.id.action_one).setChecked(false);
// Check the wished first menu item to be shown to the user. Also store that menu item on a variable to control when a menu item must be unchecked.
mActiveBottomNavigationViewMenuItem = bottomNavigationViewMenu.findItem(R.id.action_two).setChecked(true);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem selectedMenuItem) {
switch (selectedMenuItem.getItemId()) {
case R.id.action_one :
replaceFragment(new OneFragment());
break;
case R.id.action_two :
replaceFragment(new TwoFragment());
break;
case R.id.action_three :
replaceFragment(new ThreeFragment());
break;
}
if (selectedMenuItem != mActiveBottomNavigationViewMenuItem){
mActiveBottomNavigationViewMenuItem.setChecked(false);
mActiveBottomNavigationViewMenuItem = selectedMenuItem;
}
return false;
}
});
}
private MenuItem mActiveBottomNavigationViewMenuItem;
When the setupBottomNavigationView() method is executed? In Activity onCreate() method, take a look:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ...
setupBottomNavigationView();
}
Simple and without extensive code.
Hope it helps!

Implement BottomNavigationView.OnNavigationItemSelectedListener and set the selectedItemId at initialisation.
this.bottomNavigationView.setOnNavigationItemSelectedListener {
val targetFragment = when (menuItem.itemId) {
R.id.action_home -> {
HomeFragment()
}
R.id.action_post -> {
PostFragment()
}
R.id.action_settings -> {
SettingsFragment()
}
else -> null
}
targetFragment?.let {
this.activity?.supportFragmentManager?.transaction {
replace(R.id.containerLayout, it, it.javaClass.simpleName)
}
}
true
}
this.bottomNavigationView.selectedItemId = R.id.action_home

You can achieve this effect by adding a dummy menu item
as the first item in the menu you assign to BottomNavigationView
and setting this item to be invisible. Then in
your reselect handler, call setSelectedItem(dummy) to the dummy
item. As a result, the visible menu items will behave as
"virtual" toggles. Took me more work than it should have to
figure this one out. You will have to do it like this in order
that the setSelectItem be called after the reselect is complete:
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
anyView.post(new Runnable() {
#Override
public void run() {
navigationView.setSelectedItemId(R.id.action_dummy);
}
}
});

You can extend BottomNavigationView and use reflection to invoke private methods.
public class SelectableBottomNavigationView extends BottomNavigationView {
public SelectableBottomNavigationView(Context context) {
super(context);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setSelected(int index) {
try {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) getField(BottomNavigationView.class, "mMenuView");
OnNavigationItemSelectedListener listener = (OnNavigationItemSelectedListener) getField(BottomNavigationView.class, "mListener");
try {
Method method = menuView.getClass().getDeclaredMethod("activateNewButton", Integer.TYPE);
method.setAccessible(true);
// activate item.
method.invoke(menuView, index);
if (listener != null) {
// trigger item select event.
listener.onNavigationItemSelected(getMenu().getItem(index));
}
} catch (SecurityException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
private Object getField(Class clazz, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(this);
}
}

To set initial page in navigation - Jetpack Navigation
set property "startDestination" inside in the navigation graph, value should be the id given to the fragment.
<navigation
app:startDestination="#+id/navigation_movies"
...
>
<!-- default page -->
<fragment
android:id="#+id/navigation_movies"
...`enter code here`
/>
<fragment
android:id="#+id/navigation_tv_shows"
...
/>
</fragment>

Related

Android Navigation component: Is there any way I can change selected tab of BottomNavigation programmatically [duplicate]

I am using the new android.support.design.widget.BottomNavigationView from the support library.
How can I set the current selection from code? I realized, that the selection is changed back to the first item, after rotating the screen. Of course it would also help, if someone could tell me, how to "save" the current state of the BottomNavigationView in the onPause function and how to restore it in onResume.
Thanks!
From API 25.3.0 it was introduced the method setSelectedItemId(int id) which lets you mark an item as selected as if it was tapped.
From docs:
Set the selected menu item ID. This behaves the same as tapping on an item.
Code example:
BottomNavigationView bottomNavigationView;
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(myNavigationItemListener);
bottomNavigationView.setSelectedItemId(R.id.my_menu_item_id);
IMPORTANT
You MUST have already added all items to the menu (in case you do this programmatically) and set the Listener before calling setSelectedItemId(I believe you want the code in your listener to run when you call this method). If you call setSelectedItemId before adding the menu items and setting the listener nothing will happen.
To programmatically click on the BottomNavigationBar item you need use:
View view = bottomNavigationView.findViewById(R.id.menu_action_item);
view.performClick();
This arranges all the items with their labels correctly.
For those, who still use SupportLibrary < 25.3.0
I'm not sure whether this is a complete answer to this question, but my problem was very similar - I had to process back button press and bring user to previous tab where he was. So, maybe my solution will be useful for somebody:
private void updateNavigationBarState(int actionId){
Menu menu = bottomNavigationView.getMenu();
for (int i = 0, size = menu.size(); i < size; i++) {
MenuItem item = menu.getItem(i);
item.setChecked(item.getItemId() == actionId);
}
}
Please, keep in mind that if user press other navigation tab BottomNavigationView won't clear currently selected item, so you need to call this method in your onNavigationItemSelected after processing of navigation action:
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.some_id_1:
// process action
break;
case R.id.some_id_2:
// process action
break;
...
default:
return false;
}
updateNavigationBarState(item.getItemId());
return true;
}
Regarding the saving of instance state I think you could play with same action id of navigation view and find suitable solution.
bottomNavigationView.setSelectedItemId(R.id.action_item1);
where action_item1 is menu item ID.
Use this to set selected bottom navigation menu item by menu id
MenuItem item = mBottomNavView.getMenu().findItem(menu_id);
item.setChecked(true);
use
bottomNavigationView.getMenu().getItem(POSITION).setChecked(true);
It is now possible since 25.3.0 version to call setSelectedItemId() \o/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bottomNavigationView.setOnNavigationItemSelectedListener(this);
Menu menu = bottomNavigationView.getMenu();
this.onNavigationItemSelected(menu.findItem(R.id.action_favorites));
}
Add android:enabled="true" to BottomNavigationMenu Items.
And then set bottomNavigationView.setOnNavigationItemSelectedListener(mListener) and
set it as selected by doing bottomNavigationView.selectedItemId = R.id.your_menu_id
You can try the performClick method :
View view = bottomNavigationView.findViewById(R.id.YOUR_ACTION);
view.performClick();
Edit
From API 25.3.0 it was introduced the method setSelectedItemId(int id) which lets you mark an item as selected as if it was tapped.
navigationView.getMenu().findItem(R.id.navigation_id).setChecked(true);
This will probably be added in coming updates. But in the meantime, to accomplish this you can use reflection.
Create a custom view extending from BottomNavigationView and access some of its fields.
public class SelectableBottomNavigationView extends BottomNavigationView {
public SelectableBottomNavigationView(Context context) {
super(context);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setSelected(int index) {
try {
Field f = BottomNavigationView.class.getDeclaredField("mMenuView");
f.setAccessible(true);
BottomNavigationMenuView menuView = (BottomNavigationMenuView) f.get(this);
try {
Method method = menuView.getClass().getDeclaredMethod("activateNewButton", Integer.TYPE);
method.setAccessible(true);
method.invoke(menuView, index);
} catch (SecurityException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
And then use it in your xml layout file.
<com.your.app.SelectableBottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="#color/primary"
app:itemIconTint="#drawable/nav_item_color_state"
app:itemTextColor="#drawable/nav_item_color_state"
app:menu="#menu/bottom_navigation_menu"/>
I you don't want to modify your code.
If so, I recommended you to try BottomNavigationViewEx。
You just need replace call a method setCurrentItem(index); and getCurrentItem()。
Just adding another way to perform a selection programatically - this is probably what was the intention in the first place or maybe this was added later on.
Menu bottomNavigationMenu = myBottomNavigationMenu.getMenu();
bottomNavigationMenu.performIdentifierAction(selected_menu_item_id, 0);
The performIdentifierAction takes a Menu item id and a flag.
See the documentation for more info.
Seems to be fixed in SupportLibrary 25.1.0 :)
Edit: It seems to be fixed, that the state of the selection is saved, when rotating the screen.
Above API 25 you can use setSelectedItemId(menu_item_id)
but under API 25 you must do differently,
user Menu to get handle and then setChecked to Checked specific item
I made a bug to Google about the fact that there's no reliable way to select the page on a BottomNavigationView: https://code.google.com/p/android/issues/detail?id=233697
NavigationView apparently had a similar issue, which they fixed by adding a new setCheckedItem() method.
I hope this helps
//Setting default selected menu item and fragment
bottomNavigationView.setSelectedItemId(R.id.action_home);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new HomeFragment()).commit();
It is more of determining the default fragment loaded at the same time with the corresponding bottom navigation menu item.
You can include the same in your OnResume callbacks
To change the Tab, this code works!
activity?.supportFragmentManager?.beginTransaction().also { fragmentTransaction ->
fragmentTransaction?.replace(R.id.base_frame, YourFragment())?.commit()
}
val bottomNavigationView: BottomNavigationView = activity?.findViewById(R.id.bottomNavigationView) as BottomNavigationView
bottomNavigationView.menu.findItem(R.id.navigation_item).isChecked = true
Reflection is bad idea.
Head to this gist. There is a method that performs the selection but also invokes the callback:
#CallSuper
public void setSelectedItem(int position) {
if (position >= getMenu().size() || position < 0) return;
View menuItemView = getMenuItemView(position);
if (menuItemView == null) return;
MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();
itemData.setChecked(true);
boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
menuItemView.setSoundEffectsEnabled(false);
menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
menuItemView.performClick();
menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
menuItemView.setSoundEffectsEnabled(true);
mLastSelection = position;
}
private void setSelectedItem(int actionId) {
Menu menu = viewBottom.getMenu();
for (int i = 0, size = menu.size(); i < size; i++) {
MenuItem menuItem = menu.getItem(i);
((MenuItemImpl) menuItem).setExclusiveCheckable(false);
menuItem.setChecked(menuItem.getItemId() == actionId);
((MenuItemImpl) menuItem).setExclusiveCheckable(true);
}
}
The only 'minus' of the solution is using MenuItemImpl, which is 'internal' to library (though public).
IF YOU NEED TO DYNAMICALLY PASS FRAGMENT ARGUMENTS DO THIS
There are plenty of (mostly repeated or outdated) answers here but none of them handles a very common need: dynamically passing different arguments to the Fragment loaded into a tab.
You can't dynamically pass different arguments to the loaded Fragment by using setSelectedItemId(R.id.second_tab), which ends up calling the static OnNavigationItemSelectedListener. To overcome this limitation I've ended up doing this in my MainActivity that contains the tabs:
fun loadArticleTab(articleId: String) {
bottomNavigationView.menu.findItem(R.id.tab_article).isChecked = true // use setChecked() in Java
supportFragmentManager
.beginTransaction()
.replace(R.id.main_fragment_container, ArticleFragment.newInstance(articleId))
.commit()
}
The ArticleFragment.newInstance() method is implemented as usual:
private const val ARG_ARTICLE_ID = "ARG_ARTICLE_ID"
class ArticleFragment : Fragment() {
companion object {
/**
* #return An [ArticleFragment] that shows the article with the given ID.
*/
fun newInstance(articleId: String): ArticleFragment {
val args = Bundle()
args.putString(ARG_ARTICLE_ID, day)
val fragment = ArticleFragment()
fragment.arguments = args
return fragment
}
}
}
This method work for me.
private fun selectBottomNavigationViewMenuItem(bottomNavigationView : BottomNavigationView,#IdRes menuItemId: Int) {
bottomNavigationView.setOnNavigationItemSelectedListener(null)
bottomNavigationView.selectedItemId = menuItemId
bottomNavigationView.setOnNavigationItemSelectedListener(this)
}
Example
override fun onBackPressed() {
replaceFragment(HomeFragment())
selectBottomNavigationViewMenuItem(navView, R.id.navigation_home)
}
private fun replaceFragment(fragment: Fragment) {
val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frame_container, fragment)
transaction.commit()
}

Opening bottom sheet when clicked on the overflow menu on a fragment

I am trying to implement behavior of opening bottom sheet when clicked on overflow menu. ex: expected behavior
I may do this on an activity using onMenuOpened as suggested here,
But I want to do this on fragment.
How to achieve this behavior on a fragment?
I am using single activity pattern and navigation architecture component.
Create a interface which will be implemented by your Fragment's
ex:
public interface OnMenuOpenListener(){
boolean onMenuOpened();
}
public class MyFragment extends Fragment implements OnMenuOpenListener{
#Override
public boolean onMenuOpened(){
//open bottom sheet here
}
}
public class MyActivity extends Activity{
#Override
public boolean onMenuOpened(int featureId, Menu menu) {
if(featureId == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null){
//overflow menu clicked, put code here...
// As you are using navigation component
Fragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host);
//MyFragment
Fragment fragment=navHostFragment.getChildFragmentManager().getFragments().get(0);
if(fragment instanceof OnMenuOpenListener){
((OnMenuOpenListener)fragment).onMenuOpened()
return false;
}
}
return super.onMenuOpened(featureId, menu);
}
}
As Support Action Bar is attached to Activity All the event's are captured by Activity all you need to do is get the Fragment which need's the event and trigger the call using a call back.If you return false onMenuOpened will not open the overflow menu and will trigger bottom sheet menu from your fragment.
P.S- I have not written the code in Editor so might have some error's but you must have got the idea.
Reference:
https://stackoverflow.com/a/51732378/7972699
As discussed here opening bottom sheet when clicked on the overflow menu is bad UX.
Why?
Quoting from the post
Because user have to reach the top of the screen to click the oveflow
menu, then go back to the bottom to click desired action which is on
the bottom sheet.
-
According to Fitt's Law - The time to acquire a target is a function
of the distance to and size of the target. I agree that I think
distance between the menu and the bottom sheet is substantial. This
solution allows placing a lot options in one place.
-
it also doesn't match the user expectation since people are used to
the overflow menu opening in a different manner.
If you have a top action bar, use usual context menu. If you have a bottom action bar you may use bottom sheet.
**You can try the following steps to open bottom sheet dialog:**
1. Just make a function inside Activity where the fragment is replace
public Fragment getCurrentFragment() {
return getSupportFragmentManager().findFragmentById(R.id.frameContainer);
}
Fragment fragment = getCurrentFragment();
if (fragment != null) {
if (fragment instanceof RequiredFragment) {
RequiredFragment.openBottumSheetDialog();
}
}
2. In Side RequiredFragment get your function from activity:
private BottomSheetDialog mBottomSheetDialogFragment;
private void showBottomSheetFilter() {
if (mBottomSheetDialogFragment == null) {
mBottomSheetDialogFragment = mBottomSheetDialogFragment.newInstance(feedSection);
mBottomSheetDialogFragment.setCallBackListener(new OnFeedsTypeSelectedListener() {
#Override
public void onFeedsTypeSelected(int contentType) {
filterByContentTypeId(contentType);
}
}
mBottomSheetDialogFragment.show(getChildFragmentManager(),
mBottomSheetDialogFragment.getTag());
}
3. Create a BottomSheetDialog Dialog fragment.
public class BottomSheetDialog extends BottomSheetDialogFragment {
private String[] feedsFilter;
private ListView listView;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
feedsFilter = getResources().getStringArray(R.array.ideas_filter);
}
#Override
public void setupDialog(final Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.dialog_idea_filter_bottom_sheet, null);
dialog.setContentView(contentView);
listView = (ListView) contentView.findViewById(R.id.listView);
ArrayAdapter < String > adapter = new ArrayAdapter < String > (getActivity(),
android.R.layout.simple_list_item_1, feedsFilter);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView < ? > parent, View view,
int position, long id) {
if (onFeedsTypeSelected != null) {
onIdeaTypeSelectedListenonFeedsTypeSelecteder.onFeedsTypeSelected(feedsFilter[position]);
}
dismiss();
}
});
}
public void setCallBackListener(onFeedsTypeSelected SelectedListener onFeedsTypeSelected) {
this.onIdeaTypeSelectedLionFeedsTypeSelectedstener = onFeedsTypeSelected;
}
}

Set selected item in Android BottomNavigationView

I am using the new android.support.design.widget.BottomNavigationView from the support library.
How can I set the current selection from code? I realized, that the selection is changed back to the first item, after rotating the screen. Of course it would also help, if someone could tell me, how to "save" the current state of the BottomNavigationView in the onPause function and how to restore it in onResume.
Thanks!
From API 25.3.0 it was introduced the method setSelectedItemId(int id) which lets you mark an item as selected as if it was tapped.
From docs:
Set the selected menu item ID. This behaves the same as tapping on an item.
Code example:
BottomNavigationView bottomNavigationView;
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(myNavigationItemListener);
bottomNavigationView.setSelectedItemId(R.id.my_menu_item_id);
IMPORTANT
You MUST have already added all items to the menu (in case you do this programmatically) and set the Listener before calling setSelectedItemId(I believe you want the code in your listener to run when you call this method). If you call setSelectedItemId before adding the menu items and setting the listener nothing will happen.
To programmatically click on the BottomNavigationBar item you need use:
View view = bottomNavigationView.findViewById(R.id.menu_action_item);
view.performClick();
This arranges all the items with their labels correctly.
For those, who still use SupportLibrary < 25.3.0
I'm not sure whether this is a complete answer to this question, but my problem was very similar - I had to process back button press and bring user to previous tab where he was. So, maybe my solution will be useful for somebody:
private void updateNavigationBarState(int actionId){
Menu menu = bottomNavigationView.getMenu();
for (int i = 0, size = menu.size(); i < size; i++) {
MenuItem item = menu.getItem(i);
item.setChecked(item.getItemId() == actionId);
}
}
Please, keep in mind that if user press other navigation tab BottomNavigationView won't clear currently selected item, so you need to call this method in your onNavigationItemSelected after processing of navigation action:
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.some_id_1:
// process action
break;
case R.id.some_id_2:
// process action
break;
...
default:
return false;
}
updateNavigationBarState(item.getItemId());
return true;
}
Regarding the saving of instance state I think you could play with same action id of navigation view and find suitable solution.
bottomNavigationView.setSelectedItemId(R.id.action_item1);
where action_item1 is menu item ID.
Use this to set selected bottom navigation menu item by menu id
MenuItem item = mBottomNavView.getMenu().findItem(menu_id);
item.setChecked(true);
use
bottomNavigationView.getMenu().getItem(POSITION).setChecked(true);
It is now possible since 25.3.0 version to call setSelectedItemId() \o/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bottomNavigationView.setOnNavigationItemSelectedListener(this);
Menu menu = bottomNavigationView.getMenu();
this.onNavigationItemSelected(menu.findItem(R.id.action_favorites));
}
Add android:enabled="true" to BottomNavigationMenu Items.
And then set bottomNavigationView.setOnNavigationItemSelectedListener(mListener) and
set it as selected by doing bottomNavigationView.selectedItemId = R.id.your_menu_id
You can try the performClick method :
View view = bottomNavigationView.findViewById(R.id.YOUR_ACTION);
view.performClick();
Edit
From API 25.3.0 it was introduced the method setSelectedItemId(int id) which lets you mark an item as selected as if it was tapped.
navigationView.getMenu().findItem(R.id.navigation_id).setChecked(true);
This will probably be added in coming updates. But in the meantime, to accomplish this you can use reflection.
Create a custom view extending from BottomNavigationView and access some of its fields.
public class SelectableBottomNavigationView extends BottomNavigationView {
public SelectableBottomNavigationView(Context context) {
super(context);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelectableBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setSelected(int index) {
try {
Field f = BottomNavigationView.class.getDeclaredField("mMenuView");
f.setAccessible(true);
BottomNavigationMenuView menuView = (BottomNavigationMenuView) f.get(this);
try {
Method method = menuView.getClass().getDeclaredMethod("activateNewButton", Integer.TYPE);
method.setAccessible(true);
method.invoke(menuView, index);
} catch (SecurityException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
And then use it in your xml layout file.
<com.your.app.SelectableBottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="#color/primary"
app:itemIconTint="#drawable/nav_item_color_state"
app:itemTextColor="#drawable/nav_item_color_state"
app:menu="#menu/bottom_navigation_menu"/>
I you don't want to modify your code.
If so, I recommended you to try BottomNavigationViewEx。
You just need replace call a method setCurrentItem(index); and getCurrentItem()。
Just adding another way to perform a selection programatically - this is probably what was the intention in the first place or maybe this was added later on.
Menu bottomNavigationMenu = myBottomNavigationMenu.getMenu();
bottomNavigationMenu.performIdentifierAction(selected_menu_item_id, 0);
The performIdentifierAction takes a Menu item id and a flag.
See the documentation for more info.
Seems to be fixed in SupportLibrary 25.1.0 :)
Edit: It seems to be fixed, that the state of the selection is saved, when rotating the screen.
Above API 25 you can use setSelectedItemId(menu_item_id)
but under API 25 you must do differently,
user Menu to get handle and then setChecked to Checked specific item
I made a bug to Google about the fact that there's no reliable way to select the page on a BottomNavigationView: https://code.google.com/p/android/issues/detail?id=233697
NavigationView apparently had a similar issue, which they fixed by adding a new setCheckedItem() method.
I hope this helps
//Setting default selected menu item and fragment
bottomNavigationView.setSelectedItemId(R.id.action_home);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new HomeFragment()).commit();
It is more of determining the default fragment loaded at the same time with the corresponding bottom navigation menu item.
You can include the same in your OnResume callbacks
To change the Tab, this code works!
activity?.supportFragmentManager?.beginTransaction().also { fragmentTransaction ->
fragmentTransaction?.replace(R.id.base_frame, YourFragment())?.commit()
}
val bottomNavigationView: BottomNavigationView = activity?.findViewById(R.id.bottomNavigationView) as BottomNavigationView
bottomNavigationView.menu.findItem(R.id.navigation_item).isChecked = true
Reflection is bad idea.
Head to this gist. There is a method that performs the selection but also invokes the callback:
#CallSuper
public void setSelectedItem(int position) {
if (position >= getMenu().size() || position < 0) return;
View menuItemView = getMenuItemView(position);
if (menuItemView == null) return;
MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();
itemData.setChecked(true);
boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
menuItemView.setSoundEffectsEnabled(false);
menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
menuItemView.performClick();
menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
menuItemView.setSoundEffectsEnabled(true);
mLastSelection = position;
}
private void setSelectedItem(int actionId) {
Menu menu = viewBottom.getMenu();
for (int i = 0, size = menu.size(); i < size; i++) {
MenuItem menuItem = menu.getItem(i);
((MenuItemImpl) menuItem).setExclusiveCheckable(false);
menuItem.setChecked(menuItem.getItemId() == actionId);
((MenuItemImpl) menuItem).setExclusiveCheckable(true);
}
}
The only 'minus' of the solution is using MenuItemImpl, which is 'internal' to library (though public).
IF YOU NEED TO DYNAMICALLY PASS FRAGMENT ARGUMENTS DO THIS
There are plenty of (mostly repeated or outdated) answers here but none of them handles a very common need: dynamically passing different arguments to the Fragment loaded into a tab.
You can't dynamically pass different arguments to the loaded Fragment by using setSelectedItemId(R.id.second_tab), which ends up calling the static OnNavigationItemSelectedListener. To overcome this limitation I've ended up doing this in my MainActivity that contains the tabs:
fun loadArticleTab(articleId: String) {
bottomNavigationView.menu.findItem(R.id.tab_article).isChecked = true // use setChecked() in Java
supportFragmentManager
.beginTransaction()
.replace(R.id.main_fragment_container, ArticleFragment.newInstance(articleId))
.commit()
}
The ArticleFragment.newInstance() method is implemented as usual:
private const val ARG_ARTICLE_ID = "ARG_ARTICLE_ID"
class ArticleFragment : Fragment() {
companion object {
/**
* #return An [ArticleFragment] that shows the article with the given ID.
*/
fun newInstance(articleId: String): ArticleFragment {
val args = Bundle()
args.putString(ARG_ARTICLE_ID, day)
val fragment = ArticleFragment()
fragment.arguments = args
return fragment
}
}
}
This method work for me.
private fun selectBottomNavigationViewMenuItem(bottomNavigationView : BottomNavigationView,#IdRes menuItemId: Int) {
bottomNavigationView.setOnNavigationItemSelectedListener(null)
bottomNavigationView.selectedItemId = menuItemId
bottomNavigationView.setOnNavigationItemSelectedListener(this)
}
Example
override fun onBackPressed() {
replaceFragment(HomeFragment())
selectBottomNavigationViewMenuItem(navView, R.id.navigation_home)
}
private fun replaceFragment(fragment: Fragment) {
val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frame_container, fragment)
transaction.commit()
}

Android: Changing visibility on menu items with changing fragment

I want to change the visibility of menu items of a fragment activity (abs) when ever I change the fragment in the activity. The fragments are SherlockListFragments.
The menu items I want to show/hide are spinners I create on menu creation:
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
MenuInflater inflater = getSupportMenuInflater();
IcsSpinner herbSortSpinner = new IcsSpinner(this, null, R.attr.actionDropDownStyle);
SpinnerAdapter herbSortSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.herb_sort_elements, android.R.layout.simple_spinner_dropdown_item);
herbSortSpinner.setAdapter(herbSortSpinnerAdapter);
herbSortSpinner.setOnItemSelectedListener(this);
herbSortSpinner.setId(HERB_SPINNER_ID);
menu.add(R.string.menu_sort).setActionView(herbSortSpinner).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
IcsSpinner noteSortSpinner = new IcsSpinner(this, null, R.attr.actionDropDownStyle);
SpinnerAdapter noteSortSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.note_sort_elements, android.R.layout.simple_spinner_dropdown_item);
noteSortSpinner.setAdapter(noteSortSpinnerAdapter);
noteSortSpinner.setOnItemSelectedListener(this);
noteSortSpinner.setId(NOTE_SPINNER_ID);
menu.add(R.string.menu_sort).setActionView(noteSortSpinner).setVisible(false).setEnabled(false).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
inflater.inflate(R.menu.activity_lexicon, menu);
menuHolder.setMenu(menu);
return true;
}
My logic for switching the fragments is:
public boolean onNavigationItemSelected(int position, long itemId) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch(position) {
case 0:
List<Herb> herbList = findHerbsByIntent(getHerbLocale());
HerbListFragment herbListFragment = new HerbListFragment();
herbListFragment.setListAdapter(new HerbListAdapter(this, getHerbLocale(), herbList));
fragmentTransaction.replace(R.id.fragment_container, herbListFragment, HERB_LIST_FRAGMENT_TAG);
//menuHolder.showHerbsSpinner();
break;
case 1:
SymptomListFragment symptomListFragment = new SymptomListFragment();
symptomListFragment.setListAdapter(new SymptomListAdapter(this, getDefaultSymptomLocale()));
fragmentTransaction.replace(R.id.fragment_container, symptomListFragment, SYMPTOM_LIST_FRAGMENT_TAG);
//menuHolder.showHerbsSpinner();
break;
case 2:
NoteRepository noteRepository = new NoteRepository(this);
List<Note> notes = noteRepository.getNoticables(ReferedType.HERB);
NoteListFragment noteListFragment = new NoteListFragment();
noteListFragment.setListAdapter(new NoteListAdapter(this, getHerbLocale(), notes));
fragmentTransaction.replace(R.id.fragment_container, noteListFragment, NOTE_LIST_FRAGMENT_TAG);
//menuHolder.showNotesSpinner();
break;
case 3:
FavoriteRepository favoriteRepository = new FavoriteRepository(this);
Set<Integer> favoriteHerbs = favoriteRepository.getFavorables(ReferedType.HERB);
List<Herb> favoriteHerbList = herbRepository.getHerbsByIds(favoriteHerbs, getHerbLocale());
FavoriteHerbListFragment favoriteHerbListFragment = new FavoriteHerbListFragment();
favoriteHerbListFragment.setListAdapter(new HerbListAdapter(this, getHerbLocale(), favoriteHerbList));
fragmentTransaction.replace(R.id.fragment_container, favoriteHerbListFragment, HERB_LIST_FRAGMENT_TAG);
//menuHolder.showHerbsSpinner();
break;
}
fragmentTransaction.commit();
return true;
}
My first idea was to hold the menu object in a holder class and manipulate it there whenever I switch the fragment (in every case statement above).
static class MenuHolder {
private Menu mMenu;
void setMenu(Menu menu) {
mMenu = menu;
}
void showNotesSpinner() {
if (mMenu != null) {
mMenu.findItem(HERB_SPINNER_ID).setVisible(false).setEnabled(false);
mMenu.findItem(NOTE_SPINNER_ID).setVisible(true).setEnabled(true);
}
}
void showHerbsSpinner() {
if (mMenu != null) {
mMenu.findItem(NOTE_SPINNER_ID).setVisible(false).setEnabled(false);
mMenu.findItem(HERB_SPINNER_ID).setVisible(true).setEnabled(true);
}
}
}
My problem is that there is no menu item with the given ID which are activity local constants. I get an NPE here. Does anybody have an idea how I can fix that? Is there a better way to change the menu on switching fragments?
Best regards
Carsten
Is there a better way to change the menu on switching fragments?
May be yes :).
In your fragment onCreate, add this :
setHasOptionsMenu (true);
The doc :
Report that this fragment would like to participate in populating the options menu by receiving a call to onCreateOptionsMenu(Menu, MenuInflater) and related methods.
Override onPrepareOptionMenu method in your fragment class.
Prepare the Screen's standard options menu to be displayed. This is called right before the menu is shown, every time it is shown. You can use this method to efficiently enable/disable items or otherwise dynamically modify the contents.
Then, in this method, try to find your menu items by ID, and make them visible/unvisible, enabled/disabled, like this :
#Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(HERB_SPINNER_ID).setVisible(false).setEnabled(false);
}
Read More
In my case, I have 2 fragments that has a different menu item.
On my MainActivity:
FragmentA fragmentA = new FragmentA();
fragmentA.setTargetFragment(fragmentA, 0);
FragmentB fragmentB = new FragmentB();
fragmentB.setTargetFragment(fragmentB, 1);
and FragmentA and FragmentB has:
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(hidden) {
getTargetFragment().setMenuVisibility(false);
}else{
getTargetFragment().setMenuVisibility(true);
}
}
this will let menu from fragment A be visible when B is leaving. If it’s going back from B to A old menus from A can be visible.
Reference: here

Deciding the menu's action dynamically

I add menu's in my application dynamically depending on the values i get from the back-end in the onPrepareOptionsMenu(Menu menu). Now i would want to decide the action that is to be performed for the menu added dynamically (as i do not know the action to be performed beforehand) depending on certain values that came associated with the menu from the back-end. How do i achieve this. Kindly provide me some ideas on this. Thanks in advance.
Look into onCreateOptionsMenu and onPrepareOptionsMenu. Basically you need to overwrite them in your Activity and handle menus there. You can either remove or add menus in these overwridden methods.
Whole procedure is well-documented and described here
If you mean the option menu, then this guide should help you: Changing the Menu.
It shows how you can dynamically add and remove items from the menu right before it is shown to the user using the onPrepareOptionsMenu() method, which passes you the menu. You can then add or remove items from the menu.
You have to play around with onPrepareOptionsMenu and onCreateOptionsMenu. Here is good example how to do that.
I found a way to achieve what i wanted. I put the values associated to the menu as a part of the MenuItems intent. In the onOptionsItemSelected(MenuItem item) i get the intent from the MenuItem retrieve the values from it and perform the action required. The code is as below:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
ApplicationManager.getInstance().resetCounter();
menu.removeGroup(1);
Vector listOfMenuToBeAdded=Service.getInstance().getMenuList();
Enumeration<com.data.Menu> menuEnumeration = listOfMenuToBeAdded.elements();
while (menuEnumeration.hasMoreElements()) {
com.data.Menu levelOptions = (com.pravaa.mobile.dashboard.data.Menu) menuEnumeration
.nextElement();
MenuItem levelMenuItem = menu.add(1, 100, Menu.NONE,
levelOptions.getCaption());
LevelOptionIntent levelOptionIntent = new LevelOptionIntent();
levelOptionIntent.setAppCode(levelOptions.getAppCode());
levelOptionIntent.setDashboardNumber(levelOptions.getNumber());
levelOptionIntent.setUniqueRecordId(levelOptions
.getUniqueRecordId());
levelOptionIntent.setLevelNumber(levelOptions.getLevelNo());
levelMenuItem.setIntent(levelOptionIntent);
}
return true;
}
Once a menu item is clicked on perform the action according to the values set in the intent.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch (item.getItemId()) {
case 100:
LevelOptionIntent levelOptionIntent = (LevelOptionIntent) item
.getIntent();
//Perform what you would want to based on the values set in the intent.
if (levelOptionIntent.getAppCode().equals(A)) {
// Start activity A
}elseif (levelOptionIntent.getAppCode().equals(B)) {
// Start activity B
}else if (levelOptionIntent.getAppCode().equals(C)) {
// Start activity C
}
break;
}
return true;
}
The LevelOptionsIntent class is as below:
public class LevelOptionIntent extends Intent {
private int dashboardNumber;
private String appCode;
private String uniqueRecordId;
private int levelNumber;
public LevelOptionIntent() {
super();
// TODO Auto-generated constructor stub
}
public int getDashboardNumber() {
return dashboardNumber;
}
public void setDashboardNumber(int dashboardNumber) {
this.dashboardNumber = dashboardNumber;
}
public String getAppCode() {
return appCode;
}
public void setAppCode(String appCode) {
this.appCode = appCode;
}
public String getUniqueRecordId() {
return uniqueRecordId;
}
public void setUniqueRecordId(String uniqueRecordId) {
this.uniqueRecordId = uniqueRecordId;
}
public int getLevelNumber() {
return levelNumber;
}
public void setLevelNumber(int levelNumber) {
this.levelNumber = levelNumber;
}}
I am not sure if this is the right way of doing it though.Can someone throw some light on this.
Thanks in advance.

Categories

Resources