How to set custom typeface to items in NavigationView?

With the new NavigationView, we are able to set a drawer's items through a menu resource in XML.

With that, we can set each item with something like

<item
  android:id="@+id/drawer_my_account"
  android:icon="@drawable/ic_my_account"
  android:title="@string/drawer_my_account" />

But now, I want to set a custom typeface to each of those items in my drawer, and I could not find a way to do that, either by XML or by Java code. Is there a way to do it?


just add following class file to your project.

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);
    }
}

then create following method to your activity

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

and call it from activity.

navView = (NavigationView) findViewById(R.id.navView);
        Menu m = navView.getMenu();
        for (int i=0;i<m.size();i++) {
            MenuItem mi = m.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);
                }
            }

            //the method we have create in activity
            applyFontToMenuItem(mi);
        }

and here is my output

enter image description here


this one working for me

<android.support.design.widget.NavigationView
       android:id="@+id/navigation_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       android:background="#4A4444"
       android:clipToPadding="false"
       android:paddingBottom="50dp"
       app:itemIconTint="@color/white"
       app:menu="@menu/drawer_home"
       app1:itemTextAppearance="@style/NavigationDrawerStyle" >
</android.support.design.widget.NavigationView>

res->values->styles

 <style name="NavigationDrawerStyle">
    <item name="android:textSize">18sp</item>
    <item name="android:typeface">monospace</item>
</style>

//to Set Custom Typeface MainApplication.java

public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

//set Custom Typeface

        FontsOverride.setDefaultFont(this, "MONOSPACE", "OpenSans-Semibold.ttf");
    }
}

// FontsOverride.java

public final class FontsOverride {

     public static void setDefaultFont(Context context,
                String staticTypefaceFieldName, String fontAssetName) {
            final Typeface regular = Typeface.createFromAsset(context.getAssets(),
                    fontAssetName);
            replaceFont(staticTypefaceFieldName, regular);
        }

        protected static void replaceFont(String staticTypefaceFieldName,
                final Typeface newTypeface) {
            try {
                final Field staticField = Typeface.class
                        .getDeclaredField(staticTypefaceFieldName);
                staticField.setAccessible(true);

                staticField.set(null, newTypeface);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }


}

Use the app:itemTextAppearance="" property. Hope this helps.

 <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        android:background="@drawable/nav_bg_gradient"
        android:theme="@style/NavigationView"
        app:itemIconTint="@color/colorWhite"
        app:itemTextColor="@color/colorWhite"
        app:itemTextAppearance="@style/NavigationText"
        app:menu="@menu/main_drawer">

In styles.xml write

<style name="NavigationText" parent="@android:style/TextAppearance.Medium">
        <item name="android:textColor">@color/colorWhite</item>
        <item name="android:textSize">12sp</item>
        <item name="android:fontFamily">sans-serif-thin</item>
    </style>

Is there a way to do it?

Yes. The NavigationView doesn't provide a direct way of handling this, but it can easily be achieved using View.findViewsWithText.

There are two things that will help us handle this.

  1. Each MenuItem view is a TextView. So, that makes applying your Typeface that much easier. For more information on the TextView actually used by NavigationView, see NavigationMenuItemView.
  2. NavigationView provides a callback when a MenuItem is selected. We're going to have to supply each MenuItem with a unique id and this callback will help generify those ids as much as possible, which means a little less code later on. Although, this is more related to whether or not you have a SubMenu.

Implementation

Notice each MenuItem id is simply menuItem+Position. This will come in handy later when we're finding the View for each MenuItem.

<group android:checkableBehavior="single">
    <item
        android:id="@+id/menuItem1"
        android:icon="@drawable/ic_dashboard"
        android:title="MenuItem 1" />
    <item
        android:id="@+id/menuItem2"
        android:icon="@drawable/ic_event"
        android:title="MenuItem 2" />
    <item
        android:id="@+id/menuItem3"
        android:icon="@drawable/ic_headset"
        android:title="MenuItem 3" />
    <item
        android:id="@+id/menuItem4"
        android:icon="@drawable/ic_forum"
        android:title="MenuItem 4" />
</group>

<item android:title="Sub items" >
    <menu>
        <item
            android:id="@+id/menuItem5"
            android:icon="@drawable/ic_dashboard"
            android:title="Sub item 5" />
        <item
            android:id="@+id/menuItem6"
            android:icon="@drawable/ic_forum"
            android:title="Sub item 6" />
    </menu>
</item>


/** The total number of menu items in the {@link NavigationView} */
private static final int MENU_ITEMS = 6;
/** Contains the {@link MenuItem} views in the {@link NavigationView} */
private final ArrayList<View> mMenuItems = new ArrayList<>(MENU_ITEMS);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    final NavigationView navView = ...
    // Grab the NavigationView Menu
    final Menu navMenu = navView.getMenu();
    // Install an OnGlobalLayoutListener and wait for the NavigationMenu to fully initialize
    navView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Remember to remove the installed OnGlobalLayoutListener
            navView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            // Loop through and find each MenuItem View
            for (int i = 0, length = MENU_ITEMS; i < length; i++) {
                final String id = "menuItem" + (i + 1);
                final MenuItem item = navMenu.findItem(getResources().getIdentifier(id, "id", getPackageName()));
                navView.findViewsWithText(mMenuItems, item.getTitle(), View.FIND_VIEWS_WITH_TEXT);
            }
            // Loop through each MenuItem View and apply your custom Typeface
            for (final View menuItem : mMenuItems) {
                ((TextView) menuItem).setTypeface(yourTypeface, Typeface.BOLD);
            }
        }
    });
}

You can see how using a generic MenuItem id allows you to utilize Resources.getIdentifier and save a few lines of code.

SubMenu caveat

Something to keep in mind. You need to explicitly loop over your N menu items rather than using Menu.size. Otherwise, your SubMenu items won't be recognized. In other words, if you don't have a SubMenu, another way of doing this would be:

for (int i = 0, length = navMenu.size(); i < length; i++) {
    final MenuItem item = navMenu.getItem(i);
    navigationView.findViewsWithText(mMenuItems, item.getTitle(), View.FIND_VIEWS_WITH_TEXT);
}

And you don't have to worry about applying a unique id to each MenuItem.

Results

results

The font I'm using in the example is: Smoothie Shoppe


I have used app:theme

<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:background="@color/colorMenuBackground"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header_main"
    app:menu="@menu/activity_main_drawer"
    app:theme="@style/NavigationViewTextAppearance"
   />

styles.xml :

<style name="NavigationViewTextAppearance">
    <item name="android:ellipsize">end</item>
    <item name="android:fontFamily">@font/badscript_regular</item>
</style>