I know it's possible to highlight a navigation view item by calling setCheckedItem() or return true value in onNavigationItemSelected to display the item as the selected item, but How can I uncheck the checked items of a navigation view?
This will uncheck the items:
int size = mNavigationView.getMenu().size();
for (int i = 0; i < size; i++) {
mNavigationView.getMenu().getItem(i).setCheckable(false);
}
I saw #arsent solution and gave it a try, and it will indeed do what you want, which is to unselect all the items... but, I was having an issue in the following scenario:
Select menu item 1 (using NavigationView#setCheckedItem)
Unselect all the items as per #arsent's solution
Select menu item 1 again (using NavigationView#setCheckedItem)
In this scenario, item 1 will not be marked as checked. That's because internally the navigation view keeps track of the previously selected item set in step 1, which doesn't change in step 2, and it just skips step 3 because the previously selected item is the same as the one we're selecting now.
My suggestion (and an alternative solution) to avoid this is just having a dummy invisible item and use NavigationView#setCheckedItem to select that item whenever you want to unselect all, like so
<item
android:id="#+id/menu_none"
android:title=""
android:visible="false"/>
To unselect all just do
mNavigationView.setCheckedItem(R.id.menu_none);
To uncheck all MenuItems including SubMenu items you have to use recursion -
private void unCheckAllMenuItems(#NonNull final Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
final MenuItem item = menu.getItem(i);
if(item.hasSubMenu()) {
// Un check sub menu items
unCheckAllMenuItems(item.getSubMenu());
} else {
item.setChecked(false);
}
}
}
Call above method for unchecking all items, like below -
unCheckAllMenuItems(navigationView.getMenu());
just make your items non checkable like so
<item
android:id="#+id/profile_item"
android:checkable="false"
android:title="#string/profile"/>
Quoting #Codeversed, there is "no need to loop menu items with added overhead!". But, there is no need to create multiple groups (in this case he is creating the #+id/grp1 and #+id/grp2) to uncheck a previous checked item.
You can simple add a single group for all elements with the android:checkableBehavior, like this:
<group android:checkableBehavior="single">
<item
android:id="#+id/id1"
android:checked="true"
android:icon="#drawable/drawable1"
android:title="#string/string1" />
<item
android:id="#+id/id2"
android:icon="#drawable/drawable2"
android:title="#string/string2" />
</group>
Joao's solutions didn't not work for me as totally expected. This would lead to a blank space from unchecked Item View on my Navigation.
Just make sure to set the view as gone:
<item
android:id="#+id/your_menu_item_id_to_hide"
android:title=""
android:visible="false"/>
bottomNavigationView.getMenu().findItem(R.id.your_menu_item_id_to_hide).setChecked(true);
bottomNavigationView.findViewById(R.id.your_menu_item_id_to_hide).setVisibility(View.GONE);
Arsent solution is not necessary in this case.
All you need to do is surround your groups like this:
<group>
<group
android:id="#+id/grp1">
<item
android:id="#+id/nav_profile"
android:icon="#drawable/ic_account_circle_24dp"
android:title="#string/profile" />
</group>
<group
android:id="#+id/grp2">
<item
android:id="#+id/nav_settings"
android:icon="#drawable/ic_settings_24dp"
android:title="#string/settings" />
<item
android:id="#+id/nav_help"
android:icon="#drawable/topic_help"
android:title="#string/help_feedback" />
</group>
</group>
No need to loop menu items with added overhead!
I guess someone like me use those methods just like this
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_today:
break;
case R.id.nav_calendar:
navigationView.getMenu().performIdentifierAction(R.id.nav_today, 0);
navigationView.getMenu().getItem(0).setChecked(true);//or
navigationView.setCheckedItem(R.id.nav_today);//or
drawerLayout.closeDrawers();
break;
}
return true;
}
Trying to check R.id.nav_today after you clicked on R.id.nav_calendar, (btw: checkableBehavior="single"), unfortunately it will not work.
That is because after your code navigationView.setCheckedItem(R.id.nav_today) be called then the R.id.nav_today will be checked immediately, but after this, your click on R.id.nav_calendar will check itself.
That is why whatever methods you use seem never work at all. It is work, but be override immediately.
To uncheck it inside NavigationItemSelectedListener I had to use post (to UI thread):
App.getHandler().post(() -> menuItem.setChecked(false));
Full example:
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(
menuItem -> {
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
switch (menuItem.getItemId()) {
...
}
App.getHandler().post(() -> menuItem.setChecked(false));
return true;
});
p.s. in my case App.getHandler() returns Handler instance for UI Thread Lopper
I had to use a combination of all the solutions mentioned here. In my case, I want to open an URL Intent when the Item is clicked (open a Browser). The clicked item should get unchecked after the click and reset to the item before. Important is to understand, that you cannot uncheck an item during the click listener event, since the checked state will be handled afterwards. So this is my solution in Kotlin:
val item = navigationView.menu.findItem(R.id.menu_item)
item.setOnMenuItemClickListener {
val oldItem = navigationView.checkedItem
rootView.postDelayed({ // you can use here any View to post a Runnable with a delay
navigationView.setCheckedItem(oldItem?.itemId ?: R.id.default_item)
}, 500)
browseURL("https://www.example.com")
true
}
I use a Navigation Drawer in combination with the Jetpack Navigation.
i combine #arsent and #Rahul answers and write this code:
private void NavigationView_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
{
var size = navigationView.Menu.Size();
for (int i = 0; i < size; i++)
{
var item= navigationView.Menu.GetItem(i).SetChecked(false);
if (item.HasSubMenu)
{
for (int j = 0; j < item.SubMenu.Size(); j++)
{
item.SubMenu.GetItem(j).SetChecked(false);
}
}
}
e.MenuItem.SetChecked(true);
drawerLayout.CloseDrawers();
}
above code is for xamarin c# and work,but u can easily convert to java
#arsent's answer is correct but setCheckable(false) uncheck all items, it prevents the items from being checked in the future.
Just use setChecked(false)
int size = mNavigationView.getMenu().size();
for (int i = 0; i < size; i++) {
mNavigationView.getMenu().getItem(i).setChecked(false);
}
Related
I'm adding the new Bottom Navigation View from the material design library to a project, and I would like to have no pre selected item by default.
For now first item is selected by default.
I have used
mBottomNavigation.getMenu().getItem(0).setChecked(false);
but when doing it in for loop for all the menu item last item is selected again by default.
Is there a way we can achieve this?
Not sure about the proper way to achieve this but a work around will help-
setCheckable(false) for first item
navigation.getMenu().getItem(0).setCheckable(false);
item.setCheckable(true) inside onNavigationItemSelected()
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
item.setCheckable(true);
mTextMessage.setText(R.string.title_home);
return true;
}
return false;
}
I came up with another solution
Just add one more item to your menu.xml file for example
This is my bottom_nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/navigation_home"
android:icon="#drawable/ic_home_black_24dp"
android:title="#string/home" />
<item
android:id="#+id/navigation_cart"
android:icon="#drawable/ic_shopping_cart_black_24dp"
android:title="#string/cart" />
<item
android:id="#+id/navigation_wishlist"
android:icon="#drawable/ic_favorite_border_black_24dp"
android:title="#string/wish_list" />
<item
android:id="#+id/navigation_account"
android:icon="#drawable/ic_person_black_24dp"
android:title="#string/account" />
<!-- Our invisible item -->
<item
android:id="#+id/invisible"
android:visible="false"
android:icon="#drawable/ic_person_black_24dp"
android:title="#string/account" />
</menu>
Notice that I have added that item at last position and given it an id invisible and also set it's visibility to false.
Now, In the activity just set selected item id to this id like this
bottomNavMenu.setSelectedItemId(R.id.invisible);
Thanks
XML Code
<group android:checkableBehavior="single">
<item android:id="#+id/nav_item1" />
<item android:id="#+id/nav_item2" />
<item android:id="#+id/nav_item3" />
<item android:id="#+id/nav_item4" />
<item
android:id="#+id/nav_item5"
android:icon="#drawable/icon_item5"
android:title="Home"
android:visible="false"/>
</group>
JAVA Code
bottomNavigationView.getMenu().getItem(4).setChecked(true);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.nav_item1:
return true;
case R.id.nav_item2:
return true;
case R.id.nav_item3:
return true;
case R.id.nav_item4:
return true;
}
// Default operation you want to perform
return false;
}
});
If anyone interested in a nice Kotlin solution, here's mine:
//disable the preselected first item
//<navigation> is the bottom navigation view
navigation.menu.getItem(0).isCheckable=false
Then in the selection listener, make sure that you'll show the user what he selected
BottomNavigationView.OnNavigationItemSelectedListener { item: MenuItem ->
when (item.itemId) {
R.id.option1 -> {
item.isCheckable=true //here is the magic
//notify the listener
return#OnNavigationItemSelectedListener true
}
R.id.option2 ->{
item.isCheckable=true
//notify the listener
return#OnNavigationItemSelectedListener true
}
R.id.option3 ->{
//go to forgot user fragment
item.isCheckable=true
//notify the listener
return#OnNavigationItemSelectedListener true
}
else -> false
}
}
Finally , make a selector color so you can change easily in color
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true"
android:color="#color/colorAccent" />
<item android:color="#color/colorPrimary" />
And add the selector to the navigation view
app:itemIconTint="#color/navigation_colors"
app:itemTextColor="#color/navigation_colors"
Now, if you need to change the colours, just change the selector.
Add this line in your onCreate method
mBottomNavigation.setSelectedItemId("ID OF YOUR MENU ITEM");
Use setCheckable instead of setChecked(false) in line:
mBottomNavigation.getMenu().getItem(0).setCheckable(false);
It works for me.
I've created a sample project and by default there is no checked item. Just make sure you don't have any navigationView.setCheckedItem(R.id.id) on your code.
My solution was to select a different tab and immediately after select the tab I initially wanted.
bottomNavigationView.selectedItemId = R.id.dummy_tab
bottomNavigationView.selectedItemId = R.id.tab_to_select
The setOnItemReselectedListener disables reselect tab selections, but the navigationView has a default tab selected and don't render after creation because is invoking setOnItemReselectedListener
Tried the invisible solution but didn't work with programmatically bottomMenu creation.
Old question, but if you still facing issue - just use this:
bottomNavigationView.getMenu().getItem(0).setCheckable(false);
And it:
bottomNavigationView.setOnItemReselectedListener(new NavigationBarView.OnItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
if (item.getItemId() == R.id.navigation_home) {
item.setCheckable(true);
}
}
});
I combined the solution mentioned by #Ashish Kumar and resolved my query and
private void customizeBottomBar() {
mBottomNavigation.getMenu().getItem(0)
.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_reserve_normal));
changeMenuItemCheckedStateColor(mBottomNavigation, getUnCheckedColor(), getUnCheckedColor());
}
/**
* Method to change the color state of bottom bar view
* #param bottomNavigationView - BottomNavigation view instance
* #param checkedColorHex int value of checked color code
* #param uncheckedColorHex int value of unchecked color code
*/
void changeMenuItemCheckedStateColor(BottomNavigationView bottomNavigationView,
int checkedColorHex, int uncheckedColorHex) {
int[][] states = new int[][]{
new int[]{-android.R.attr.state_checked}, // unchecked
new int[]{android.R.attr.state_checked}, // checked
};
int[] colors = new int[]{
uncheckedColorHex,
checkedColorHex
};
ColorStateList colorStateList = new ColorStateList(states, colors);
bottomNavigationView.setItemTextColor(colorStateList);
bottomNavigationView.setItemIconTintList(colorStateList);
}
As I liked the design from BottomNavigationView I decided to implement a new Menu for my App with it, instead of just using simple buttons.
I took this post as a guideline.
According to BottomNavigationView's documentation, its purpose is to
provide quick navigation between top-level views of an app. It is
primarily designed for use on mobile.
In my case, I just want each MenuItem to launch an activity, but by default there is always one MenuItem selected:
I tried to set the color to white with:
app:itemIconTint="#color/white"
app:itemTextColor="#color/white"
Still, visibly selected MenuItem is different from others (Title size bigger), which is still bothering me:
I came with the idea to place a hidden MenuItem to select like:
<item
android:id="#+id/uncheckedItem"
android:title="" />
and make its view GONE:
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
bottomNavigationView.findViewById(R.id.uncheckedItem).setVisibility(View.GONE);
This makes all MenuItems unchecked, but by default BottomNavigationView is hidding Titles, as it has more than 3 MenuItems to display, even if the fourth MenuItem is settle to GONE:
So my question remains, is there away/hack to unselect all MenuItems and keep its titles being displayed?
mNavigationBottom.getMenu().setGroupCheckable(0, false, true);
To unselect all items I have create this extension:
fun BottomNavigationView.uncheckAllItems() {
menu.setGroupCheckable(0, true, false)
for (i in 0 until menu.size()) {
menu.getItem(i).isChecked = false
}
menu.setGroupCheckable(0, true, true)
}
menu.setGroupCheckable(0, true, false) make it possible.
The third parameter made the menu not exclusive and then within the loop you change the checked status.
To finish set the menu to exclusive again.
Here the doc
Thanks for your idea. I have implement it in my lib.
I have a better way do it by reflect. So it won't show space.
If you have interest. Click here : https://github.com/ittianyu/BottomNavigationViewEx
private void initBottomViewAndLoadFragments(final BottomNavigationViewEx bnve) {
bnve.enableAnimation(false);
bnve.enableShiftingMode(false);
bnve.enableItemShiftingMode(false);
// use the unchecked color for first item
bnve.setIconTintList(0, getResources().getColorStateList(R.color.bnv_unchecked_black));
bnve.setTextTintList(0, getResources().getColorStateList(R.color.bnv_unchecked_black));
bnve.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
private boolean firstClick = true;
private int lastItemId = -1;
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
// restore the color when click
if (firstClick) {
firstClick = false;
bnve.setIconTintList(0, getResources().getColorStateList(R.color.selector_bnv));
bnve.setTextTintList(0, getResources().getColorStateList(R.color.selector_bnv));
}
if (firstClick || lastItemId == -1 || lastItemId != item.getItemId()) {
lastItemId = item.getItemId();
} else {
return false;
}
// do stuff
return fillContent(item.getItemId());
}
});
}
-- res/color/selector_bnv.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/bnv_checked_white" android:state_checked="true" />
<item android:color="#color/bnv_unchecked_black" />
</selector>
-- res/values/colors.xml
<color name="bnv_checked_white">#android:color/white</color>
<color name="bnv_unchecked_black">#android:color/black</color>
Your solution seems change the space between items
There is my solution :
"Just set color of clicked same as color of un-clicked."
for example:
private void changeMenuItemCheckedStateColor(BottomNavigationView bottomNavigationView, String checkedColorHex, String uncheckedColorHex) {
int checkedColor = Color.parseColor(checkedColorHex);
int uncheckedColor = Color.parseColor(uncheckedColorHex);
int[][] states = new int[][] {
new int[] {-android.R.attr.state_checked}, // unchecked
new int[] {android.R.attr.state_checked}, // checked
};
int[] colors = new int[] {
uncheckedColor,
checkedColor
};
ColorStateList colorStateList = new ColorStateList(states, colors);
bottomNavigationView.setItemTextColor(colorStateList);
bottomNavigationView.setItemIconTintList(colorStateList);
}
if you want to un-check all items, you can
changeMenuItemCheckedStateColor(mBottomNavigationView, "#999999", "#999999");
if you want to restore the color setting, you can
changeMenuItemCheckedStateColor(mBottomNavigationView, "FF743A", "999999");
I found my own solution merging my progress with this post.
Steps:
Update proguard-rules.pro and sync build
Create Helper to disable BottomNavigationView Shift Mode
Create an Item to hide on Menu.xml
Inflate BottomNavigationView
Set Item to be hidden as Checked GONE
Use Helper to disable Shifting Mode
Output:
Working code:
proguard-rules.pro:
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView {
boolean mShiftingMode;
}
BottomNavigationShiftHelper.java:
public class BottomNavigationShiftHelper {
private final static String TAG = "DEBUG_BOTTOM_NAV_UTIL";
public static void disableShiftMode(BottomNavigationView view) {
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try {
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++) {
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
item.setShiftingMode(false);
// set once again checked value, so view will be updated
item.setChecked(item.getItemData().isChecked());
}
} catch (NoSuchFieldException e) {
Log.d(TAG, "Unable to get shift mode field");
} catch (IllegalAccessException e) {
Log.d(TAG, "Unable to change value of shift mode");
}
}
}
Activity Sample.java:
private void loadNavigationBar() {
BottomNavigationView bottomNavigationView = (BottomNavigationView)
findViewById(R.id.navigation_bar);
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
bottomNavigationView.findViewById(R.id.uncheckedItem).setVisibility(View.GONE);
BottomNavigationViewUtils.disableShiftMode(bottomNavigationView);
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.newList:
//Do The Math
break;
case R.id.loadList:
//Do The Math
break;
case R.id.settings:
//Do The Math
break;
}
return false;
}
});
}
BottomNavigationMenu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/newList"
android:enabled="true"
android:icon="#drawable/new_list"
android:title="#string/common.button.list.new"
app:showAsAction="withText" />
<item
android:id="#+id/loadList"
android:enabled="true"
android:icon="#drawable/load"
android:title="#string/common.button.list.load"
app:showAsAction="withText" />
<item
android:id="#+id/settings"
android:enabled="true"
android:icon="#drawable/settings"
android:title="#string/common.label.settings"
app:showAsAction="withText" />
<item
android:id="#+id/uncheckedItem"
android:title="" />
</menu>
BottomNavigationComponent (inside Activity.xml):
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:itemIconTint="#color/white"
app:itemTextColor="#color/white"
android:background="#drawable/BottomNavigationMenu.xml"
app:menu="#menu/supercart_bottom_navigation" />
Try this, it worked for me
<item
android:id="#+id/uncheckedItem"
android:visible="false"
android:title="" />
and set
bottomNavigationView.getMenu().findItem(R.id.uncheckedItem).setChecked(true);
you get all menu item view as unselected; since the selection is given for uncheckedItem which is invisible
Hope it helped you.
There is an exception in accepted answer which we set maximum item we cant implement that code.So I got a code that is more simple than accepted code and it's also working with maximum item.
I referred from here Custom TextSize of BottomNavigationView support android
In your dimen.xml you can put:
<dimen name="design_bottom_navigation_text_size" tools:override="true">10sp</dimen>
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">10sp</dimen>
Doing this you are overriding the default value of dimen that the internal classes of BottomNavigationView use. So be carreful.
Set this code in your onCreate method where the bottom navigation view initialized
mNavigationBottom.getMenu().setGroupCheckable(0, false, true);
and Last set your bottom navigation bar like this :
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
android:fitsSystemWindows="true"
app:labelVisibilityMode="labeled"
app:layout_anchorGravity="fill"
app:menu="#menu/bottom_nav_menu" />
In this set your app:labelVisibilityMode to labeled
This is the same as the accepted answer except I have changed two lines of code marked below. When looping through the BottomNavigationItemViews, I set checked to false always and I also set checkable to false. This prevents the Menu Items from changing size. You still need this proguard rule:
-keepclassmembers class android.support.design.internal.BottomNavigationMenuView {
boolean mShiftingMode;
}
Updated code:
static void removeShiftMode(BottomNavigationView view)
{
BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
try
{
Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
shiftingMode.setAccessible(true);
shiftingMode.setBoolean(menuView, false);
shiftingMode.setAccessible(false);
for (int i = 0; i < menuView.getChildCount(); i++)
{
BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
item.setShiftingMode(false);
item.setChecked(false); // <--- This line changed
item.setCheckable(false); // <-- This line was added
}
}
catch (NoSuchFieldException e)
{
Log.e("ERROR NO SUCH FIELD", "Unable to get shift mode field");
}
catch (IllegalAccessException e)
{
Log.e("ERROR ILLEGAL ALG", "Unable to change value of shift mode");
}
}
The best answer mNavigationBottom.getMenu().setGroupCheckable(0, false, true) did not work for me. It still showed the first item as being selected.
What did work for me is to have an invisible item and to select that, exactly like you did in the question.
Add app:labelVisibilityMode="labeled" to your BottomNavigationView so that all items remain in view with their titles.
if(isDarkMode(context)) {
mBotNavView.setItemIconTintList(ColorStateList.valueOf(Color.parseColor("#FFFFFF")));
} else {
mBotNavView.setItemIconTintList(ColorStateList.valueOf(Color.parseColor("#000000")));
}
public boolean isDarkMode(Context context) {
int nightModeFlags = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return darkMode = (nightModeFlags == Configuration.UI_MODE_NIGHT_YES);
}
I have an app with ActionBarSherlock using theme Theme.Sherlock.Light.DarkActionBar. Action bar is dark and my menu icons are light. When I run my app on small layouts, 2 or 3 menu items with icons are displayed in the overflow menu.
On Android 3+ the overflow menu items will not display their icons, but on Android 2.3 and earlier I see menu tiles with almost invisible icons, because the tile color is white and icons are close to be white.
As you can see, the light icons are invisible on a white background, but they must have light color to be visible on a dark action bar:
Can I remove icons when menu items are displayed in the overflow menu?
you could use configuration qualifiers.
e.g.
make a drawable folder
/res/drawable-v11/ put all the "light" icons in it.
and for the darker icons use the
/res/drawable/ folder.
be sure to use the same filenames in both folders.
I hope I have understood your problem and this might help you.
However, if you want to change the drawables JUST for the overflow menu, I don't think it's possible. Also because the menu icons are not intended to be used like that. ActionBarSherlock is probably also because of issues like this, not an official library.
I was also facing the same issue:
there are many ways you can achieve this rather than removing image:
1)you can use respective drawable folder to put light and dark image.
2)You can also change the background color by code of your menu by checking your device version.
If you device doen't support overflow menu, the, you can change the background color of your menu as well as you can also change menu text color.
I was also facing the same issue and resolved using following one:
static final Class<?>[] constructorSignature = new Class[] {Context.class, AttributeSet.class};
class MenuColorFix implements LayoutInflater.Factory {
public View onCreateView(String name, Context context, AttributeSet attrs) {
if (name.equalsIgnoreCase("com.android.internal.view.menu.ListMenuItemView")) {
try {
Class<? extends ViewGroup> clazz = context.getClassLoader().loadClass(name).asSubclass(ViewGroup.class);
Constructor<? extends ViewGroup> constructor = clazz.getConstructor(constructorSignature);
final ViewGroup view = constructor.newInstance(new Object[]{context,attrs});
new Handler().post(new Runnable() {
public void run() {
try {
view.setBackgroundColor(Color.BLACK);
List<View> children = getAllChildren(view);
for(int i = 0; i< children.size(); i++) {
View child = children.get(i);
if ( child instanceof TextView ) {
((TextView)child).setTextColor(Color.WHITE);
}
}
}
catch (Exception e) {
Log.i(TAG, "Caught Exception!",e);
}
}
});
return view;
}
catch (Exception e) {
Log.i(TAG, "Caught Exception!",e);
}
}
return null;
}
}
public List<View> getAllChildren(ViewGroup vg) {
ArrayList<View> result = new ArrayList<View>();
for ( int i = 0; i < vg.getChildCount(); i++ ) {
View child = vg.getChildAt(i);
if ( child instanceof ViewGroup) {
result.addAll(getAllChildren((ViewGroup)child));
}
else {
result.add(child);
}
}
return result;
}
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
LayoutInflater lInflater = getLayoutInflater();
if ( lInflater.getFactory() == null ) {
lInflater.setFactory(new MenuColorFix());
}
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.myMenu, menu);
}
3) change background color from styles.xml file
<style name="Theme.MyTheme" parent="Theme.Sherlock.ForceOverflow">
<item name="actionBarStyle">#style/Widget.MyTheme.ActionBar</item>
<item name="android:actionBarStyle">#style/Widget.MyTheme.ActionBar</item>
</style>
<style name="Widget.MyTheme.ActionBar" parent="Widget.Sherlock.ActionBar">
<item name="android:background">#ff000000</item>
<item name="background">#ff000000</item>
</style>
For me, all of the 3 worked fine
Hope, this will work for you as well
Another option is to remove the icons from the non-action items in onPrepareOptionsMenu.
The idea is to use actionbarsherlock's MenuItemImpl.isActionButton to figure out if each item is an action item, and if not to remove the icon. This is made a little bit tricky because onPrepareOptionsMenu is called (at least) twice by ABS - the first time when it is building the action bar, in which case MenuItemImpl.isActionButton has not yet been set and will always return false. If that's the case, you want to leave the icons alone. Once the action bar has been built the isActionButton method will return true for action bar items, false otherwise. So you want to remove the icons for the ones that return false. This is what I came up with:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
boolean buildingOptionsMenu = false;
for (int i=0; i<menu.size(); ++i) {
MenuItemImpl mi = (MenuItemImpl)menu.getItem(i);
if (mi.isActionButton()) {
buildingOptionsMenu = true;
break;
}
}
if (buildingOptionsMenu) {
for (int i=0; i<menu.size(); ++i) {
MenuItemImpl mi = (MenuItemImpl)menu.getItem(i);
if (!mi.isActionButton()) {
mi.setIcon(null);
mi.setIcon(0);
}
}
}
}
return super.onPrepareOptionsMenu(menu);
}
You'll need these two imports:
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
This works in ABS 4.3.0, but since it uses internal library classes it might not work with other versions of the library.
OS 2.x was a mess since the options menu background could be black or white, depending on the device, with no way to know which for sure.
The easy fix was to use grey (#888888) icons for Android 2.x & under and put your modern (ICS/JB) icons in a v11 folder for modern devices:
drawable // old school icons
drawable-v11 // modern icons
Of course that means drawable-mdpi-v11, drawable-hdpi-v11, and so on.
A simple alternative to adding a whole set of duplicate dark icons for 2.x versions can be simply removing the icons from all the items that can go to the overflow menu. For example:
res/menu
<item
android:id="#+id/menu_send_email"
android:showAsAction="ifRoom"
android:title="#string/menu_send_email"/>
res/menu-v11 (or even res/menu-v9, because 2.3 usually has a dark menu)
<item
android:id="#+id/menu_send_email"
android:icon="#drawable/ic_action_send_email"
android:showAsAction="ifRoom"
android:title="#string/menu_send_email"/>
Of course, you need to make the titles short enough to fit into the ActionBar at least on some larger screens, or settle with the fact that they always go into the overflow.
I have a problem in a simple case (at least, it looks like so). I need to create a submenu for a context menu dynamically and provide each item with a radiobox. I made a lot of trials. When I call menu.setGroupCheckable(0, true, true), where 0 is by default the menu itself, it displays radio buttons to the right on every menu item as expected, but I need this for submenu. So I have the following code:
SubMenu sub = menu.addSubMenu(R.string.name);
int count = 1000;
for(String e : someList)
{
MenuItem item = sub.add(1, count, count, e);
count++;
}
menu.setGroupCheckable(1, true, true);
In this case I don't see neither radioboxes, nor checkboxes in the submenu. Then I wrote the following code:
SubMenu sub = menu.addSubMenu(R.string.name);
int count = 1000;
for(String e : someList)
{
MenuItem item = sub.add(1, count, count, e);
item.setCheckable(true);
count++;
}
menu.setGroupCheckable(1, true, true);
This makes the submenu to have a checkbox in every item, and the checkboxes work exclusively, but I want radioboxes, because they look more intuitively for exclusive selection.
So, how can this be accomplished?
Set the checkableBehavior in xml to single.
Here is some code:
<menu>
<group android:id="#+id/group"
android:checkableBehavior="single">
<item android:id="#+id/menu_sort_by_name"
android:title="#string/action_sort_by_name"/>
<item android:id="#+id/menu_sort_by_last_edit"
android:title="#string/action_sort_by_last_edit"/>
</group>
</menu>
I found out that groups of menus and submenus are processed separately, that is a group formed in a submenu, should be addressed via the submenu, not via the top-level menu. So the solution is to call:
sub.setGroupCheckable(1, true, true);
This code works as expected, that is items in the submenu show radiobuttons instead of checkboxes.
I have a field where the user can type a search query in the action bar of the application. This is declared in the action bar using a menu inflate in the Activity:
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:id="#+id/action_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
android:title="#string/search"
></item>
</menu>
I need to customize the appearance of the SearchView (for instance background and text color). So far I could not find a way to do it using XML (using styles or themes).
Is my only option to do it in the code when inflating the menu?
Edit #1: I have tried programmatically but I cannot get a simple way to set the text color. Plus when I do searchView.setBackgroundResource(...) The background is set on the global widget, (also when the SearchView is iconified).
Edit #2: Not much information on the Search Developer Reference either
Seibelj had an answer that is good if you want to change the icons. But you'll need to
do it for every API version. I was using ICS with ActionBarSherlock and it didn't do justice for me but it did push me in the correct direction.
Below I change the text color and hint color. I showed how you might go about changing the
icons too, though I have no interest in that for now (and you probably want to use the default icons anyways to be consistent)
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Set up the search menu
SearchView searchView = (SearchView)menu.findItem(R.id.action_search).getActionView();
traverseView(searchView, 0);
return true;
}
private void traverseView(View view, int index) {
if (view instanceof SearchView) {
SearchView v = (SearchView) view;
for(int i = 0; i < v.getChildCount(); i++) {
traverseView(v.getChildAt(i), i);
}
} else if (view instanceof LinearLayout) {
LinearLayout ll = (LinearLayout) view;
for(int i = 0; i < ll.getChildCount(); i++) {
traverseView(ll.getChildAt(i), i);
}
} else if (view instanceof EditText) {
((EditText) view).setTextColor(Color.WHITE);
((EditText) view).setHintTextColor(R.color.blue_trans);
} else if (view instanceof TextView) {
((TextView) view).setTextColor(Color.WHITE);
} else if (view instanceof ImageView) {
// TODO dissect images and replace with custom images
} else {
Log.v("View Scout", "Undefined view type here...");
}
}
adding my take on things which is probably a little more efficient and safe across different android versions.
you can actually get a numeric ID value from a string ID name. using android's hierarchyviewer tool, you can actually find the string IDs of the things you are interested in, and then just use findViewById(...) to look them up.
the code below sets the hint and text color for the edit field itself. you could apply the same pattern for other aspects that you wish to style.
private static synchronized int getSearchSrcTextId(View view) {
if (searchSrcTextId == -1) {
searchSrcTextId = getId(view, "android:id/search_src_text");
}
return searchSrcTextId;
}
private static int getId(View view, String name) {
return view.getContext().getResources().getIdentifier(name, null, null);
}
#TargetApi(11)
private void style(View view) {
ImageView iv;
AutoCompleteTextView actv = (AutoCompleteTextView) view.findViewById(getSearchSrcTextId(view));
if (actv != null) {
actv.setHint(getDecoratedHint(actv,
searchView.getContext().getResources().getString(R.string.titleApplicationSearchHint),
R.drawable.ic_ab_search));
actv.setTextColor(view.getContext().getResources().getColor(R.color.ab_text));
actv.setHintTextColor(view.getContext().getResources().getColor(R.color.hint_text));
}
}
You can use the attribute android:actionLayout instead which lets you specify a layout to be inflated. Just have a layout with your SearchView and you won't have to modify anything really.
As to changing text style on the SearchView that is probably not possible as the SearchView is a ViewGroup. You should probably try changing text color via themes instead.
In case anyone wants to modify the views directly, here is how you can change the colors/fonts/images and customize the search box to your pleasure. It is wrapped in a try/catch in case there are differences between versions or distributions, so it won't crash the app if this fails.
// SearchView structure as we currently understand it:
// 0 => linearlayout
// 0 => textview (not sure what this does)
// 1 => image view (the search icon before it's pressed)
// 2 => linearlayout
// 0 => linearlayout
// 0 => ImageView (Search icon on the left of the search box)
// 1 => SearchView$SearchAutoComplete (Object that controls the text, subclass of TextView)
// 2 => ImageView (Cancel icon to the right of the text entry)
// 1 => linearlayout
// 0 => ImageView ('Go' icon to the right of cancel)
// 1 => ImageView (not sure what this does)
try {
LinearLayout ll = (LinearLayout) searchView.getChildAt(0);
LinearLayout ll2 = (LinearLayout) ll.getChildAt(2);
LinearLayout ll3 = (LinearLayout) ll2.getChildAt(0);
LinearLayout ll4 = (LinearLayout) ll2.getChildAt(1);
TextView search_text = (TextView) ll3.getChildAt(1);
search_text.setTextColor(R.color.search_text);
ImageView cancel_icon = (ImageView)ll3.getChildAt(2);
ImageView accept_icon = (ImageView)ll4.getChildAt(0);
cancel_icon.setBackgroundDrawable(d);
accept_icon.setBackgroundDrawable(d);
} catch (Throwable e) {
Log.e("SearchBoxConstructor", "Unable to set the custom look of the search box");
}
This example shows changing the text color and the background colors of the cancel/accept images. searchView is a SearchView object already instantiated with it's background color:
Drawable d = getResources().getDrawable(R.drawable.search_widget_background);
searchView.setBackgroundDrawable(d);
Here is the drawable code:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/white" />
</shape>
Obviously, this is hacky, but it will work for now.
From ICS this is doable using themes and styles. I'm using ActionBarSherlock which makes it applicable also for HC and below.
Add a style to define "android:textColorHint":
<style name="Theme.MyHolo.widget" parent="#style/Theme.Holo">
<item name="android:textColorHint">#color/text_hint_corp_dark</item>
</style>
Apply this as "actionBarWidgetTheme" to your theme:
<style name="Theme.MyApp" parent="#style/Theme.Holo.Light.DarkActionBar">
...
<item name="android:actionBarWidgetTheme">#style/Theme.MyHolo.widget</item>
</style>
Presto! Make sure that you use getSupportActionBar().getThemedContext() (or getSupportActionBar() for ActionBarSherlock) if any widgets are initiated where you might have other themes in effect.
How do you inflate the menu xml in your Activity? if you inflate the menu by using getMenuInflator() in your Activity, then the menu and also the searchView get the themed context, that have attached to the activity.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater.inflate(R.menu.search_action_menu, menu);
}
if you check the source code of Activity.getMenuInflator() at API-15, you can see the themed context codes. Here it is.
*/
public MenuInflater getMenuInflater() {
// Make sure that action views can get an appropriate theme.
if (mMenuInflater == null) {
initActionBar();
if (mActionBar != null) {
mMenuInflater = new MenuInflater(mActionBar.getThemedContext());
} else {
mMenuInflater = new MenuInflater(this);
}
}
return mMenuInflater;
}