I need to use certain font for my entire application. I have .ttf file for the same. Is it possible to set this as default font, at application start up and then use it else
I wrote a class assigning typeface to the views in the current view hierarchy and based os the current typeface properties (bold, normal, you can add other styles if you want):
public final class TypefaceAssigner {
public final Typeface DEFAULT;
public final Typeface DEFAULT_BOLD;
@Inject
public TypefaceAssigner(AssetManager assetManager) {
DEFAULT = Typeface.createFromAsset(assetManager, "TradeGothicLTCom.ttf");
DEFAULT_BOLD = Typeface.createFromAsset(assetManager, "TradeGothicLTCom-Bd2.ttf");
}
public void assignTypeface(View v) {
if (v instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) v).getChildCount(); i++) {
View view = ((ViewGroup) v).getChildAt(i);
if (view instanceof ViewGroup) {
setTypeface(view);
} else {
setTypeface(view);
}
}
} else {
setTypeface(v);
}
}
private void setTypeface(View view) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
Typeface typeface = textView.getTypeface();
if (typeface != null && typeface.isBold()) {
textView.setTypeface(DEFAULT_BOLD);
} else {
textView.setTypeface(DEFAULT);
}
}
}
}
Now in all fragments in onViewCreated or onCreateView, in all activities in onCreate and in all view adapters in getView or newView just invoke:
typefaceAssigner.assignTypeface(view);
Finally, Google realized the severity of this problem (applying custom font to UI components) and they devised a clean solution for it.
First, you need to update to support library 26+ (you may also need to update your gradle{4.0+}, android studio), then you can create a new resource folder called font. In this folder, you can put your font resources (.tff,...). Then you need to override the default app them and force your custom font into it :)
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:fontFamily">@font/my_custom_font</item>
</style>
Note: if you want to support devices with older API than 16, you have to use app namespace instead of android!
While this would not work for an entire application, it would work for an Activity and could be re-used for any other Activity. I've updated my code thanks to @FR073N to support other Views. I'm not sure about issues with Buttons
, RadioGroups
, etc. because those classes all extend TextView
so they should work just fine. I added a boolean conditional for using reflection because it seems very hackish and might notably compromise performance.
Note: as pointed out, this will not work for dynamic content! For that, it's possible to call this method with say an onCreateView
or getView
method, but requires additional effort.
/**
* Recursively sets a {@link Typeface} to all
* {@link TextView}s in a {@link ViewGroup}.
*/
public static final void setAppFont(ViewGroup mContainer, Typeface mFont, boolean reflect)
{
if (mContainer == null || mFont == null) return;
final int mCount = mContainer.getChildCount();
// Loop through all of the children.
for (int i = 0; i < mCount; ++i)
{
final View mChild = mContainer.getChildAt(i);
if (mChild instanceof TextView)
{
// Set the font if it is a TextView.
((TextView) mChild).setTypeface(mFont);
}
else if (mChild instanceof ViewGroup)
{
// Recursively attempt another ViewGroup.
setAppFont((ViewGroup) mChild, mFont);
}
else if (reflect)
{
try {
Method mSetTypeface = mChild.getClass().getMethod("setTypeface", Typeface.class);
mSetTypeface.invoke(mChild, mFont);
} catch (Exception e) { /* Do something... */ }
}
}
}
Then to use it you would do something like this:
final Typeface mFont = Typeface.createFromAsset(getAssets(),
"fonts/MyFont.ttf");
final ViewGroup mContainer = (ViewGroup) findViewById(
android.R.id.content).getRootView();
HomeActivity.setAppFont(mContainer, mFont);
Hope that helps.
Yes with reflection. This works (based on this answer):
(Note: this is a workaround due to lack of support for custom fonts, so if you want to change this situation please do star to up-vote the android issue here). Note: Do not leave "me too" comments on that issue, everyone who has stared it gets an email when you do that. So just "star" it please.
import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Typeface;
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();
}
}
}
You then need to overload the few default fonts, for example in an application class:
public final class Application extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
FontsOverride.setDefaultFont(this, "DEFAULT", "MyFontAsset.ttf");
FontsOverride.setDefaultFont(this, "MONOSPACE", "MyFontAsset2.ttf");
FontsOverride.setDefaultFont(this, "SERIF", "MyFontAsset3.ttf");
FontsOverride.setDefaultFont(this, "SANS_SERIF", "MyFontAsset4.ttf");
}
}
Or course if you are using the same font file, you can improve on this to load it just once.
However I tend to just override one, say "MONOSPACE"
, then set up a style to force that font typeface application wide:
<resources>
<style name="AppBaseTheme" parent="android:Theme.Light">
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:typeface">monospace</item>
</style>
</resources>
I've investigated the reports in the comments that it doesn't work and it appears to be incompatible with the theme android:Theme.Material.Light
.
If that theme is not important to you, use an older theme, e.g.:
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<item name="android:typeface">monospace</item>
</style>
I would also like to improve weston's answer for API 21 Android 5.0.
I had the same issue on my Samsung s5, when using DEFAULT font. (with the others fonts it's working fine)
I managed to make it working by setting the typeface ("sans" for example) in XML files, for each Textview or Button
<TextView
android:layout_width="match_parent"
android:layout_height="39dp"
android:textColor="@color/abs__background_holo_light"
android:textSize="12sp"
android:gravity="bottom|center"
android:typeface="sans" />
and in MyApplication Class :
public class MyApplication extends Application {
@Override
public void onCreate() {
TypefaceUtil.overrideFont(getApplicationContext(), "SANS_SERIF",
"fonts/my_font.ttf");
}
}
Hope it helps.
As of Android O this is now possible to define directly from the XML and my bug is now closed!
See here for details
TL;DR:
First you must add your fonts to the project
Second you add a font family, like so:
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:fontStyle="normal"
android:fontWeight="400"
android:font="@font/lobster_regular" />
<font
android:fontStyle="italic"
android:fontWeight="400"
android:font="@font/lobster_italic" />
</font-family>
Finally, you can use the font in a layout or style:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/lobster"/>
<style name="customfontstyle" parent="@android:style/TextAppearance.Small">
<item name="android:fontFamily">@font/lobster</item>
</style>
Enjoy!