自定义字体和XML布局(Android)

174

我正在尝试在Android中使用XML文件定义GUI布局。据我所知,无法在XML文件中指定小部件应使用自定义字体(例如放置在assets / font /中的字体),您只能使用系统安装的字体。

我知道,在Java代码中,我可以使用唯一ID手动更改每个小部件的字体。或者,我可以在Java中迭代所有小部件以进行此更改,但这可能会非常慢。

我还有哪些选项?是否有更好的方法来制作具有自定义外观的小部件? 我不想为每个新添加的小部件手动更改字体。


69
DrDefrost,请接受一些答案,这样你就会在这个网站上获得更多的回复。 - Ken
1
这里还有一个类似的问题:https://dev59.com/6Wox5IYBdhLWcg3wsGWC - Vins
更新于05/2017:“支持库26.0 Beta为在运行Android API 14及更高版本的设备上使用XML字体功能提供支持。”参见:https://developer.android.com/preview/features/fonts-in-xml.html#using-support-lib - albert c braun
18个回答

220

根据我在这里学到的,你可以扩展TextView来设置自定义字体。

TextViewPlus.java:

package com.example;

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

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

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

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

    public TextViewPlus(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.TextViewPlus);
        String customFont = a.getString(R.styleable.TextViewPlus_customFont);
        setCustomFont(ctx, customFont);
        a.recycle();
    }

    public boolean setCustomFont(Context ctx, String asset) {
        Typeface tf = null;
        try {
        tf = Typeface.createFromAsset(ctx.getAssets(), asset);  
        } catch (Exception e) {
            Log.e(TAG, "Could not get typeface: "+e.getMessage());
            return false;
        }

        setTypeface(tf);  
        return true;
    }

}

attrs.xml:(在res/values目录下)

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

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:foo="http://schemas.android.com/apk/res/com.example"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <com.example.TextViewPlus
        android:id="@+id/textViewPlus1"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:text="@string/showingOffTheNewTypeface"
        foo:customFont="saxmono.ttf">
    </com.example.TextViewPlus>
</LinearLayout>

你需要将 "saxmono.ttf" 放在 assets 文件夹中。

更新于8/1/13

这种方法存在严重的内存问题。请参见下面chedabob的评论


5
看起来不错,但当我尝试在 main.xml 中使用 "TextViewPlus" 时出现了错误。我收到以下信息:
  • 错误:解析 XML 时出错:未绑定前缀。
  • 与元素类型 "supportLibs.TextViewPlus" 相关联的属性 "foo:customFont" 的前缀 "foo" 未绑定。
- Majjoodi
79
需要注意的是,这将生成数十个Typeface对象并消耗内存。在4.0之前的Android版本中存在一个bug,无法正确释放Typeface。最简单的方法是使用HashMap创建Typeface缓存。这使我的应用程序内存使用量从120+ MB降至18 MB。http://code.google.com/p/android/issues/detail?id=9904 - chedabob
7
@Majjoodi:如果您忘记将第二个命名空间添加到布局中,则通常会发生这种情况:xmlns:foo="http://schemas.android.com/apk/res/com.example - Theo
11
非常重要 - 我只想再次强调chedabob的建议。除非您遵循它,在ICS之前会出现内存泄漏问题。其中有几种解决方案,其中之一在chedabob提供的链接中,另一个在这里:http://stackoverflow.com/questions/8057010/listview-memory-leak。Peter,请更新您的答案 - 它很好但不完整。 - Michał Klimczak
1
@TomFobear 当然有!你可以查看我在mdevcon、Droidcon、Dev Days和MobDevCon上展示的演示文稿和示例代码(https://github.com/pflammertsma/mobdevcon)。它不仅能精确回答你的问题,而且是一个关于如何创建自定义视图的通用教程。 - Paul Lammertsma
显示剩余10条评论

35

我晚了3年才来参加派对 :( 然而这可能对于偶然发现此帖子的某人有所帮助。

我编写了一个缓存字体并允许您直接从XML指定自定义字体的库。您可以在这里找到该库。

以下是使用它时,您的XML布局的样子。

<com.mobsandgeeks.ui.TypefaceTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world"
    geekui:customTypeface="fonts/custom_font.ttf" />

嘿@Ragunath-jawahar,我该如何为Gradle项目导入库?我尝试了 compile 'com.mobsandgeeks:android-typeface-textview:1.0' 但没成功。 - aimango
1
你需要一个AAR才能以这种方式使用它。目前还没有。你可以复制源代码并构建一个Android Studio库项目。 - Ragunath Jawahar
2
你的"geekui"标签从哪里来? - sefirosu
3
从父标签中指定的命名空间 - xmlns:geekui="http://schemas.android.com/apk/res-auto" - Ragunath Jawahar
你的库解决了@peter回答中提到的内存问题吗?! - Muhammed Refaat
1
@MuhammedRefaat,是的,它可以 :) - Ragunath Jawahar

18

这可能有点晚了,但是您需要创建一个单例类,以返回定制字体以避免内存泄漏。

TypeFace类:

public class OpenSans {

private static OpenSans instance;
private static Typeface typeface;

public static OpenSans getInstance(Context context) {
    synchronized (OpenSans.class) {
        if (instance == null) {
            instance = new OpenSans();
            typeface = Typeface.createFromAsset(context.getResources().getAssets(), "open_sans.ttf");
        }
        return instance;
    }
}

public Typeface getTypeFace() {
    return typeface;
}
}

自定义 TextView:

public class NativelyCustomTextView extends TextView {

    public NativelyCustomTextView(Context context) {
        super(context);
        setTypeface(OpenSans.getInstance(context).getTypeFace());
    }

    public NativelyCustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setTypeface(OpenSans.getInstance(context).getTypeFace());
    }

    public NativelyCustomTextView(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        setTypeface(OpenSans.getInstance(context).getTypeFace());
    }

}

通过 XML:

<com.yourpackage.views.NativelyCustomTextView
            android:id="@+id/natively_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_margin="20dp"
            android:text="@string/natively"
            android:textSize="30sp" /> 

编程方式:

TextView programmaticallyTextView = (TextView) 
       findViewById(R.id.programmatically_text_view);

programmaticallyTextView.setTypeface(OpenSans.getInstance(this)
                .getTypeFace());

现在我明白了。不,它在设计时不起作用。我还没有处理它。 - Leonardo Cardoso
上面的例子对我来说似乎工作得很好(但实际上,在设计师中不会显示出来)。 - Wolfgang Schreurs
2
getTypeFace 在每次调用时都会创建一个新的字体,这违背了单例的目的。它应该有一个静态字段,在第一次调用时设置该字段,并在后续调用中返回该字段的值。 - Steven Pena
好的。谢谢。只有两个问题,1)为什么实例需要持有对上下文的引用?2)synchronized (Gothic.class)? - John Pang
1
@chiwai 你说得对!关于第一个问题,不需要它。关于第二个问题,那是一个打字错误。 - Leonardo Cardoso
显示剩余10条评论

13

虽然这是一个老问题,但我真的希望在开始寻找好的解决方案之前能够在这里阅读到这个答案。 Calligraphy 扩展了 android:fontFamily 属性,以支持在您的资产文件夹中添加自定义字体,如下所示:

<TextView 
  android:text="@string/hello_world"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:fontFamily="fonts/Roboto-Bold.ttf"/>

要激活它,您唯一需要做的就是将其附加到您正在使用的 Activity 的 Context 上:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(new CalligraphyContextWrapper(newBase));
}

你还可以指定自定义属性来替换android:fontFamily

它也适用于主题,包括AppTheme。


8

使用 DataBinding

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

字体文件必须在assets/fonts/目录下。


7
如果你只有一种字体想要添加,且希望写更少的代码,你可以为你特定的字体创建一个专用的TextView。请参考下面的代码。
package com.yourpackage;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontTextView extends TextView {
    public static Typeface FONT_NAME;


    public FontTextView(Context context) {
        super(context);
        if(FONT_NAME == null) FONT_NAME = Typeface.createFromAsset(context.getAssets(), "fonts/FontName.otf");
        this.setTypeface(FONT_NAME);
    }
    public FontTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        if(FONT_NAME == null) FONT_NAME = Typeface.createFromAsset(context.getAssets(), "fonts/FontName.otf");
        this.setTypeface(FONT_NAME);
    }
    public FontTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if(FONT_NAME == null) FONT_NAME = Typeface.createFromAsset(context.getAssets(), "fonts/FontName.otf");
        this.setTypeface(FONT_NAME);
    }
}

在 main.xml 中,现在你可以像这样添加 textView:
<com.yourpackage.FontTextView
    android:id="@+id/tvTimer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="" />

在init()函数中执行此操作,可以避免重复调用并节省3倍的时间。 - ericosg
@ericosg 当我使用你的解决方案时,出现了以下错误:android.view.InflateException: Binary XML file line #9: Error inflating class com.ascent.adwad.utils.CustomTextView - Sagar Devanga
@SagarDevanga,没有更多信息很难提供帮助。也许您可以尽可能地尝试并提出新问题。 - ericosg

7
从Android O预览版发布以来,最好的方法是这样做:
1.)右键单击res文件夹,然后转到新建 > Android资源目录。会出现新资源目录窗口。
2.)在资源类型列表中,选择字体,然后单击确定。
3.)将字体文件添加到字体文件夹中。下面的文件夹结构生成R.font.dancing_script、R.font.la_la和R.font.ba_ba。
4.)双击字体文件,在编辑器中预览文件的字体。
接下来,我们必须创建一个字体系列:
1.)右键单击字体文件夹,然后转到新建 > 字体资源文件。会出现新资源文件窗口。
2.)输入文件名,然后单击确定。新的字体资源XML将在编辑器中打开。
3.)在字体标签元素中包含每个字体文件、样式和权重属性。以下XML演示了如何在字体资源XML中添加与字体相关的属性:
<?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/hey_regular" />
    <font
    android:fontStyle="italic"
    android:fontWeight="400"
    android:font="@font/hey_bababa" />
</font-family>

将字体添加到TextView:
   <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    **android:fontFamily="@font/ba_ba"**/>

根据文档

使用字体

所有步骤都是正确的。


1
最佳答案!字体文件名必须为小写。 - Dmitry
您还可以创建一个自定义样式,将其应用于您的应用程序(在AndroidManifest文件下),并将其应用于所有视图。这样,您就不必仅仅将其放置在单个TextView上,因为这不会影响工具栏。 - goldenmaza

5

扩展 TextView 并添加自定义属性,或者只需使用 android:tag 属性传递要使用的字体字符串。您需要选择一个约定并坚持使用,例如我将所有字体放在 res/assets/fonts/ 文件夹中,以便您的 TextView 类知道在哪里找到它们。然后在构造函数中,在 super 调用之后手动设置字体即可。


4

想要使用自定义字体,唯一的方式是通过源代码。

请记住,Android 运行在资源非常有限的设备上,字体可能需要大量的 RAM。内置的 Droid 字体是特别制作的,如果你注意到的话,会发现很多字符和装饰没有包含在内。


请记住,Android 运行在资源非常有限的设备上。这种情况越来越少见了。四核手机?真的吗? - Sandy
9
我会更加考虑tareqHs的答案背景,这个答案比你的评论早了2年半。 - Ken
你的回答的第一部分可能在2010年时是正确的。后面的内容是多余的,离题了:Droid在2012年被Google放弃,改用Roboto字体。Android中字体选择的缺乏是一个缺陷,而不是一个特性;相比之下,iOS自2008年以来就为开发者提供了多种字体选择,即使按照今天的标准,那时的设备还很原始。 - cosmix
此答案已不再有效。 - Martin Konecny

2
也可以在XML中定义而不创建自定义类。
style.xml
<style name="ionicons" parent="android:TextAppearance">
    <!-- Custom Attr-->
    <item name="fontPath">fonts/ionicons.ttf</item>
</style>

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/ionicons"
        android:text=""/>
</LinearLayout>
< p > 快速提示,因为我总是忘记在哪里放字体,它必须在assets中,并且此文件夹与ressrc处于同一级别,在我的情况下是assets/fonts/ionicons.ttf

更新 添加了根布局,因为此方法需要xmlns:app="http://schemas.android.com/apk/res-auto"才能工作

更新2 忘记了一个我之前安装的库,叫做Calligraphy


这对我不起作用。当我尝试构建它时,我收到错误消息:Error:(49, 5) No resource found that matches the given name: attr 'fontPath'。 - Stef
尝试将 xmlns:app="http://schemas.android.com/apk/res-auto" 添加到您的根布局中,同时检查更新后的答案。 - norman784
错误不是来自布局XML文件,而是来自样式XML文件。似乎它不知道什么是fontPath。 - Stef
你说得对,我忘记了我有一个叫做Calligrapy的库。 - norman784

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