在Android中使用自定义字体

111

我想在我创建的Android应用程序中使用自定义字体。
我可以从代码中逐个更改每个对象的字体,但我有成百上千个对象。

所以,

  • 是否有一种方法可以从XML中设置自定义字体类型?
  • 是否有一种方法可以在一个地方从代码中进行设置,以便整个应用程序和所有组件都使用自定义字体而不是默认字体?

看看这篇文章,我在这里发布了关于此问题的完整代码。 - Manish Singla
您可以在主活动中使用静态变量来保存嵌入字体的引用。这将导致存在一个持久的字体集,不会被GC清除。 - Jacksonkr
工厂方法。或者一个接受你的视图并设置所有字体和字形设置的方法。 - mtmurdock
新的支持库26现在允许您在XML中使用字体。以下是如何实现它Fonts in XML - Vinicius Silva
21个回答

109

可以的。

你需要创建一个扩展文本视图的自定义视图。

values 文件夹中的 attrs.xml 中:

<resources>
    <declare-styleable name="MyTextView">
        <attr name="first_name" format="string"/>
        <attr name="last_name" format="string"/>
        <attr name="ttf_name" format="string"/>
    </declare-styleable>
</resources>

main.xml 文件中:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:lht="http://schemas.android.com/apk/res/com.lht"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="Hello"/>
    <com.lht.ui.MyTextView  
        android:id="@+id/MyTextView"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="Hello friends"
        lht:ttf_name="ITCBLKAD.TTF"
        />   
</LinearLayout>

MyTextView.java文件中:

package com.lht.ui;

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

public class MyTextView extends TextView {

    Context context;
    String ttfName;

    String TAG = getClass().getName();

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            Log.i(TAG, attrs.getAttributeName(i));
            /*
             * Read value of custom attributes
             */

            this.ttfName = attrs.getAttributeValue(
                    "http://schemas.android.com/apk/res/com.lht", "ttf_name");
            Log.i(TAG, "firstText " + firstText);
            // Log.i(TAG, "lastText "+ lastText);

            init();
        }

    }

    private void init() {
        Typeface font = Typeface.createFromAsset(context.getAssets(), ttfName);
        setTypeface(font);
    }

    @Override
    public void setTypeface(Typeface tf) {

        // TODO Auto-generated method stub
        super.setTypeface(tf);
    }

}

4
这个方法可以使用,但只适用于“TextView”。如果希望在继承自“TextView”的每个小部件类中实现相同的功能,则需要为每个小部件类编写自定义子类。 - CommonsWare
17
这个可以行得通,但在ICS之前,它会为每个你实例化的视图分配字体的内存: http://code.google.com/p/android/issues/detail?id=9904 修复的方法是创建一个全局可访问的静态哈希映射表来保存所有已实例化的字体: http://code.google.com/p/android/issues/detail?id=9904#c7 - ByteWelder
一个不错的逐步教程http://kotikan.com/blog/posts/2012/09/android-attributes,可与此答案一起使用。 - Ryan Heitner
1
@AlaksiejN。如果您需要为不同的TextView设置不同的字体... - Nick
我可以从样式XML资源中设置标签吗? - Mr. Developerdude
显示剩余6条评论

80

这个能否从XML中完成呢?

不行,抱歉。你只能通过XML来指定内置字体。

有没有一种方法可以在代码中以一处设置,使整个应用程序和所有组件都使用自定义字体而非默认字体?

据我所知,没有。

现在有各种选择:

  • 如果您正在使用appcompat,则可以使用Android SDK中的字体资源和向后兼容功能。

  • 对于不使用appcompat的用户,有第三方库可供使用,但并非所有库都支持在布局资源中定义字体。


4
在很多情况下,更好的解决方法并不是把精力放在定制字体上。定制字体往往会很大,导致你的下载量变大,并减少了愿意下载并继续使用你应用的人数。 - CommonsWare
22
我不一定同意。字体就像背景图片等UI样式的重要组成部分。而且大小并不总是很大。例如,我的情况下字体只有19.6KB :) - Codevalley
2
@Amit:这个问题还没有改变。有很多代码片段可以帮助简化在Java中将字体应用于您的UI,但是无法从XML中实现。 - CommonsWare
1
@Amit:“那这是否意味着我需要创建自己的.ttf或.otf文件来使用自定义字体?” - 呃,不用。您可以使用现有的TTF / OTF字体,尽管它们可能不都适用。这个问题的关键是如何在整个应用程序中应用这些字体,而这仍然是不可能的,这就是我在评论中提到的内容。 - CommonsWare
1
这个被接受的答案已经过时了,更新或删除它将是很好的选择。 - Sky Kelsey
显示剩余12条评论

49

我以一种更加“蛮力”的方式完成了这个任务,不需要更改布局xml或Activity。

已在Android 2.1至4.4版本上进行了测试。 在您的Application类中,在应用程序启动时运行此代码:

private void setDefaultFont() {

    try {
        final Typeface bold = Typeface.createFromAsset(getAssets(), DEFAULT_BOLD_FONT_FILENAME);
        final Typeface italic = Typeface.createFromAsset(getAssets(), DEFAULT_ITALIC_FONT_FILENAME);
        final Typeface boldItalic = Typeface.createFromAsset(getAssets(), DEFAULT_BOLD_ITALIC_FONT_FILENAME);
        final Typeface regular = Typeface.createFromAsset(getAssets(),DEFAULT_NORMAL_FONT_FILENAME);

        Field DEFAULT = Typeface.class.getDeclaredField("DEFAULT");
        DEFAULT.setAccessible(true);
        DEFAULT.set(null, regular);

        Field DEFAULT_BOLD = Typeface.class.getDeclaredField("DEFAULT_BOLD");
        DEFAULT_BOLD.setAccessible(true);
        DEFAULT_BOLD.set(null, bold);

        Field sDefaults = Typeface.class.getDeclaredField("sDefaults");
        sDefaults.setAccessible(true);
        sDefaults.set(null, new Typeface[]{
                regular, bold, italic, boldItalic
        });

    } catch (NoSuchFieldException e) {
        logFontError(e);
    } catch (IllegalAccessException e) {
        logFontError(e);
    } catch (Throwable e) {
        //cannot crash app if there is a failure with overriding the default font!
        logFontError(e);
    }
}

更完整的示例请参见http://github.com/perchrh/FontOverrideExample


这对我来说是最好的解决方案。 - Igor K
2
默认设置对我不起作用。如果我使用等宽字体,然后设置code<style name="AppTheme" parent="AppBaseTheme"> <item name="android:typeface">monospace</item> </style>code,它可以工作,但是加粗无效。我将此代码添加到扩展Application类的类中。这是正确的位置吗?@P-chan - Christopher Rivera
2
@ChristopherRivera 是的,请将它添加到您的应用程序的Application类中,并确保它在onCreate上运行。查看http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/graphics/Typeface.java/后,我建议您覆盖等宽字体的附加字段以及上面我的示例代码中的字段! - Per Christian Henden
2
好的,我改了SERIF字段,现在可以正常工作了 :) - Abdullah Umer
3
这个答案引用了这个答案,并提供了一个在我看来更加简洁的方法。 - adamdport
显示剩余6条评论

37

虽然我支持Manish的答案,认为它是最快和最有针对性的方法,但我也看到了一些朴素的解决方案,这些解决方案只是递归遍历视图层次结构并依次更新所有元素的字体。类似这样:

public static void applyFonts(final View v, Typeface fontToSet)
{
    try {
        if (v instanceof ViewGroup) {
            ViewGroup vg = (ViewGroup) v;
            for (int i = 0; i < vg.getChildCount(); i++) {
                View child = vg.getChildAt(i);
                applyFonts(child, fontToSet);
            }
        } else if (v instanceof TextView) {
            ((TextView)v).setTypeface(fontToSet);
        }
    } catch (Exception e) {
        e.printStackTrace();
        // ignore
    }
}

您需要在加载布局后以及在Activity的onContentChanged()方法中调用此函数。


基本上是这样。当时,这是在项目时间范围内完成它的唯一方法。如果需要的话,这是一个方便的快捷方式 (: - pospi

23
我能够以集中的方式完成这个任务,以下是结果:

enter image description here

我有以下的Activity,如果需要自定义字体,我会从中继承:
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater.Factory;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

public class CustomFontActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    getLayoutInflater().setFactory(new Factory() {

        @Override
        public View onCreateView(String name, Context context,
                AttributeSet attrs) {
            View v = tryInflate(name, context, attrs);
            if (v instanceof TextView) {
                setTypeFace((TextView) v);
            }
            return v;
        }
    });
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

private View tryInflate(String name, Context context, AttributeSet attrs) {
    LayoutInflater li = LayoutInflater.from(context);
    View v = null;
    try {
        v = li.createView(name, null, attrs); 
    } catch (Exception e) {
        try {
            v = li.createView("android.widget." + name, null, attrs);
        } catch (Exception e1) {
        }
    }
    return v;
}

private void setTypeFace(TextView tv) {
    tv.setTypeface(FontUtils.getFonts(this, "MTCORSVA.TTF"));
}
}

但是如果我使用的是支持包中的活动,例如FragmentActivity,那么我会使用这个Activity

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

public class CustomFontFragmentActivity extends FragmentActivity {

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

// we can't setLayout Factory as its already set by FragmentActivity so we
// use this approach
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    View v = super.onCreateView(name, context, attrs);
    if (v == null) {
        v = tryInflate(name, context, attrs);
        if (v instanceof TextView) {
            setTypeFace((TextView) v);
        }
    }
    return v;
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public View onCreateView(View parent, String name, Context context,
        AttributeSet attrs) {
    View v = super.onCreateView(parent, name, context, attrs);
    if (v == null) {
        v = tryInflate(name, context, attrs);
        if (v instanceof TextView) {
            setTypeFace((TextView) v);
        }
    }
    return v;
}

private View tryInflate(String name, Context context, AttributeSet attrs) {
    LayoutInflater li = LayoutInflater.from(context);
    View v = null;
    try {
        v = li.createView(name, null, attrs);
    } catch (Exception e) {
        try {
            v = li.createView("android.widget." + name, null, attrs);
        } catch (Exception e1) {
        }
    }
    return v;
}

private void setTypeFace(TextView tv) {
    tv.setTypeface(FontUtils.getFonts(this, "MTCORSVA.TTF"));
}
}

我还没有测试过这段代码是否适用于Fragment,但希望它能够正常工作。

我的FontUtils很简单,也解决了这里提到的pre-ICS问题:https://code.google.com/p/android/issues/detail?id=9904

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.Typeface;

public class FontUtils {

private static Map<String, Typeface> TYPEFACE = new HashMap<String, Typeface>();

public static Typeface getFonts(Context context, String name) { 
    Typeface typeface = TYPEFACE.get(name);
    if (typeface == null) {
        typeface = Typeface.createFromAsset(context.getAssets(), "fonts/"
                + name);
        TYPEFACE.put(name, typeface);
    }
    return typeface;
}
}

2
这个应该得到更多的投票。它能够正常工作,并且有一个演示截图。 - ericn

10

嘿,我也需要在我的应用程序中为不同的小部件使用2种不同的字体! 我使用以下方法:

在我的Application类中,我创建了一个静态方法:

public static Typeface getTypeface(Context context, String typeface) {
    if (mFont == null) {
        mFont = Typeface.createFromAsset(context.getAssets(), typeface);
    }
    return mFont;
}

String类型代表asset文件夹中的xyz.ttf字体。(我创建了一个常量类)现在你可以在应用程序的任何地方使用它:

mTextView = (TextView) findViewById(R.id.text_view);
mTextView.setTypeface(MyApplication.getTypeface(this, Constants.TYPEFACE_XY));

唯一的问题是,你需要在每个想要使用这种字体的小部件中都添加这段代码!但我认为这是最好的方法。


4
使用 pospi 的建议,并像 Richard 一样使用 'tag' 属性,我创建了一个自定义的 class,加载我的自定义字体,并根据它们的 tag 应用它们到视图上。
所以基本上,不是在属性 android:fontFamily 中设置 TypeFace,而是使用 android:tag 属性,并将其设置为已定义的枚举值之一。
public class Fonts {
    private AssetManager mngr;

    public Fonts(Context context) {
        mngr = context.getAssets();
    }
    private enum AssetTypefaces {
        RobotoLight,
        RobotoThin,
        RobotoCondensedBold,
        RobotoCondensedLight,
        RobotoCondensedRegular
    }

    private Typeface getTypeface(AssetTypefaces font) {
        Typeface tf = null;
        switch (font) {
            case RobotoLight:
                tf = Typeface.createFromAsset(mngr,"fonts/Roboto-Light.ttf");
                break;
            case RobotoThin:
                tf = Typeface.createFromAsset(mngr,"fonts/Roboto-Thin.ttf");
                break;
            case RobotoCondensedBold:
                tf = Typeface.createFromAsset(mngr,"fonts/RobotoCondensed-Bold.ttf");
                break;
            case RobotoCondensedLight:
                tf = Typeface.createFromAsset(mngr,"fonts/RobotoCondensed-Light.ttf");
                break;
            case RobotoCondensedRegular:
                tf = Typeface.createFromAsset(mngr,"fonts/RobotoCondensed-Regular.ttf");
                break;
            default:
                tf = Typeface.DEFAULT;
                break;
        }
        return tf;
    }
    public void setupLayoutTypefaces(View v) {
        try {
            if (v instanceof ViewGroup) {
                ViewGroup vg = (ViewGroup) v;
                for (int i = 0; i < vg.getChildCount(); i++) {
                    View child = vg.getChildAt(i);
                    setupLayoutTypefaces(child);
                }
            } else if (v instanceof TextView) {
                if (v.getTag().toString().equals(AssetTypefaces.RobotoLight.toString())){
                    ((TextView)v).setTypeface(getTypeface(AssetTypefaces.RobotoLight));
                }else if (v.getTag().toString().equals(AssetTypefaces.RobotoCondensedRegular.toString())) {
                    ((TextView)v).setTypeface(getTypeface(AssetTypefaces.RobotoCondensedRegular));
                }else if (v.getTag().toString().equals(AssetTypefaces.RobotoCondensedBold.toString())) {
                    ((TextView)v).setTypeface(getTypeface(AssetTypefaces.RobotoCondensedBold));
                }else if (v.getTag().toString().equals(AssetTypefaces.RobotoCondensedLight.toString())) {
                    ((TextView)v).setTypeface(getTypeface(AssetTypefaces.RobotoCondensedLight));
                }else if (v.getTag().toString().equals(AssetTypefaces.RobotoThin.toString())) {
                    ((TextView)v).setTypeface(getTypeface(AssetTypefaces.RobotoThin));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            // ignore
        }
    }
}

在你的Activity或Fragment中,只需调用:
Fonts fonts = new Fonts(getActivity());
fonts.setupLayoutTypefaces(mainLayout);

4

我在 Lisa Wray 的博客 上找到了一个好的解决方案。使用新的数据绑定,可以在XML文件中设置字体。

@BindingAdapter({"bind:font"})
public static void setFont(TextView textView, String fontName){
    textView.setTypeface(Typeface.createFromAsset(textView.getContext().getAssets(), "fonts/" + fontName));
}

在XML中:

<TextView
app:font="@{`Source-Sans-Pro-Regular.ttf`}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

你能否提供一个使用数据绑定的示例?非常感谢。 - Sri Krishna

3
我认为有一种更方便的方法来实现它。以下类将为您应用程序的所有组件设置自定义字体(每个类别都有一个设置)。
/**
 * Base Activity of our app hierarchy.
 * @author SNI
 */
public class BaseActivity extends Activity {

    private static final String FONT_LOG_CAT_TAG = "FONT";
    private static final boolean ENABLE_FONT_LOGGING = false;

    private Typeface helloTypeface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        helloTypeface = Typeface.createFromAsset(getAssets(), "fonts/<your type face in assets/fonts folder>.ttf");
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View view = super.onCreateView(name, context, attrs);
        return setCustomTypeFaceIfNeeded(name, attrs, view);
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View view = super.onCreateView(parent, name, context, attrs);
        return setCustomTypeFaceIfNeeded(name, attrs, view);
    }

    protected View setCustomTypeFaceIfNeeded(String name, AttributeSet attrs, View view) {
        View result = null;
        if ("TextView".equals(name)) {
            result = new TextView(this, attrs);
            ((TextView) result).setTypeface(helloTypeface);
        }

        if ("EditText".equals(name)) {
            result = new EditText(this, attrs);
            ((EditText) result).setTypeface(helloTypeface);
        }

        if ("Button".equals(name)) {
            result = new Button(this, attrs);
            ((Button) result).setTypeface(helloTypeface);
        }

        if (result == null) {
            return view;
        } else {
            if (ENABLE_FONT_LOGGING) {
                Log.v(FONT_LOG_CAT_TAG, "A type face was set on " + result.getId());
            }
            return result;
        }
    }

}

2
LayoutInflater的默认实现不支持从xml指定字体类型。但是,我在xml中看到过这样的操作,通过为LayoutInflater提供自定义工厂来解析xml标签中的这些属性。

基本结构如下所示:

public class TypefaceInflaterFactory implements LayoutInflater.Factory {

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        // CUSTOM CODE TO CREATE VIEW WITH TYPEFACE HERE
        // RETURNING NULL HERE WILL TELL THE INFLATER TO USE THE
        // DEFAULT MECHANISMS FOR INFLATING THE VIEW FROM THE XML
    }

}

public class BaseActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater.from(this).setFactory(new TypefaceInflaterFactory());
    }
}

这篇文章提供了更深入的解释,介绍了作者如何尝试通过这种方式为字体提供XML布局支持的机制。作者实现的代码可以在这里找到。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接