Android字体工作原理与应用

      Android字体系统由android 2D图形引擎skia实现,字体系统的配置方法在各个版本中不完全相同,按照API level可以划分为三个阶段:4.0以下版本、4.0-4.4版本、5.0及以上版本。本文主要针对4.0及以上版本中字体系统的配置方法及字体相关应用进行分析。注意,浏览器及webView中的字体有单独的字体系统。     
     Android中将每个字体文件描述为字型(Typeface),包括字体族(FontFamily)和字体样式(textStyle)两个维度,字体族对应各种字体类型,样式则分为normal、bold、italic和bold-italic四种。
     

工作原理

   下面对字体工作原理的分析围绕系统字体配置文件解析与字体加载相关内容,不涉及skia的实现细节,为便于理解分为三个部分。

1. java层
     在这一层,android.graphics.Typeface类负责加载系统字体,并对上层提供创建字体功能调用。下面分析Typeface类的调用过程。
     在android启动过程中,ZygoteInit类的入口函数main()中调用preload()函数,preload()函数主要用于加载并初始化各种类、链接库、资源等。
static void preload() {
        Log.d(TAG, "begin preload");
        preloadClasses();
        preloadResources();
        preloadOpenGL();
        preloadSharedLibraries();
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        Log.d(TAG, "end preload");
}
     在preload()中调用了preloadClasses()函数用于加载并初始化一些系统常用类,这些类的列表位于frameworks/base/preloaded-classes文件中,其中包括了Typeface类。
/**
* Performs Zygote process initialization. Loads and initializes
* commonly used classes.
*
* Most classes only cause a few hundred bytes to be allocated, but
* a few will allocate a dozen Kbytes (in one case, 500+K).
*/
private static void preloadClasses() {
    ......
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(PRELOADED_CLASSES);
     
    ......
 
    try {
                BufferedReader br = new BufferedReader(new InputStreamReader(is), 256);
 
                int count = 0;
                String line;
                while ((line = br.readLine()) != null) {
                    // Skip comments and blank lines.
                    line = line.trim();
                    if (line.startsWith("#") || line.equals("")) {
                        continue;
                    }
 
                    try {
                        if (false) {
                            Log.v(TAG, "Preloading " + line + "...");
                        }
                        Class.forName(line);
 
    ......
}
     preloadClasses()中调用Class.forName("android.graphics.Typeface")加载Typeface类,并调用Typeface类的static块。create()通过调用相应的native方法创建字体对象。
// 4.x
    static {
        DEFAULT         = create((String) null, 0);
        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
        SANS_SERIF      = create("sans-serif", 0);
        SERIF           = create("serif", 0);
        MONOSPACE       = create("monospace", 0);
  
        sDefaults = new Typeface[] {
            DEFAULT,
            DEFAULT_BOLD,
            create((String) null, Typeface.ITALIC),
            create((String) null, Typeface.BOLD_ITALIC),
        };
  
    private static native int  nativeCreate(String familyName, int style);
    private static native int  nativeCreateFromTypeface(int native_instance, int style);
    private static native void nativeUnref(int native_instance);
    private static native int  nativeGetStyle(int native_instance);
    private static native int  nativeCreateFromAsset(AssetManager mgr, String path);
    private static native int nativeCreateFromFile(String path);
 
 
// 5.x
    static {
        init();
        // Set up defaults and typefaces exposed in public API
        DEFAULT         = create((String) null, 0);
        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
        SANS_SERIF      = create("sans-serif", 0);
        SERIF           = create("serif", 0);
        MONOSPACE       = create("monospace", 0);
  
        sDefaults = new Typeface[] {
            DEFAULT,
            DEFAULT_BOLD,
            create((String) null, Typeface.ITALIC),
            create((String) null, Typeface.BOLD_ITALIC),
        };
 
    private static native long nativeCreateFromTypeface(long native_instance, int style);
    private static native long nativeCreateWeightAlias(long native_instance, int weight);
    private static native void nativeUnref(long native_instance);
    private static native int  nativeGetStyle(long native_instance);
    private static native long nativeCreateFromArray(long[] familyArray);
    private static native void nativeSetDefault(long native_instance);
     注意4.x版本与5.x版本提供的上层API调用是一致的,但是c++层的native函数却并不一致,这与5.x版本中static块增加了一个init()函数调用有关,init()方法主要实现了解析系统字体配置文件,并据此加载系统字体。对于4.x版本,解析并加载系统字体的功能在c++层的skia代码中实现,留到下面进行分析。
private static void init() {
        // Load font config and initialize Minikin state
        File systemFontConfigLocation = getSystemFontConfigLocation();
        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
        try {
            FileInputStream fontsIn = new FileInputStream(configFilename);
            FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
 
            List<FontFamily> familyList = new ArrayList<FontFamily>();
            // Note that the default typeface is always present in the fallback list;
            // this is an enhancement from pre-Minikin behavior.
            for (int i = 0; i < fontConfig.families.size(); i++) {
                Family f = fontConfig.families.get(i);
                if (i == 0 || f.name == null) {
                    familyList.add(makeFamilyFromParsed(f));
                }
            }
            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
            setDefault(Typeface.createFromFamilies(sFallbackFonts));
 
            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
            for (int i = 0; i < fontConfig.families.size(); i++) {
                Typeface typeface;
                Family f = fontConfig.families.get(i);
                if (f.name != null) {
                    if (i == 0) {
                        // The first entry is the default typeface; no sense in
                        // duplicating the corresponding FontFamily.
                        typeface = sDefaultTypeface;
                    } else {
                        FontFamily fontFamily = makeFamilyFromParsed(f);
                        FontFamily[] families = { fontFamily };
                        typeface = Typeface.createFromFamiliesWithDefault(families);
                    }
                    systemFonts.put(f.name, typeface);
                }
            }
            for (FontListParser.Alias alias : fontConfig.aliases) {
                Typeface base = systemFonts.get(alias.toName);
                Typeface newFace = base;
                int weight = alias.weight;
                if (weight != 400) {
                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
                }
                systemFonts.put(alias.name, newFace);
            }
            sSystemFontMap = systemFonts;
 
        } catch (RuntimeException e) {
            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Error opening " + configFilename);
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + configFilename);
        } catch (XmlPullParserException e) {
            Log.e(TAG, "XML parse exception for " + configFilename);
        }
    }
     相关native方法的注册,是在frameworks/base/core/jni/android/graphics/Typeface.cpp中,调用了下层skia代码中的SkTypeface::CreateFromxxxx方法。
// 4.x
static JNINativeMethod gTypefaceMethods[] = {
    { "nativeCreate",        "(Ljava/lang/String;I)I", (void*)Typeface_create },
    { "nativeCreateFromTypeface", "(II)I", (void*)Typeface_createFromTypeface },
    { "nativeUnref",              "(I)V",  (void*)Typeface_unref },
    { "nativeGetStyle",           "(I)I",  (void*)Typeface_getStyle },
    { "nativeCreateFromAsset",    "(Landroid/content/res/AssetManager;Ljava/lang/String;)I",
                                           (void*)Typeface_createFromAsset },
    { "nativeCreateFromFile",     "(Ljava/lang/String;)I",
                                           (void*)Typeface_createFromFile },
};
 
int register_android_graphics_Typeface(JNIEnv* env)
{
    return android::AndroidRuntime::registerNativeMethods(env,
                                                       "android/graphics/Typeface",
                                                       gTypefaceMethods,
                                                       SK_ARRAY_COUNT(gTypefaceMethods));
}
 
 
// 5.x
static JNINativeMethod gTypefaceMethods[] = {
    { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
    { "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },
    { "nativeUnref",              "(J)V",  (void*)Typeface_unref },
    { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
    { "nativeCreateFromArray",    "([J)J",
                                           (void*)Typeface_createFromArray },
    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
};
 
int register_android_graphics_Typeface(JNIEnv* env)
{
    return android::AndroidRuntime::registerNativeMethods(env,
                                                       "android/graphics/Typeface",
                                                       gTypefaceMethods,
                                                       SK_ARRAY_COUNT(gTypefaceMethods));
}

2. C/C++层
     C++层主要是skia图形引擎的android移植版,项目源码位于external\skia目录下。Java层native函数调用,在C++层调用首先进入SkTypeface.cpp,接下来进入android定制的SkFontHost.h中函数的定义。
     对于4.0-4.2版本,会进入SkFontHost_android.cpp中CreateTypeface()函数的调用,其中load_system_fonts() -> load_font_info() -> getFontFamilies()解析系统配置文件并据此加载系统字体。getFontFamilies()函数位于FontHostConfiguration_android.cpp中,主要功能是加载系统字体配置文件以及备用字体配置文件,配置文件的位置也是在这个文件中定义的,解析各个配置文件是在相应的parseConfigFile()函数中进行的。4.3版本也是这个流程,只不过解析配置文件并加载系统字体的函数调用层次为CreateTypeface() -> createTypefaceLocked() -> loadSystemFontsLocked() -> initSystemFontsLocked() ->loadFontInfoLocked() -> getFontFamilies(),基本逻辑是一致的,并没有本质的变化。
// SkFontHost_android.cpp
SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
                                       const char familyName[],
                                       const void* data, size_t bytelength,
                                       SkTypeface::Style style) {
    load_system_fonts();
 
    SkAutoMutexAcquire  ac(gFamilyMutex);
 
    // clip to legal style bits
    style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic);
 
    SkTypeface* tf = NULL;
 
    if (NULL != familyFace) {
        tf = find_typeface(familyFace, style);
    } else if (NULL != familyName) {
//        SkDebugf("======= familyName <%s>\n", familyName);
        tf = find_typeface(familyName, style);
    }
 
    if (NULL == tf) {
        tf = find_best_face(gDefaultFamily, style);
    }
 
    // we ref(), since the symantic is to return a new instance
    tf->ref();
    return tf;
}
 
 
// FontHostConfiguration_android.cpp
#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
#define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
......
 
 
/**
 * Loads data on font families from various expected configuration files. The resulting data
 * is returned in the given fontFamilies array.
 */
void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
 
    SkTDArray<FontFamily*> fallbackFonts;
    SkTDArray<FontFamily*> vendorFonts;
    parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
    parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
    parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
 
    // This loop inserts the vendor fallback fonts in the correct order in the overall
    // fallbacks list.
    int currentOrder = -1;
    for (int i = 0; i < vendorFonts.count(); ++i) {
        FontFamily* family = vendorFonts[i];
        int order = family->order;
        if (order < 0) {
            if (currentOrder < 0) {
                // Default case - just add it to the end of the fallback list
                *fallbackFonts.append() = family;
            } else {
                // no order specified on this font, but we're incrementing the order
                // based on an earlier order insertion request
                *fallbackFonts.insert(currentOrder++) = family;
            }
        } else {
            // Add the font into the fallback list in the specified order. Set currentOrder
            // for correct placement of other fonts in the vendor list.
            *fallbackFonts.insert(order) = family;
            currentOrder = order + 1;
        }
    }
    // Append all fallback fonts to system fonts
    for (int i = 0; i < fallbackFonts.count(); ++i) {
        *fontFamilies.append() = fallbackFonts[i];
    }
}
     对于4.4版本,则会进入src/ports/SkFontHost_fontconfig.cpp的CreateTypeface()函数,解析配置文件并加载系统字体的函数调用层次为CreateTypeface() -> LegacyCreateTypeface() -> RefFCI() -> GetSingletonDirectInterface(),此函数以及接下来的调用位于SkFontConfigInterface_android.cpp文件中, GetSingletonDirectInterface() -> getSingletonInterface() -> GetFontFamiliesGetFontFamilies(),GetFontFamilies()函数位于SkFontConfigParser_android.cpp文件中,此函数会解析系统字体配置文件以及备用配置文件,配置文件的定义也位于此文件中。虽然函数所在文件以及函数调用流程和之前的版本不太一样,但是解析配置文件的逻辑是一致的,不再给出代码片段。
     对于5.x版本,解析配置文件并加载字体是在java层中完成的,skia中不再进行这一项操作。

3. 配置文件
     4.x版本的系统字体配置文件位于system/etc/system_fonts.xml,备用字体配置文件位于system/etc/fallback_fonts.xml和vendor/etc/fallback_fonts.xml。
<!-- system_fonts.xml -->
<familyset>
    <family>
        <nameset>
            <name>sans-serif</name>
            <name>arial</name>
            <name>helvetica</name>
            <name>tahoma</name>
            <name>verdana</name>
        </nameset>
        <fileset>
            <file>Roboto-Regular.ttf</file>
            <file>Roboto-Bold.ttf</file>
            <file>Roboto-Italic.ttf</file>
            <file>Roboto-BoldItalic.ttf</file>
       </fileset>
    </family>
...
 
<!-- fallback_fonts.xml(system/etc) -->
<familyset>
    <family>
        <fileset>
            <file variant="elegant">DroidNaskh-Regular.ttf</file>
        </fileset>
    </family>
    <family>
        <fileset>
            <file variant="compact">DroidNaskh-Regular-SystemUI.ttf</file>
        </fileset>
    </family>
    <family>
        <fileset>
            <file>DroidSansEthiopic-Regular.ttf</file>
        </fileset>
    </family>
...
 
<!-- fallback_fonts.xml(vendor/etc) -->
<familyset>
    <family order="0">
        <fileset>
            <file>MyFont.ttf</file>
        </fileset>
    </family>
    <family>
        <fileset>
            <file>MyOtherFont.ttf</file>
        </fileset>
    </family>
</familyset>
     system_fonts.xml配置了系统的内置字体,第一个family节点为系统默认字体。nameset节点的各个name子节点定义可用的字体名称,fileset节点的file子节点分别对应normal、bold、italic、bold-italic四种字体样式,如果file节点个数少于四个,相应字体样式会对应已有兄弟file节点的字体文件。
     fallback_fonts.xml配置了系统备用字体,只有在系统内置字体中找不到相应字符时,才会到备用字体中去寻找,family节点的顺序对应搜索顺序,搜索匹配规则采用BCP47的定义。vendor/etc/fallback_fonts.xml是为了规范厂商定制默认字体,加载备用字体配置时,会将此文件中定义的各个family插入到system/etc/fallback_fonts.xml中,插入位置由family节点order属性指定,如果没有order属性,默认会插入到最后。

     5.x版本的系统字体及备用字体配置均位于system/etc/fonts.xml文件中。因为其它程序可能会读取4.x版本的配置文件,因此5.0版本仍然保留了4.x版本的配置文件,文件片段如下。
<familyset version="22">
    <!-- first font is default -->
    <family name="sans-serif">
        <font weight="100" style="normal">Roboto-Thin.ttf</font>
        <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
        <font weight="300" style="normal">Roboto-Light.ttf</font>
        <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
        <font weight="400" style="normal">Roboto-Regular.ttf</font>
        <font weight="400" style="italic">Roboto-Italic.ttf</font>
        <font weight="500" style="normal">Roboto-Medium.ttf</font>
        <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
        <font weight="900" style="normal">Roboto-Black.ttf</font>
        <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
        <font weight="700" style="normal">Roboto-Bold.ttf</font>
        <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
    </family>
 
    <!-- Note that aliases must come after the fonts they reference. -->
    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
    <alias name="sans-serif-light" to="sans-serif" weight="300" />
    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
    <alias name="sans-serif-black" to="sans-serif" weight="900" />
    <alias name="arial" to="sans-serif" />
    <alias name="helvetica" to="sans-serif" />
    <alias name="tahoma" to="sans-serif" />
    <alias name="verdana" to="sans-serif" />
...
 
    <!-- fallback fonts -->
    <family variant="elegant">
        <font weight="400" style="normal">NotoNaskh-Regular.ttf</font>
        <font weight="700" style="normal">NotoNaskh-Bold.ttf</font>
    </family>
    <family variant="compact">
        <font weight="400" style="normal">NotoNaskhUI-Regular.ttf</font>
        <font weight="700" style="normal">NotoNaskhUI-Bold.ttf</font>
    </family>
...
 
    <family lang="zh-Hans">
        <font weight="400" style="normal">NotoSansHans-Regular.otf</font>
    </family>
    <family lang="zh-Hant">
        <font weight="400" style="normal">NotoSansHant-Regular.otf</font>
...
    <family>
        <font weight="400" style="normal">DroidSansFallback.ttf</font>
    </family>
</familyset>
     5.x和4.x的字体配置文件相比,最大的一个改进是将之前字体样式中的单一bold样式改为各种不同过的weight,这样可以更加细粒度的控制字重。


系统内置字体修改

     通过以上分析可以发现修改系统字体,可以分为在代码中修改和在字体配置文件中修改两种思路。在4.0以前的版本中,由于字体系统的配置体系并不完善,因此要想修改系统字体必须要修改相应的代码部分,而在4.0及以上版本中可以通过只修改配置文件即可达到修改系统字体的目的,因此下面对于4.0及以上版本中系统字体修改方法中并没有给出通过代码修改的流程,4.0以前版本的添加修改方法不再给出,可查看参考阅读中的文章。

1. 在已有ROM中修改
     将字体文件<newfontFile.ttf>放在/system/fonts目录下。然后修改/system/etc/fallback_fonts.xml(4.x)或者/system/etc/fonts.xml(5.x)文件,添加新字体文件的设置,注意新添加xml节点的位置。
<!-- android 4.x版本,fallback_fonts.xml -->
<family>
    <fileset>
       <file>NewFontFile.ttf</file>
    </fileset>
</family>
 
 
<!-- android 5.x版本,fonts.xml -->
<family lang="zh-Hans">
    <font weight="400" style="normal">newFontFile.ttf</font>
</family>

2. 在ROM对应工程源码中修改
     将字体文件<newfontFile.ttf>放在/frameworks/base/data/fonts目录下。对于4.x版本,修改/frameworks/base/data/fonts目录下的fallback_fonts.xml,如果是android5.x版本,为保持兼容性要同时修改fallback_fonts.xml和fonts.xml文件,具体格式与在已有ROM中添加字体设置一样。最后修改/frameworks/base/data/fonts/fonts.mk文件,添加字体文件拷贝代码段。
PRODUCT_COPY_FILES := \
        ......
        frameworks/base/data/fonts/NewFontFile.ttf:system/fonts/<newFontFile.ttf>
     对于厂商定制ROM备用字体文件,android官方指导规范的方法是,修改/frameworks/base/data/fonts/vendor_fonts.xml文件,并在fonts.mk文件中添加代码段,使此文件在构建过程中拷贝并重命名为/vendor/etc/fallback_fonts.xml文件。
<!-- Sample fallback font additions to the default fallback list. These fonts will be added
    to the top two positions of the fallback list, since the first has an order of 0. -->
 
<familyset>
    <family order="0">
        <fileset>
            <file>MyFont.ttf</file>
        </fileset>
    </family>
    <family>
        <fileset>
            <file>MyOtherFont.ttf</file>
        </fileset>
    </family>
</familyset>
     注意,实际上可以直接替换原有默认字体文件,来达到修改系统字体的目的,但是这个方法会导致由于替换后的字体文件可能不包含某些字符,从而导致这些字符无法正常显示,所以并不推荐采用这种方法,因此没有将这种方法列出。


系统(frameworks)内置字体及使用

1. 布局文件中使用
      系统中有三个与字体设置有关的xml属性,分别为fontFamily(API16及以上可用)、typeface和textStyle,通过适当的组合可以设置不同的字体风格。通过查看TextView源码,发现fontFamily和textStyle的组合,以及typeface和TextStyle的组合,均可以用来唯一的确定字体对应的Typeface对象,并且如果在布局文件中设置了fontFamily属性,则会忽略typeface属性的值。
private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
        Typeface tf = null;
        if (familyName != null) {
            tf = Typeface.create(familyName, styleIndex);
            if (tf != null) {
                setTypeface(tf);
                return;
            }
        }
        switch (typefaceIndex) {
            case SANS:
                tf = Typeface.SANS_SERIF;
                break;
 
            case SERIF:
                tf = Typeface.SERIF;
                break;
 
            case MONOSPAC:
                tf = Typeface.MONOSPACE;
                break;
        }
 
        setTypeface(tf, styleIndex);
    }
     注意,在上面的代码中,分别用到了两个不同参数的setTypeface()方法:
  • fontFamily属性对应setTypeface(Typeface tf)方法。当fontFamily属性设置的字体没有对应的bold或italic样式的字体文件时,最终显示的字体样式将会由对应的其它样式字体文件提供,因此可能会和所设置的textStyle属性值并不一致,通过Typeface的getStyle()可以获取到实际对应的是哪个字体样式。
  • typeface属性对应setTypeface(Typeface tf, int style)方法当fontFamily属性设置的字体没有对应的bold或italic样式的字体文件时,此方法会调用Paint对象的setFakeBoldText()和setTextSkewX()方法,从而使最终文字的显示效果和textStyle的设置相一致。     
     接下来总结一下这三个属性可设置的有效属性值:     
  • fontFamily属性:通过对字体工作原理的分析可知,其有效值是由系统字体配置文件设定的,不同版本的android系统,甚至不同厂商品牌的android系统,其字体配置文件可能不会一样,在假设厂商按照android规范修改系统字体的前提下,可以在各版本相应的system_fonts.xml或者fonts.xml文件中找到有效的属性值。
  • typeface属性:有效值定义在frameworks中的attrs.xml文件中,包括"normal"、"sans-serif"、"serif"和"monospace"四种,其中normal对应系统默认字体,其它三种分别对应字体配置文件中相应的字体,具体可查看系统配置文件。由此也可以看出,typeface可设置的字体实际上是fontFamily可设置字体的子集,只提供了三大字体类型的代表字体,属于比较粗粒度的字体设置。
  • textStyle属性:有效值包括"normal"、"italic"、"bold"和"bold | italic"四种。
2. 代码中使用
     代码中使用系统内置字体,首先调用Typeface类中的相应方法加载系统字体文件,然后将Typeface对象应用于文本绘制的Paint/TextPaint对象,一共有三个方法用于加载系统内置字体:
  • create(String familyName, int style)
  • create(Typeface family, int style)
  • defaultFromStyle(int style)

App指定字体及使用

     如果需要使用指定字体文件中的字体,则需要将字体文件放在源码工程的assets目录下,在代码中通过Typeface.CreateFromAsset(AssertManager, StringRelativePath)创建Typeface对象,接下来就可以使用 Paint 或 TextView 的setTypeface(Typeface)方法对字体进行设置。
public class CustomTypefaceTextView extends TextView {
 
    public CustomTypefaceTextView(Context context) {
        super(context);
        initTypeface();
    }
 
    public CustomTypefaceTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initTypeface();
    }
 
    public CustomTypefaceTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initTypeface();
    }
 
    /**
     * Initialize the custom TextView with specified font typeface.
     */
    private void initTypeface() {
        Typeface customTypeface = Typeface.createFromAsset(getContext().getAssets(), "NotoSerif-BoldItalic.ttf");
        this.setTypeface(customTypeface);
    }
}
     实际上,Typeface类提供了三个方法用于在App中加载特定的字体文件,都可以用来创建Typeface对象:
  • createFromAsset(AssetManager mgr, String path)
  • createFromFile(File path)
  • createFromFile(String path)

中文字体

在4.x版本中,默认的中文字体对应DroidSansFallback.ttf字体文件,这个字体文件包含了除中文外的很多其它字符。
在5.x版本中,默认的中文字体对应NotoSansHans-Regular.otf字体文件,这是Adobe与Google共同开发的中文字体,属于Google的Noto字体系列,Adobe则称为思源字体。


参考与阅读


©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值