How to set a font for the Options menu?
Asked Answered
L

7

35

When I create an Options Menu the items seem to default to the native "sans" font. When I look at commercial apps they mostly seem to do the same thing. Is it possible to set the font size, color weight or typeface for Option Menu items?

Thanks in advance.

Libbey answered 9/11, 2010 at 15:57 Comment(1)
Trying something of the same. But no much success. But here is some a link to the reading. developer.android.com/guide/topics/ui/menus.html#options-menu. It is recommended not to disturb the default information provided. But one can create a view to do customization.Lucan
B
65

You can customize the option menu, including:

  1. Add a custom font

  2. Change font size

  3. Change font color

  4. Set background to a Drawable resource (e.g. image, border, gradient)

To change background to a border or gradient you have to create a resource folder in res called drawable and, inside it, create the border XML or gradient XML.

This can all be done programatically as shown below:

public class CustomMenu extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public boolean onCreateOptionsMenu(android.view.Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.cool_menu, menu);
        getLayoutInflater().setFactory(new Factory() {
            public View onCreateView(String name, Context context,
                    AttributeSet attrs) {

                if (name.equalsIgnoreCase(
                        "com.android.internal.view.menu.IconMenuItemView")) {
                    try {
                        LayoutInflater li = LayoutInflater.from(context);
                        final View view = li.createView(name, null, attrs);
                        new Handler().post(new Runnable() {
                            public void run() {
                                // set the background drawable if you want that
                                //or keep it default -- either an image, border
                                //gradient, drawable, etc.
                                view.setBackgroundResource(R.drawable.myimage);
                                ((TextView) view).setTextSize(20); 

                                // set the text color
                                Typeface face = Typeface.createFromAsset(
                                        getAssets(),"OldeEnglish.ttf");     
                                ((TextView) view).setTypeface(face);
                                ((TextView) view).setTextColor(Color.RED);
                            }
                        });
                        return view;
                    } catch (InflateException e) {
                        //Handle any inflation exception here
                    } catch (ClassNotFoundException e) {
                        //Handle any ClassNotFoundException here
                    }
                }
                return null;
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.AboutUs:
            Intent i = new Intent("com.test.demo.ABOUT");
            startActivity(i);
            break;
        case R.id.preferences:
            Intent p = new Intent("com.test.demo.PREFS");
            startActivity(p);
            break;
        case R.id.exit:
            finish();
            break;
        }
        return false;
    }
}

Dont forget to create folder called menu in res folder, and inside the menu folder create an XML for your menu (e.g. cool_menu.xml) such as this:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item  android:title="about"android:id="@+id/AboutUs" /> 
    <item android:title="Prefs" android:id="@+id/preferences" /> 
    <item android:title="Exit" android:id="@+id/exit" /> 
</menu>

Then the output will be something like this:

enter image description here

Broddie answered 7/7, 2012 at 16:10 Comment(14)
Hi, is it possible to change font dynamically? I mean, instead of setting font in OnCreateOptionsMenu(), i would like to set it in OnPrepareOptionsMenu() so that at different point of time, my menu will have different fonts. Is it possible?Seedcase
Or, if setting different font to different menu items, then i can set font in OnCreateOptionsMenu() and i ll necessary menus visible and unnecessary onesinvisible in OnPrepareOptionsMenu(). Is it possible?Seedcase
Hi, when I run the code above (slightly modified, see following comment), the appearance of the menu items is only changed the first time I open the options menu. If I exit the options menu (by selecting an option, pressing the back button or pressing the menu button) and then open it again, it's appearance is restored to the default appearance. How can I make the modifications persistent? I'm developing for API level 8.Leonardaleonardi
The modifications I made when running the above code: I don't set the background resource, I don't change the typeface and instead of starting a new activity I only log an info message to LogCat. The rest is unmodified.Leonardaleonardi
@jellefresen you said y dont change back ground or typeface so what modification you did , you just have the default option menu , am i right , and explain more whats the appearance you get in first open that change later to default .Broddie
@AndroidStack Sorry for the late reply. I change the text size and the text color, resulting in an options menu where the text in the options is red and larger then usual. The second time I invoke the options menu, the text is white again and of default size (don't know what size exactly, but something strictly smaller then 20).Leonardaleonardi
Why properties setting must be done in handler? Doesn't handler run on the same thread?Lightness
This code did not work with me. I don't have "com.android.internal.view.menu.IconMenuItemView" . even if I removed the if statement on "com.android.internal.view.menu.IconMenuItemView" noting happens.Tombolo
Hi,i want to change overflow menu item.. can u tell me class for it.. like IconMenuItemView. This wont work for me..Vagarious
it didn't work for me, I get the next exception: A factory has already been set on this LayoutInflaterConidium
Didn't work for me. Application closed unexpectedly!Buyse
Same for me as well - "A factory has already been set on this LayoutInflater"Laina
Anyone found any solutions for this yet ??Lavaliere
I get this err: java.lang.IllegalStateException: A factory has already been set on this LayoutInflaterLotion
O
10

@Android Stack, when I read your answer I started to panic thinking that I would have to use a "factory".

I searched around for a bit, and I learned that you can use custom views for menu items. Simply call setActionView on the menu item.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    // Inflate the menu items for use in the action bar
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.my_menu, menu);

    // Get the root inflator. 
    LayoutInflater baseInflater = (LayoutInflater)getBaseContext()
           .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    // Inflate your custom view.
    View myCustomView = baseInflater.inflate(R.layout.my_custom_view, null);
    menu.findItem(R.id.my_custom_menu_icon).setActionView(myCustomView);


    // If myCustomView has additional children, you might have to inflate them separately here.
    // In my case, I used buttons in my custom view, and registered onClick listeners at this point.

 }

Your implementation of my_custom_view can be any view you want (although it might have to have a LinearLayout as the root element). For instance, you can use the TextView + ImageView layout that @R4j proposes in his answer.

In my use case, I simply put Button objects into the menu, and then relied on the onButtonClick handler of the buttons to respond to events -- effectively side-stepping the need to handle them in the activity containing the menu.

(Great question, by the way. Thanks!!)

Ogburn answered 29/6, 2014 at 1:3 Comment(1)
I'd like to add to this answer that, as we're changing the view on the menu, the onOptionsItemSelected callback won't be called when the user taps on the menu item, so we'll have to add an onClickListener to our customView.Blueberry
M
8

Tested and work like charm :)

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_feedback_filter, menu);

    for (int i = 0; i < menu.size(); i++) {
        MenuItem mi = menu.getItem(i);
        //for aapplying a font to subMenu ...
        SubMenu subMenu = mi.getSubMenu();
        if (subMenu != null && subMenu.size() > 0) {
            for (int j = 0; j < subMenu.size(); j++) {
                MenuItem subMenuItem = subMenu.getItem(j);
                applyFontToMenuItem(subMenuItem, typeface);
            }
        }
        //the method we have create in activity
        applyFontToMenuItem(mi, typeface);
    }

    return super.onCreateOptionsMenu(menu);
}



private void applyFontToMenuItem(MenuItem mi, Typeface font) {
    SpannableString mNewTitle = new SpannableString(mi.getTitle());
    mNewTitle.setSpan(new CustomTypefaceSpan("", font), 0, mNewTitle.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    mi.setTitle(mNewTitle);
}

Custom span class

import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.TypefaceSpan;

public class CustomTypefaceSpan extends TypefaceSpan {

    private final Typeface newType;

    public CustomTypefaceSpan(String family, Typeface type) {
        super(family);
        newType = type;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        applyCustomTypeFace(ds, newType);
    }

    @Override
    public void updateMeasureState(TextPaint paint) {
        applyCustomTypeFace(paint, newType);
    }

    private static void applyCustomTypeFace(Paint paint, Typeface tf) {
        int oldStyle;
        Typeface old = paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }

        int fake = oldStyle & ~tf.getStyle();
        if ((fake & Typeface.BOLD) != 0) {
            paint.setFakeBoldText(true);
        }

        if ((fake & Typeface.ITALIC) != 0) {
            paint.setTextSkewX(-0.25f);
        }

        paint.setTypeface(tf);
    }
}
Mcclees answered 1/3, 2017 at 13:53 Comment(1)
It works for overflow menu but not for Action menu. Am i missing something?Maverick
D
4

Instead of Using the XML Resource for Menu, inflate it from code using menu.add and use new SpannableString() for assigning a custom font.

Here is an example working on Android 4.x:

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
                                ContextMenu.ContextMenuInfo menuInfo) {
    ...
    menu.add(Menu.NONE,1234,1,wrapInSpan(getResources().getString(R.string.item_title)))
        .setTitleCondensed(getResources().getString(R.string.item_title));
    ...
}

private CharSequence wrapInSpan(CharSequence value) {
    SpannableStringBuilder sb = new SpannableStringBuilder(value);
    sb.setSpan(MY_TYPEFACE, 0, value.length(), 0);
    return sb;
}

The setTitleCondensed(...) is required to workaround a bug in android API : when a menu item is selected the event is logged and it use the titleCondensed to write the log. If the titleCondensed is not defined it use the title and the EventLog.writeEvent crashes whenever the string to log is formatted.

So passing an non-formated CharSequence in the consendedTitle workaround the bug.

Dress answered 5/11, 2013 at 4:4 Comment(1)
This does not work when Menu kept as showAsAction="always". Can anyone resolve the issue for that with this code ?Lavaliere
T
3

None of the answers above worked for me. I achieved this by following solution:

public boolean onPrepareOptionsMenu(Menu menu)
    {
        MenuItem item = menu.findItem(R.id.menu_name);
        item.setTitle(someTextToDisplayOnMenu);
        SpannableString spanString = new SpannableString(item.getTitle().toString());
        spanString.setSpan(new TextAppearanceSpan(context,android.R.style.TextAppearance_Medium), 0,spanString.length(), 0);
        spanString.setSpan(new ForegroundColorSpan(Color.WHITE), 0, spanString.length(), 0); //fix the color to white
        item.setTitle(spanString);
        return true;
    }
Tajuanatak answered 11/8, 2017 at 17:15 Comment(0)
M
1

I think Android doesn't support customization for option menu. But you can try another way: http://www.codeproject.com/Articles/173121/Android-Menus-My-Way
In this way, actually menu item is a textview and imageview, so you can easily change font, color...

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:padding="4dip"
android:clickable="true"
android:background="@drawable/custom_menu_selector">
<ImageView
    android:id="@+id/custom_menu_item_icon"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:paddingBottom="2dip"
    android:paddingTop="2dip"/>
<TextView
    android:id="@+id/custom_menu_item_caption"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="#ffffff"
    android:textSize="12sp"
    android:gravity="center"/>

Mucilage answered 7/7, 2012 at 5:55 Comment(0)
C
1

The only solution I found was create a custom dialog what appears when you press the menu button. The layout would be like that:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Mi cuenta"
        android:id="@+id/buttonMyAccount" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Ayuda"
        android:id="@+id/buttonHelp" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Contacto"
        android:id="@+id/buttonContact" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Acerca de"
        android:id="@+id/buttonAbout" />
</LinearLayout>

After that, from Activity class, in the 'OnOptionsItemSelected' method I write the following code:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

            case R.id.action_settings:
            Dialog dialog = new Dialog(this);
            dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            dialog.setContentView(R.layout.options_menu);
            dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));

            dialog.show();


            Button buttonMyAccount = (Button) dialog.findViewById(R.id.buttonMyAccount);
            Typeface font = Typeface.createFromAsset(this.getAssets(), "SamsungIF_Rg.ttf");
            buttonMyAccount.setTypeface(font);
            buttonMyAccount.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent itMyAccount = new Intent(getBaseContext(), AccountActivity.class);
                    startActivity(itMyAccount);
                }
            });


            Button buttonHelp = (Button) dialog.findViewById(R.id.buttonHelp);
            buttonHelp.setTypeface(font);
            buttonHelp.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent itAssistant = new Intent(getBaseContext(), AssistantPagerActivity.class);
                    startActivity(itAssistant);
                }
            });


            Button buttonContact = (Button) dialog.findViewById(R.id.buttonContact);
            buttonContact.setTypeface(font);
            buttonContact.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent itContact = new Intent(getBaseContext(), ContactActivity.class);
                    startActivity(itContact);
                }
            });

            Button buttonAbout = (Button) dialog.findViewById(R.id.buttonAbout);
            buttonAbout.setTypeface(font);
            buttonAbout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent itAbout = new Intent(getBaseContext(), AboutActivity.class);
                    startActivity(itAbout);
                }
            });


            Window window = dialog.getWindow();
            WindowManager.LayoutParams wlp = window.getAttributes();
            wlp.gravity = Gravity.RIGHT | Gravity.TOP;
            wlp.y = getSupportActionBar().getHeight();
            wlp.width = 300;
            wlp.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            window.setAttributes(wlp);


            return true;

        default:
            return super.onOptionsItemSelected(item);

    }
}
Conidium answered 14/4, 2014 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.