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()
}
Related
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()
}
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>
I just updated the support design library from 22.2.1 to 23.0.1 and immediately noticed the presence of a scrollbar in the navigation drawer. I tried to use
android:scrollbars="none"
But that didn't fix it. Is there any other way to remove the scrollbar?
Unfortunately the scrollbar is set in the NavigationMenuView layout not in the NavigationView, for this reason if you use android:scrollbars="none" the scrollbar is still present.
You can do it programmatically calling this method:
private void disableNavigationViewScrollbars(NavigationView navigationView) {
if (navigationView != null) {
NavigationMenuView navigationMenuView = (NavigationMenuView) navigationView.getChildAt(0);
if (navigationMenuView != null) {
navigationMenuView.setVerticalScrollBarEnabled(false);
}
}
}
You can also use following style in your style.xml
<item name="android:scrollbarThumbVertical">#color/transparent</item>
if you don't have a transparent color defined in colors.xml, use the android library "transparent" with: <item name="android:scrollbarThumbVertical">#android:color/transparent</item>
I tried doing this in Kotlin, hope it will be a help.
first, create different fragments and any navigation you want to after that create a function which will be used to load fragments into the activity.
when (item.getItemId()) {
R.id.home -> {
//this is the name of the method I am using for adding fragments
//with bottom navigation bar you can use it with any type o navigation.
loadFragment(getString(R.string.home_fragment), HomeFragment());
appBar.title = "Home"
return true
}
R.id.jobs -> {
loadFragment(getString(R.string.jobs_fragment), JobsFragment());
appBar.title = "Jobs"
return true
}
after that here is the method
private fun loadFragment(tag: String,loadFragment: Fragment) {
val fManager = supportFragmentManager
val fTransaction = fManager.beginTransaction()
val fragment = fManager.findFragmentByTag(tag)
if (fragment == null) {
fTransaction.replace(R.id.activity_main_content_main, loadFragment,tag);
} else { // re-use the old fragment
fTransaction.replace(R.id.activity_main_content_main, fragment, tag);
}
fTransaction.addToBackStack(tag);
fTransaction.commit();
}
first val fragment = fManager.findFragmentByTag(tag) this will search if the fragment is already loaded then else statement will be executed and the preloaded fragment will be displayed but if not
then loadFragment parameter we passed contains the fragment you want to load then if statement will be executed which will load the passed fragment.
you can use it in apptheme in style :
<item name="android:scrollbarThumbVertical">#android:color/transparent</item>
I'm using the support library v4 and my questions are, How to know if a Fragment is Visible? and How can I change the propierties of the Layout inflated in the Fragment?
I'm using fragments like in the android developers tutorial with a FragmentActivity.
You should be able to do the following:
MyFragmentClass test = (MyFragmentClass) getSupportFragmentManager().findFragmentByTag("testID");
if (test != null && test.isVisible()) {
//DO STUFF
}
else {
//Whatever
}
Both isVisible() and isAdded() return true as soon as the Fragment is created, and not even actually visible. The only solution that actually works is:
if (isAdded() && isVisible() && getUserVisibleHint()) {
// ... do your thing
}
This does the job. Period.
NOTICE:
getUserVisibleHint() is now deprecated. be careful.
If you want to know when use is looking at the fragment you should use
yourFragment.isResumed()
instead of
yourFragment.isVisible()
First of all isVisible() already checks for isAdded() so no need for calling both. Second, non-of these two means that user is actually seeing your fragment. Only isResumed() makes sure that your fragment is in front of the user and user can interact with it if thats whats you are looking for.
you can try this way:
Fragment currentFragment = getFragmentManager().findFragmentById(R.id.fragment_container);
or
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
In this if, you check if currentFragment is instance of YourFragment
if (currentFragment instanceof YourFragment) {
Log.v(TAG, "your Fragment is Visible");
}
You can override setMenuVisibility like this:
#Override
public void setMenuVisibility(final boolean visible) {
if (visible) {
//Do your stuff here
}
super.setMenuVisibility(visible);
}
getUserVisibleHint() comes as true only when the fragment is on the view and visible
One thing to be aware of, is that isVisible() returns the visible state of the current fragment. There is a problem in the support library, where if you have nested fragments, and you hide the parent fragment (and therefore all the children), the child still says it is visible.
isVisible() is final, so can't override unfortunately. My workaround was to create a BaseFragment class that all my fragments extend, and then create a method like so:
public boolean getIsVisible()
{
if (getParentFragment() != null && getParentFragment() instanceof BaseFragment)
{
return isVisible() && ((BaseFragment) getParentFragment()).getIsVisible();
}
else
{
return isVisible();
}
}
I do isVisible() && ((BaseFragment) getParentFragment()).getIsVisible(); because we want to return false if any of the parent fragments are hidden.
This seems to do the trick for me.
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null && articleFrag.isVisible()) {
// Call a method in the ArticleFragment to update its content
articleFrag.updateArticleView(position);
}
see http://developer.android.com/training/basics/fragments/communicating.html
Just in case you use a Fragment layout with a ViewPager (TabLayout), you can easily ask for the current (in front) fragment by ViewPager.getCurrentItem() method. It will give you the page index.
Mapping from page index to fragment[class] should be easy as you did the mapping in your FragmentPagerAdapter derived Adapter already.
int i = pager.getCurrentItem();
You may register for page change notifications by
ViewPager pager = (ViewPager) findViewById(R.id.container);
pager.addOnPageChangeListener(this);
Of course you must implement interface ViewPager.OnPageChangeListener
public class MainActivity
extends AppCompatActivity
implements ViewPager.OnPageChangeListener
{
public void onPageSelected (int position)
{
// we get notified here when user scrolls/switches Fragment in ViewPager -- so
// we know which one is in front.
Toast toast = Toast.makeText(this, "current page " + String.valueOf(position), Toast.LENGTH_LONG);
toast.show();
}
public void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageScrollStateChanged (int state) {
}
}
My answer here might be a little off the question. But as a newbie to Android Apps I was just facing exactly this problem and did not find an answer anywhere. So worked out above solution and posting it here -- perhaps someone finds it useful.
Edit: You might combine this method with LiveData on which the fragments subscribe. Further on, if you give your Fragments a page index as constructor argument, you can make a simple amIvisible() function in your fragment class.
In MainActivity:
private final MutableLiveData<Integer> current_page_ld = new MutableLiveData<>();
public LiveData<Integer> getCurrentPageIdx() { return current_page_ld; }
public void onPageSelected(int position) {
current_page_ld.setValue(position);
}
public class MyPagerAdapter extends FragmentPagerAdapter
{
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page: But only on first
// creation -- not on restore state !!!
// see: https://stackoverflow.com/a/35677363/3290848
switch (position) {
case 0:
return MyFragment.newInstance(0);
case 1:
return OtherFragment.newInstance(1);
case 2:
return XYFragment.newInstance(2);
}
return null;
}
}
In Fragment:
public static MyFragment newInstance(int index) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("idx", index);
fragment.setArguments(args);
return fragment;
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mPageIndex = getArguments().getInt(ARG_PARAM1);
}
...
}
public void onAttach(Context context)
{
super.onAttach(context);
MyActivity mActivity = (MyActivity)context;
mActivity.getCurrentPageIdx().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer data) {
if (data == mPageIndex) {
// have focus
} else {
// not in front
}
}
});
}
Try this if you have only one Fragment
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
//TODO: Your Code Here
}
Adding some information here that I experienced:
fragment.isVisible is only working (true/false) when you replaceFragment() otherwise if you work with addFragment(), isVisible always returns true whether the fragment is in behind of some other fragment.
None of the above solutions worked for me.
The following however works like a charm:-
override fun setUserVisibleHint(isVisibleToUser: Boolean)
getUserVisibleHint is now deprecated, and I was having problems with isVisible being true when another fragment was added in front of it. This detects the fragment's visibility on the back stack using its view. This may be helpful if your issue is related to other fragments on the back stack.
View extension to detect if a view is being displayed on the screen: (see also How can you tell if a View is visible on screen in Android?)
fun View.isVisibleOnScreen(): Boolean {
if (!isShown) return false
val actualPosition = Rect().also { getGlobalVisibleRect(it) }
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
val screenHeight = Resources.getSystem().displayMetrics.heightPixels
val screen = Rect(0, 0, screenWidth, screenHeight)
return Rect.intersects(actualPosition, screen)
}
Then defined a back stack listener from the fragment, watching the top fragment on the stack (the one added last)
fun Fragment.setOnFragmentStackVisibilityListener(onVisible: () -> Unit) {
val renderDelayMillis = 300L
parentFragmentManager.addOnBackStackChangedListener {
Handler(Looper.getMainLooper()).postDelayed({
if (isAdded) {
val topStackFragment = parentFragmentManager.fragments[parentFragmentManager.fragments.size - 1]
if (topStackFragment.view == view && isVisible && view!!.isVisibleOnScreen()) {
onVisible.invoke()
}
}
}, renderDelayMillis)
}
}
The back stack listener is called before the view is ready so an arbitrarily small delay was needed. The lambda is called when the view becomes visible.
I was using Android's BottomNavigationView and managing fragments with FragmentTransactions.hide(frag) and FragmentTransaction.show(frag). So, to detect if a fragment is visible or not, I used following:
abstract class BaseFragment : Fragment() {
open fun onFragmentVisible(){
}
override fun onStart() {
super.onStart()
if (!isHidden){
onFragmentVisible()
}
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (!hidden){
onFragmentVisible()
}
}
}
You can extend BaseFragment in your fragment and implement it's onFragmentVisible function.
In Kotlin
if you use FragmentPagerAdapter and since getUserVisibleHint() is deprecated in api 29, I suggest you to add behaviour parameter BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT in your FragmentPagerAdapter like this:
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
then in your fragment you can check using their lifecycle state:
if(lifecycle.currentState == Lifecycle.State.RESUMED) {
// do something when fragment is visible
}
Several tries to ask this question in #android-dev (irc) and hours of searching, but I still don't have a solution to this problem.
I'm currently working on the search-function in my android music player. I'm using the amazing ActionBarSherlock to provide support for older android versions.
My Problem is the following:
When the user clicks the search menu/action button, the actionView of the clicked action should be expanded, and a new Fragment (the searchFragment) should be shown instead of the currently active one.
However when i'm attempting to do this, the actionView doesn't expand.
I've tried to expand the actionView, without adding the SearchFragment, and in that case the actionView DOES expand. However the combination seems impossible.
Here's my code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null) {
if (item.getItemId() == R.id.collectionactivity_search_menu_button) {
item.expandActionView();
mTabsAdapter.replace(new SearchFragment(), false);
return true;
}
}
return false;
}
/**
* Replaces the view pager fragment at specified position.
*/
public void replace(int position, Fragment newFragment, boolean isBackAction) {
// Get currently active fragment.
ArrayList<Fragment> fragmentsStack = mFragments.get(position);
Fragment currentFragment = fragmentsStack.get(fragmentsStack.size() - 1);
if (currentFragment == null) {
return;
}
// Replace the fragment using a transaction.
this.startUpdate(mViewPager);
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.attach(newFragment).remove(currentFragment).commit();
if (isBackAction == true)
fragmentsStack.remove(currentFragment);
else
fragmentsStack.add(newFragment);
this.notifyDataSetChanged();
this.finishUpdate(mViewPager);
}
The mTabsAdapter.replace(...) method replaces the currently shown Fragment with the one in the first parameter. In Addition the fragment is being added to a custom backStack.
Replacing the Fragment before or after expanding the View didn't make any difference.
Hopefully somebody is able to help me :)
thanks in advance!
Have you tried setting your actionviews android:showAsAction to collapseActionView? that way you don't have to manage the expand/close action.
If that does not work you can handle it in another way,you set an expand listener and replace your fragment once your action view starts expanding
item.setOnActionExpandListener(new OnActionExpandListener() {
#Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Do something when collapsed
return true; // Return true to collapse action view
}
#Override
public boolean onMenuItemActionExpand(MenuItem item) {
mTabsAdapter.replace(new SearchFragment(), false);
return true; // Return true to expand action view
}
});
remember to return true to let the actionview expand
I found out what the problem was caused by.
My mTabsAdapter.replace(..) method was calling notifyDataSetChanged();. So everytime I replaced the fragment, onPrepareOptionsMenu was being called, resulting in the search action button being removed and added again, thus resulting in the actionView being collapsed.
The solution to this is to fix my onPrepareOptionsMenu, so the actionView will be expanded again, whenever onPrepareOptionsMenu is called and the actionView was expanded before.