在Android TextView中使用自定义字体的方法(XML方式)

97

我已将一个自定义字体文件添加到我的assets/fonts目录下。如何在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 进行操作吗?


我已经搜索了很多,没有办法从xml中完成它。 - C.d.
2
尝试查看此帖子 https://dev59.com/33E95IYBdhLWcg3wSb7P - dor506
看一下这个答案!它允许您使用多种字体和XML。链接 - Rafa0809
正如其他人所說,您可以使用Calligraphy來實現這一目標。 - mbonnin
请查看此文章:http://www.gadgetsaint.com/tips/set-custom-font-for-textview-and-edittext-in-android-using-fontfamily - ASP
11个回答

45

简短回答:不行。Android没有内置支持通过XML将自定义字体应用于文本小部件的功能。

然而,有一个变通方法,实现起来并不太难。

首先

您需要定义自己的可样式化对象。在/res/values文件夹中,打开/创建attrs.xml文件,并添加一个declare-styleable对象,如下所示:

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

第二步

假设您经常使用此小部件,则应为已加载的Typeface对象设置一个简单的缓存,因为即时从内存中加载可能需要时间。例如:

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

这个方法可以让你使用 .ttc 文件扩展名,但是它没有经过测试。

第三步

创建一个继承自 TextView 的新类。此示例考虑了定义的 XML 字体类型(bolditalic 等等)并将其应用于字体(假设你正在使用 .ttc 文件)。

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

最后

将XML中的所有TextView实例替换为完全限定的类名。像声明Android命名空间一样声明您的自定义命名空间。请注意,"typefaceAsset"应指向位于/assets目录中的.ttf或.ttc文件。

<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>

很棒的答案!不过有一个问题:为什么当 isInEditMode 时你返回了? - Avi Shukron
04-11 18:18:32.685 3506-3506/com.example.demo E/AndroidRuntime﹕ 致命异常: 主线程 进程: com.example.demo, PID: 3506 android.view.InflateException: 二进制XML文件第2行: 加载com.example.demo.libraries.LatoTextView类时出错 at android.view.LayoutInflater.createView(LayoutInflater.java:620) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:696) at android.view.LayoutInflater.inflate(LayoutInflater.java:469) at android.view.LayoutInflater.inflate(LayoutInflater.java:397) at ... - e-info128
@AvrahamShukron - 这是因为我在 Android Studio 的预览模式下一直遇到错误(可能是因为它不知道如何处理应用自定义字体)。 - themarshal
1
你好 @themarshal:为了使用styleable属性,你应该使用xmlns:custom="http://schemas.android.com/apk/res-auto"而不是xmlns:custom="http://schemas.android.com/tools"。 - Abhinav Saxena
为什么要写 if (isInEditMode()) return; ? 这是否意味着 AndroidStudio 在预览时将不知道如何处理它? - Slobodan Antonijević
显示剩余3条评论

28
这里有一个示例代码,其中字体已在静态变量中定义,而字体文件位于资产目录中。
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);
    }

}

5
楼主明确表示他知道如何通过编程来设置字体(他甚至提供了一个示例)。问题是如何在XML中设置字体? - SMBiggs

13

创建属于您想要使用的字体的自定义TextView。在此类中,我使用静态的mTypeface字段来缓存Typeface(以提高性能)。

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

}

在xml文件中:

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

在Java类中:

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

11

Activity 实现了 LayoutInflater.Factory2 接口,能够在创建每个 View 时提供回调。可以使用自定义字体 Family 属性对 TextView 进行样式设置,按需加载字体并自动调用 setTypeface 方法实例化文本视图。

不幸的是,由于 Inflater 实例与 Activities 和 Windows 的架构关系,Android 中使用自定义字体的最简单方法是在 Application 级别上缓存已加载的字体。

示例代码库在此处:

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"
  />

导致

在此输入图片描述


两年没碰过那段代码了。但它应该还能用。理论上没有什么阻止它的地方。 - Leo

7
更新:https://github.com/chrisjenx/Calligraphy 似乎是比这个更好的解决方案。
也许当您的应用程序创建时,您可以使用反射将字体注入/黑客静态可用字体列表中?如果这是一个非常糟糕的想法,还是一个很好的解决方案,我对其他人的反馈感兴趣 - 看来它将是极端之一...
我能够将自定义字体插入系统字体列表中,并指定该自定义字体系列名称(“brush-script”)作为android:FontFamily的值,在运行Android 6.0的LG G4上的标准TextView上工作。
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;
    }
}

在我的布局中
<TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Fancy Text"
    android:fontFamily="brush-script"/>

对我不起作用,你能给更多详细说明如何使用吗?MyApplication类需要被调用吗? - Aesthetic
@Yawz,您需要在应用程序中至少调用一次injectTypeface方法。如果它记录了异常,我会对细节感兴趣。我仅在运行Android 6.0的LG G4上进行了测试(当时我正在进行自己的研究),我认为它不适用于所有版本的Android。 - Ross Bradbury
@RossBradbury 在一些设备上无法正常工作。大多数设备可以正常工作,但是有些设备无法接受这种字体。我测试的设备是联想A7000型号。 - Ranjithkumar
更新:https://github.com/chrisjenx/Calligraphy 是一个更优秀的解决方案。 - Ross Bradbury

7
不建议在XML中使用自定义字体,因为存在内存泄漏问题,为避免该问题,应通过编程方式实现。

6
似乎在冰淇淋三明治版本中修复了内存泄漏问题。 - Michael Scheper
2
你应该使用TypedArray方法recycle()来避免内存泄漏。 - Abhinav Saxena

6

我知道这是一个老问题,但我找到了一个更简单的解决方案。

首先像往常一样在xml中声明你的TextView。将你的字体(TTF或TTC)放入asset文件夹中。

app\src\main\assets\

然后在你的onCreate方法中为你的TextView设置字体。

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

完成。


1
问题明确说明要在 XML 文件中使用它,而不是以编程方式。 - Gustavo Baiocchi Costa

6
在资产中创建“字体”文件夹,并将所需的所有字体放在其中。
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;
    }
}

并在 attrs.xml 中添加可声明项

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

然后像这样添加您的自定义字体:
app:customFont="arial.ttf"

你可以根据这里的样式 https://github.com/leok7v/android-textview-custom-fonts/blob/master/res/values/styles.xml 来自定义上述类以适应样式。 - Zar E Ahmer

2

0

不要使用 xmlns:custom="schemas.android.com/tools";,而应该使用 xmlns:custom="schemas.android.com/apk/res-auto"; 来使用 styleable 属性。我已经进行了更改,现在它可以正常工作。


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