使用 xml 在 android TextView 中使用自定义字体

我已经添加了一个自定义字体文件到我的资产/字体文件夹。我如何使用它从我的 XML?

我可以通过以下代码使用它:

TextView text = (TextView) findViewById(R.id.textview03);
Typeface tf = Typeface.createFromAsset(getAssets(), "fonts/Molot.otf");
text.setTypeface(tf);

我不能使用 android:typeface="/fonts/Molot.otf"属性从 XML 进行操作吗?

136163 次浏览

Not a good idea to use custom fonts in xml due to this fact that is, you have to do it programmatically to avoid the memory leak!

Here is example code that does this. I have the font defined in a static final variable and the font file is in the assets directory.

public class TextViewWithFont extends TextView {


public TextViewWithFont(Context context, AttributeSet attrs) {
super(context, attrs);
this.setTypeface(MainActivity.typeface);
}


public TextViewWithFont(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setTypeface(MainActivity.typeface);
}


public TextViewWithFont(Context context) {
super(context);
this.setTypeface(MainActivity.typeface);
}


}

Create your customed TextView belong to the font you want to use. In this class, I use a static mTypeface field to cache the Typeface (for better performance)

public class HeliVnTextView extends TextView {


/*
* Caches typefaces based on their file path and name, so that they don't have to be created every time when they are referenced.
*/
private static Typeface mTypeface;


public HeliVnTextView(final Context context) {
this(context, null);
}


public HeliVnTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}


public HeliVnTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);


if (mTypeface == null) {
mTypeface = Typeface.createFromAsset(context.getAssets(), "HelveticaiDesignVnLt.ttf");
}
setTypeface(mTypeface);
}


}

In xml file:

<java.example.HeliVnTextView
android:id="@+id/textView1"
android:layout_width="0dp"
... />

In java class:

HeliVnTextView title = new HeliVnTextView(getActivity());
title.setText(issue.getName());

Activity implements LayoutInflater.Factory2 that provides callbacks on each created View. It's possible to style the TextView with custom font Family attribute, load the typefaces on demand and call setTypeface on instantiated text views automatically.

Unfortunately due to the architectural relationship of Inflater instances relative to Activities and Windows the simplest approach to use custom fonts in android is to cache loaded fonts on the Application level.

The sample code base is here:

https://github.com/leok7v/android-textview-custom-fonts

  <style name="Baroque" parent="@android:style/TextAppearance.Medium">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#F2BAD0</item>
<item name="android:textSize">14pt</item>
<item name="fontFamily">baroque_script</item>
</style>


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/custom.fonts"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
style="@style/Baroque"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sample_text"
/>

results in

enter image description here

Short answer: No. Android doesn't have built-in support for applying custom fonts to text widgets through XML.

However, there's a workaround that's not terribly difficult to implement.

First

You'll need to define your own stylable. In your /res/values folder, open/create the attrs.xml file and add a declare-styleable object like so:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FontText">
<attr name="typefaceAsset" format="string"/>
</declare-styleable>
</resources>

Second

Assuming you want to use this widget often, you should set up a simple cache for the loaded Typeface objects, since loading them from memory on the fly can take time. Something like:

public class FontManager {
private static FontManager instance;


private AssetManager mgr;


private Map<String, Typeface> fonts;


private FontManager(AssetManager _mgr) {
mgr = _mgr;
fonts = new HashMap<String, Typeface>();
}


public static void init(AssetManager mgr) {
instance = new FontManager(mgr);
}


public static FontManager getInstance() {
if (instance == null) {
// App.getContext() is just one way to get a Context here
// getContext() is just a method in an Application subclass
// that returns the application context
AssetManager assetManager = App.getContext().getAssets();
init(assetManager);
}
return instance;
}


public Typeface getFont(String asset) {
if (fonts.containsKey(asset))
return fonts.get(asset);


Typeface font = null;


try {
font = Typeface.createFromAsset(mgr, asset);
fonts.put(asset, font);
} catch (Exception e) {


}


if (font == null) {
try {
String fixedAsset = fixAssetFilename(asset);
font = Typeface.createFromAsset(mgr, fixedAsset);
fonts.put(asset, font);
fonts.put(fixedAsset, font);
} catch (Exception e) {


}
}


return font;
}


private String fixAssetFilename(String asset) {
// Empty font filename?
// Just return it. We can't help.
if (TextUtils.isEmpty(asset))
return asset;


// Make sure that the font ends in '.ttf' or '.ttc'
if ((!asset.endsWith(".ttf")) && (!asset.endsWith(".ttc")))
asset = String.format("%s.ttf", asset);


return asset;
}
}

This one will allow you to use .ttc file extensions, but it's untested.

Third

Create a new class that subclasses TextView. This particular example takes into account the defined XML typeface (bold, italic, etc.) and apply it to the font (assuming you're using a .ttc file).

/**
* TextView subclass which allows the user to define a truetype font file to use as the view's typeface.
*/
public class FontText extends TextView {
public FontText(Context context) {
this(context, null);
}


public FontText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}


public FontText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);


if (isInEditMode())
return;


TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FontText);


if (ta != null) {
String fontAsset = ta.getString(R.styleable.FontText_typefaceAsset);


if (!TextUtils.isEmpty(fontAsset)) {
Typeface tf = FontManager.getInstance().getFont(fontAsset);
int style = Typeface.NORMAL;
float size = getTextSize();


if (getTypeface() != null)
style = getTypeface().getStyle();


if (tf != null)
setTypeface(tf, style);
else
Log.d("FontText", String.format("Could not create a font from asset: %s", fontAsset));
}
}
}
}

Finally

Replace the instances of TextView in your XML with the fully qualified class name. Declare your custom namespace just like you would the Android namespace. Note that the "typefaceAsset" should point to a .ttf or .ttc file contained in your /assets directory.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">


<com.example.FontText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a custom font text"
custom:typefaceAsset="fonts/AvenirNext-Regular.ttf"/>
</RelativeLayout>

UPDATE: https://github.com/chrisjenx/Calligraphy appears to be a superior solution to this.


Maybe you can use reflection to inject/hack your font into the static list of available fonts when your application is created? I am interested in feedback from others if this is a really, really bad idea or if this is a great solution — it seems it is going to be one of those extremes...

I was able to inject my custom typeface into the list of system typefaces with my own font family name, then specifying that custom font family name ("brush-script") as the value of android:FontFamily on a standard TextView worked on my LG G4 running Android 6.0.

public class MyApplication extends android.app.Application
{
@Override
public void onCreate()
{
super.onCreate();


Typeface font = Typeface.createFromAsset(this.getResources().getAssets(),"fonts/brush-script.ttf");
injectTypeface("brush-script", font);
}


private boolean injectTypeface(String fontFamily, Typeface typeface)
{
try
{
Field field = Typeface.class.getDeclaredField("sSystemFontMap");
field.setAccessible(true);
Object fieldValue = field.get(null);
Map<String, Typeface> map = (Map<String, Typeface>) fieldValue;
map.put(fontFamily, typeface);
return true;
}
catch (Exception e)
{
Log.e("Font-Injection", "Failed to inject typeface.", e);
}
return false;
}
}

In my layout

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fancy Text"
android:fontFamily="brush-script"/>

instead of xmlns:custom="schemas.android.com/tools"; you should use: xmlns:custom="schemas.android.com/apk/res-auto"; in order to use styleable attributes. I made this change and it is working now.

Create a fonts folder in assets and add all your required font's there.

public class CustomTextView extends TextView {
private static final String TAG = "TextView";


public CustomTextView(Context context) {
super(context);
}


public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setCustomFont(context, attrs);
}


public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setCustomFont(context, attrs);
}


private void setCustomFont(Context ctx, AttributeSet attrs) {
TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
String customFont = a.getString(R.styleable.CustomTextView_customFont);
setCustomFont(ctx, customFont);
a.recycle();
}


public boolean setCustomFont(Context ctx, String fontName) {
Typeface typeface = null;
try {
if(fontName == null){
fontName = Constants.DEFAULT_FONT_NAME;
}
typeface = Typeface.createFromAsset(ctx.getAssets(), "fonts/" + fontName);
} catch (Exception e) {
Log.e(TAG, "Unable to load typeface: "+e.getMessage());
return false;
}
setTypeface(typeface);
return true;
}
}

and add a declarable in attrs.xml

<declare-styleable name="CustomTextView">
<attr name="customFont" format="string"/>
</declare-styleable>

and then add your customFont like

app:customFont="arial.ttf"

I know this is an old question, but i've found a much easier solution.

First declare your TextView in xml as usual. Put your font (TTF or TTC) in the asset folder

app\src\main\assets\

Then just set the typeface for your text view in your onCreate method.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_name);


TextView textView = findViewById(R.id.my_textView);
Typeface typeface = Typeface.createFromAsset(getAssets(), "fontName.ttf");
textView.setTypeface(typeface);
}

Done.

The best solution is to use (finally) introduced by Google a native custom font feature in XML. But you have to target API 26. It supports API 16+

https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml

the latest update now that you can set the font in XML without any other classes added like:

android:fontFamily="@font_folder/font_file"