I am developing a tutorial for an application and I need to point at a particular icon in the toolbar.
Here is an extract of the XML for the action menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="#+id/AbErase"
android:title="#string/Erase"
android:icon="#android:drawable/ic_delete"
android:orderInCategory="10"
app:showAsAction="ifRoom|collapseActionView" />
<item android:id="#+id/AbSuggest"
android:title="#string/Suggest"
android:icon="#mipmap/ic_lightbulb_outline_white_48dp"
android:orderInCategory="50"
app:showAsAction="ifRoom|collapseActionView" />
<item android:id="#+id/AbUndo"
android:title="#string/ActionBarUndo"
android:icon="#android:drawable/ic_menu_revert"
android:orderInCategory="51"
app:showAsAction="ifRoom|collapseActionView" />
...
Here is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isBeingRestored = (savedInstanceState != null);
Toolbar scToolbar = (Toolbar) findViewById(R.id.Sc_toolbar);
setSupportActionBar(scToolbar);
scToolbar.post(new Runnable() {
#Override
public void run() {
if (!isBeingRestored) {
//View mErase = findViewById(R.id.AbErase);
View mErase = overflowMenu.getItem(0).getActionView();
int[] location = new int[2];
mErase.getLocationOnScreen(location);
eraseIconLeft = location[0];
}
}
}
With View mErase = findViewById(R.id.AbErase); mErase is set to null, **** start EDIT **** which is not suprising as AbErase is the id of a MenuItem, not the id of a View. **** end EDIT ****
With View mErase = overflowMenu.getItem(0).getActionView(); location is set to (0, 24), which is wrong as there already is a logo icon and a title in the toolbar.
How can I get the absolute X coordinate of the AbErase view in the toolbar?
**** EDIT ****
here is the code where the initialisation of the static variable overflowMenu can be found:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
actionBar.collapseActionView();
overflowMenu = menu;
isInitializedMenuItem = menu.findItem(R.id.AbInitialized);
isInitializedMenuItem.setChecked(isInitializeCbxChecked);
return super.onCreateOptionsMenu(menu);
}
Try the following code snippet
#Nullable
View getMenuItemView(Toolbar toolbar, #IdRes int menuItemId) throws IllegalAccessException,NoSuchFieldException {
Field mMenuView = Toolbar.class.getDeclaredField("mMenuView");
mMenuView.setAccessible(true);
Object menuView = mMenuView.get(toolbar);
Field mChildren = menuView.getClass() //android.support.v7.internal.view.menu.ActionMenuView
.getSuperclass() //android.support.v7.widget.LinearLayoutCompat
.getSuperclass() //android.view.ViewGroup
.getDeclaredField("mChildren");
mChildren.setAccessible(true);
View[] children = (View[]) mChildren.get(menuView);
for (View child : children) {
if (child.getId() == menuItemId) {
return child;
}
}
return null;
}
assuming android.support.v7.widget has been used. The above method would return a View corresponding to you menu item.
View menuItemView = getMenuItemView(scToolbar,R.id.AbErase);
int x = menuItemView.getX();
int y = menuItemView.getY();
and you have your coordinates.
EDIT
after a bit more research I found out reflection isn't required.
Before fetching the menuItemView we must wait for the Toolbar to finish its layout pass
scToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
scToolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
View menuItem = findViewById(R.id.AbErase);
if (menuItem != null) {
int[] location = new int[2];
menuItem.getLocationOnScreen(location);
int x = location[0]; //x coordinate
int y = location[1]; //y coordinate
}
}
});
the above snippet is tested to work, it may be placed in any convenient lifecycle callback method.
EDIT
On looking at the source for the activity the key things that must be remembered is that View menuItem = findViewById(R.id.AbErase); should only be called from a ViewTreeObserver.OnGlobalLayoutListener, onGlobalLayout callback.
In onCreate method is too early. The view is not measured yet. Override onPrepareOptionMenu() and put your code in it. Also remove your Runnable, its unnecessary.
So I have a menu item, that's defined as:
<item
android:id="#+id/action_live"
android:title="#string/action_live"
android:orderInCategory="1"
app:showAsAction="ifRoom|withText" />
It shows as text, as you can see below:
And I want to programmatically change the "LIVE" text color. I've searched for a while and I found a method:
With globally defined:
private Menu mOptionsMenu;
and:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
mOptionsMenu = menu;
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
I do:
MenuItem liveitem = mOptionsMenu.findItem(R.id.action_live);
SpannableString s = new SpannableString(liveitem.getTitle().toString());
s.setSpan(new ForegroundColorSpan(Color.RED), 0, s.length(), 0);
liveitem.setTitle(s);
But nothing happens!
If I do the same for an item of the overflow menu, it works:
Is there some limitation for app:showAsAction="ifRoom|withText" items? Is there any workaround?
Thanks in advance.
Bit late to the party with this one, but I spent a while working on this and found a solution, which may be of use to anyone else trying to do the same thing. Some credit goes to Harish Sridharan for steering me in the right direction.
You can use findViewById(R.id.MY_MENU_ITEM_ID) to locate the menu item (provided that the menu had been created and prepared), and cast it to a TextView instance as suggested by Harish, which can then be styled as required.
public class MyAwesomeActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
super.onCreate(savedInstanceState);
// Force invalidatation of the menu to cause onPrepareOptionMenu to be called
invalidateOptionsMenu();
}
private void styleMenuButton() {
// Find the menu item you want to style
View view = findViewById(R.id.YOUR_MENU_ITEM_ID_HERE);
// Cast to a TextView instance if the menu item was found
if (view != null && view instanceof TextView) {
((TextView) view).setTextColor( Color.BLUE ); // Make text colour blue
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_SP, 24); // Increase font size
}
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean result = super.onPrepareOptionsMenu(menu);
styleMenuButton();
return result;
}
}
The trick here is to force the menu to be invalidated in the activity's onCreate event (thereby causing the onPrepareMenuOptions to be called sooner than it would normally). Inside this method, we can locate the menu item and style as required.
#RRP give me a clue ,but his solution does not work for me. And #Box give a another, but his answer looks a little not so cleaner. Thanks them. So according to them, I have a total solution.
private static void setMenuTextColor(final Context context, final Toolbar toolbar, final int menuResId, final int colorRes) {
toolbar.post(new Runnable() {
#Override
public void run() {
View settingsMenuItem = toolbar.findViewById(menuResId);
if (settingsMenuItem instanceof TextView) {
if (DEBUG) {
Log.i(TAG, "setMenuTextColor textview");
}
TextView tv = (TextView) settingsMenuItem;
tv.setTextColor(ContextCompat.getColor(context, colorRes));
} else { // you can ignore this branch, because usually there is not the situation
Menu menu = toolbar.getMenu();
MenuItem item = menu.findItem(menuResId);
SpannableString s = new SpannableString(item.getTitle());
s.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, s.length(), 0);
item.setTitle(s);
}
}
});
}
In order to change the colour of menu item you can find that item, extract the title from it, put it in a Spannable String and set the foreground colour to it. Try out this code piece
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
MenuItem mColorFullMenuBtn = menu.findItem(R.id.action_submit); // extract the menu item here
String title = mColorFullMenuBtn.getTitle().toString();
if (title != null) {
SpannableString s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(Color.parseColor("#FFFFFF")), 0, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); // provide whatever color you want here.
mColorFullMenuBtn.setTitle(s);
}
return super.onCreateOptionsMenu(menu);
}
It only becomes a text view after inspection, its real class is ActionMenuItemView, on which we can further set the text color like this:
public static void setToolbarMenuItemTextColor(final Toolbar toolbar,
final #ColorRes int color,
#IdRes final int resId) {
if (toolbar != null) {
for (int i = 0; i < toolbar.getChildCount(); i++) {
final View view = toolbar.getChildAt(i);
if (view instanceof ActionMenuView) {
final ActionMenuView actionMenuView = (ActionMenuView) view;
// view children are accessible only after layout-ing
actionMenuView.post(new Runnable() {
#Override
public void run() {
for (int j = 0; j < actionMenuView.getChildCount(); j++) {
final View innerView = actionMenuView.getChildAt(j);
if (innerView instanceof ActionMenuItemView) {
final ActionMenuItemView itemView = (ActionMenuItemView) innerView;
if (resId == itemView.getId()) {
itemView.setTextColor(ContextCompat.getColor(toolbar.getContext(), color));
}
}
}
}
});
}
}
}
}
You could put the change of the color in the onPrepareOptionsMenu:
override fun onPrepareOptionsMenu(menu: Menu?): Boolean
{
val signInMenuItem = menu?.findItem(R.id.menu_main_sign_in)
val title = signInMenuItem?.title.toString()
val spannable = SpannableString(title)
spannable.setSpan(
ForegroundColorSpan(Color.GREEN),
0,
spannable.length,
Spannable.SPAN_INCLUSIVE_INCLUSIVE)
SgnInMenuItem?.title = spannable
return super.onPrepareOptionsMenu(menu)
}
of course you can make it shorter above...
now you can change the color appearance upon other (ie. viewmodel) values...
RG
I spent a lot of hours on this and finally got it into work. There is easy solusion for Android 6 and 7 but it doesn't work on Android 5. This code works on all of them. So, if you are doing it in Kotlin this is my suggestion:
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.my_menu, menu)
setToolbarActionTextColor(menu, R.color.black)
this.menu = menu
return true
}
private fun setToolbarActionTextColor(menu: Menu, color: Int) {
val tb = findViewById<Toolbar>(R.id.toolbar)
tb?.let { toolbar ->
toolbar.post {
val view = findViewById<View>(R.id.my_tag)
if (view is TextView) {
view.setTextColor(ContextCompat.getColor(this, color))
} else {
val mi = menu.findItem(R.id.my_tag)
mi?.let {
val newTitle: Spannable = SpannableString(it.title.toString())
val newColor = ContextCompat.getColor(this, color)
newTitle.setSpan(ForegroundColorSpan(newColor),
0, newTitle.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
it.title = newTitle
}
}
}
}
}
It's complicated, but you can use the app:actionLayout attribute. For example,
my_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/englishList"
android:orderInCategory="1"
app:showAsAction="ifRoom|withText"
app:actionLayout="#layout/custom_menu_item_english_list"
android:title=""/>
</menu>
custom_menu_item_english_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/englishListWhiteText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:lineHeight="16dp"
android:textSize="16sp"
android:text="英文"
android:layout_marginRight="8dp"/>
</LinearLayout>
MainActivity.java:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.my_menu, menu);
MenuItem item = menu.findItem(R.id.englishList);
item.getActionView().findViewById(R.id.englishListWhiteText)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v){
//Handle button click.
}
});
return true;
}
Result:
More Detailed Example=
https://medium.com/#info.anikdey003/custom-menu-item-using-action-layout-7a25118b9d5
if you are using popup menu function to show the menu items in the application and trying to change the design or color of your text items in the menu list, first create a style item in your style.xml file:
<style name="PopupMenuStyle" parent="Widget.AppCompat.PopupMenu">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#color/ColorPrimary</item>
<item name="android:textSize">#dimen/textsize</item>
<item name="android:fontFamily">#font/myfonts</item></style>
and use this style in your code as:
val popupWrapper = ContextThemeWrapper(this, R.style.PopupMenuStyle)
val popup = PopupMenu(popupWrapper, your_menu_view)
MenuItem as defined by documentation is an interface. It will definitely be implemented with a view widget before being portrayed as an menu. Most cases these menu items are implemented as TextView. You can use UiAutomatorViewer to see the view hierarchy or even use hierarchyviewer which will be found in [sdk-home]/tools/. Attached one sample uiautomatorviewer screenshot for a MenuItem
So you can always typecast your MenuItem and set the color.
TextView liveitem = (TextView)mOptionsMenu.findItem(R.id.action_live);
liveitem.setTextColor(Color.RED);
EDIT:
Since there was request to see how to use this tool, I'm adding a few more contents.
Make sure you have set environment variable $ANDROID_HOME pointing to your SDK HOME.
In your terminal:
cd $ANDROID_HOME
./tools/uiautomatorviewer
This tool will open up.
The second or third button (refer screenshot) in the menu will capture the screenshot of your attached device or emulator and you can inspect the view and their hierarchy. Clicking on the view will describe the view and their information. It is tool purposely designed for testing and you can inspect any application.
Refer developer site for more info: uiautomatorviewer
I want to have similar menu item functionality as in the chrome browser for mobile as it is in the picture. I want to have back, forward, refresh menu items in a single row. How can I implement a similar menu item? is there any reference or is there is any hack to bring this functionality?
My app is aimed only for tablets. Here is my current Action bar menu item:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/favorite"
android:showAsAction="always"
android:title="Happy"/>
<item
android:id="#+id/share_button"
android:icon="#drawable/share"
android:showAsAction="always"
android:title="Share"/>
<item
android:id="#+id/hola_button"
android:showAsAction="always"
android:title="Hola"/>
<item
android:icon="#drawable/more_actions"
android:showAsAction="always">
<menu>
<item
android:id="#+id/back_button"
android:icon="#drawable/back"
android:title="back"/>
<item
android:id="#+id/forward_button"
android:icon="#drawable/forward"
android:title="forward"/>
<item
android:id="#+id/refresh_button"
android:icon="#drawable/refresh"
android:title="refresh"/>
</menu>
</item>
</menu>
This looks like a customized Dialog with a listview and a custom listheader
OR
a listview below a simple layout with 3 buttons on top within a dialog.
You could show the same on the actionbar menu item click.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.menu_items, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.show_dlg:
// Show your custom dialog
break;
}
return super.onOptionsItemSelected(item);
}
Also this tutorial would help as a reference for inflating custom menus
http://www.codeproject.com/Articles/173121/Android-Menus-My-Way
EDIT:
This examples is as an OverflowMenu (since ABS ditched the overflow-theme). you can inflate any kind of layout-combinations. This class extends from PopUpWindow and doesn't use the typical onCreateOptions. It uses the ABS-CustomView and a PopUpWindow to create menu's.
From android reference: A popup window that can be used to display an arbitrary view. The popup window is a floating container that appears on top of the current activity.
The layout looks similar to your requested layout. This view is anchored to the ActionBar but you can display it anywhere you want. Popup window supports many show functions out of the box.
Customizable OverflowMenu
public class OverflowMenu extends PopupWindow {
private View mContentView;
public OverflowMenu(Context context, int resourceId) {
super(context);
mContentView = LayoutInflater.from(context).inflate(resourceId, null);
setContentView(mContentView);
setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
// Handle touchevents
setOutsideTouchable(true);
setFocusable(true);
setBackgroundDrawable(new BitmapDrawable());
}
/**
* Attach the OverflowMenu View to the ActionBar's Right corner
* #param actionBarView
*/
public void show(View actionBarView) {
int x = actionBarView.getMeasuredWidth() - mContentView.getMeasuredWidth();
showAsDropDown(actionBarView, x, 0);
}
/**
* Return mContentView,
* used for mContentView.findViewById();
* #return
*/
public View getView(){
return mContentView;
}
}
MainActivity
public class MainActivity extends SherlockActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mai n);
final ActionBar actionBar = getSupportActionBar();
actionBar.setCustomView(R.layout.actionbar);
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
final OverflowMenu menu = new OverflowMenu(this,R.layout.menu_overflow);
final ImageButton overflowBtn = (ImageButton) actionBar.getCustomView().findViewById(R.id.actionbar_overflow);
overflowBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
menu.show(actionBar.getCustomView());
}
});
}
}
You could add the menu item to the page with something like this :
OptionCommand command = new OptionCommand();
command.setActionView(view);
command.setIcon(canvas.getContext().getResources().getDrawable(android.R.drawable.ic_menu_search));
command.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
canvas.getActivity().getOptionCommands().add(command);
Having
<item name="android:actionBarDivider">#drawable/action_bar_divider</item>
in my Activity style has zero effect. Doesn't matter if I'm using native ActionBar or ActionBarSherlock. Why?
I worked around it by programatically inserting dividers into the view hierarchy. It occured to me later that dividers may be only visible in the bottom action bar (I needed them at the top bar). But I didn't verify that.
public boolean onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu) {
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.main_activity, menu);
new Handler().post(new Runnable() {
#Override
public void run() {
for (int id : new int[] { R.id.action_1, R.id.action_2 }) {
View actionView = findViewById(id);
insertDividerBefore(actionView);
}
}
});
return true;
}
android:actionBarDivider
Defines a drawable resource for the divider between action items. (Added in API level 14.)
Make sure whether you are using API level 14 or later.
I would like to display badges on menu items. How do I do it?
Basically, I don't want to draw or use the canvas to do so.
You could try creating a LayerListDrawable, with your regular icon as the first layer and your badge as the second layer, then use that with setIcon() on MenuItem.
The options menu in Android can be customized to set the background or change the text appearance. The background and text color in the menu couldn’t be changed using themes and styles.
The Android source code (data\res\layout\icon_menu_item_layout.xml) uses a custom item of class “com.android.internal.view.menu.IconMenuItem”View for the menu layout. We can make changes in the above class to customize the menu. To achieve the same, use the LayoutInflater factory class and set the background and text color for the view.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.my_menu, menu);
getLayoutInflater().setFactory(new Factory() {
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if (name .equalsIgnoreCase(“com.android.internal.view.menu.IconMenuItemView”)) {
try{
LayoutInflater f = getLayoutInflater();
final View view = f.createView(name, null, attrs);
new Handler().post(new Runnable() {
public void run() {
// Set the background drawable
view .setBackgroundResource(R.drawable.my_ac_menu_background);
// Set the text color
((TextView) view).setTextColor(Color.WHITE);
}
});
return view;
}
catch (InflateException e) {
}
catch (ClassNotFoundException e) {
}
}
return null;
}
});
return super.onCreateOptionsMenu(menu);
}
Menuitem has an attribute icon, for example:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/mi_main_preferences"
android:title="#string/cmd_preferences"
android:icon="#android:drawable/ic_menu_preferences">
</item>
</menu>
Above example uses system icon (preferences menu).