盒子
盒子
文章目录
  1. Android字体机制介绍
    1. 关键类:
    2. TextView的字体显示机制
  2. 方案一(底层方案):通过反射机制,修改Typeface类的字体库引用
  3. 方案二(顶层方案):自定义布局加载器,在加载layout_xml时对view tree的 view做字体的逻辑处理

Android APP更换字体策略精要

前言
近期项目需要在我们的APP中使用指定的字体库。经过搜集资料,研读源码,和别人探讨请教,最终产出了一些比较好的方案。不敢专享,写成文章分享出来,希望对大家的实际开发工作有所帮助。喜欢探讨Android开发技术的同学可以加学习小组QQ群: 193765960

本文只总结了较优方案,其他诸如自定义textView类,遍历layout_root_view这样的方案,作者认为限制较大,使用麻烦,就不在这里介绍了,感兴趣的朋友请自行百度。

版权归作者所有,如有转发,请注明文章出处:https://xiaodanchen.github.io/archives/

Android字体机制介绍

关键类:

  • Typeface:
    字体类,定义了字体类型到字体库的映射关系,Android有DEFAULT, MONOSPACE, SERIF, SANS_SERIF几种字体,根据各自的NORMAL(常规),BOLD(加粗),ITALIC(倾斜),BOLD_ITALIC(加粗倾斜)等几种样式,总共可以映射到至少16种字体库。
  • TextAppearance:
    字体外观类,定义了字体的外观比如,typeface,textsize,textcolor等外观属性。

TextView的字体显示机制

先看一下TextView的构造方法:

1
2
3
4
5
public TextView(Context context);
public TextView(Context context, @Nullable AttributeSet attrs);
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr);
public TextView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);

  • AttributeSet:xml中设置的属性
  • defStyleAttr:系统默认的属性
  • defStyleRes:系统默认的样式,这个是我们需要注意的参数哈

Textview的字体设置逻辑:
1)查看xml中是否设置了TextAppearance属性,如果设置了就判断外观中是否设置了字体。否则就执行第二步。
2)查看xml中是否设置了Typeface属性,指明了字体。否则执行第三步
3)使用系统的默认样式:defStyleRes

所以,假如我们的xml中对字体没有做设置,要是想要修改字体又不想修改xml,那么我们就要想其他办法了。
我最终的方案(方案一)是在APP的theme中去设置修改系统的默认样式(最终走到这个思路上是经过了比较酸爽的经过的,就不在这里细说了)。

方案一(底层方案):通过反射机制,修改Typeface类的字体库引用

第一步:通过反射机制修改Typeface字体指向的字体库到我们的字体库。

  • 定义修改字体库的方法类(示例):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import java.lang.reflect.Field;
    import android.content.Context;
    import android.graphics.Typeface;
    public final class FontsUtils {
    public static void setDefaultFont(Context context,
    String staticTypefaceFieldName, String fontAssetName) {
    final Typeface regular = Typeface.createFromAsset(context.getAssets(),
    fontAssetName);
    replaceFont(staticTypefaceFieldName, regular);
    }
    protected static void replaceFont(String staticTypefaceFieldName,
    final Typeface newTypeface) {
    try {
    final Field staticField = Typeface.class
    .getDeclaredField(staticTypefaceFieldName);
    staticField.setAccessible(true);
    staticField.set(null, newTypeface);
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }
    }
  • 在工程assets目录下新建fonts文件夹,把我们需要的字库放在里面,比如:FZLTHJW.TTF

  • 在MyApplication.oncreate()中调用修改字体库:

    1
    2
    3
    4
    FontsUtils.setDefaultFont(this, "DEFAULT", "fonts/FZLTHJW.TTF");
    FontsUtils.setDefaultFont(this, "MONOSPACE", "fonts/FZLTHJW.TTF");
    FontsUtils.setDefaultFont(this, "SERIF", "fonts/FZLTHJW.TTF");
    FontsUtils.setDefaultFont(this, "SANS_SERIF", "fonts/FZLTHJW.TTF");

第二步:修改APP theme的默认属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:textViewStyle">@style/FontTextviewstyle</item>
<item name="android:buttonStyle">@style/FontButtonstyle</item>
<item name="editTextStyle">@style/FontEditTextstyle</item>
<item name="android:radioButtonStyle">@style/FontradioButtonstyle</item>
</style>
<style name="FontTextviewstyle" parent="android:style/Widget.TextView">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontButtonstyle" parent="android:style/Widget.Button">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontradioButtonstyle" parent="android:style/Widget.CompoundButton.RadioButton">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontEditTextstyle" parent="Widget.AppCompat.EditText">
<item name="android:textAppearance">@style/FontTextAppearance</item>
</style>
<style name="FontTextAppearance" parent="@android:style/TextAppearance">
<item name="android:typeface">monospace</item>
</style>

总结:

  • 优点:
    • 不用修改xml,没有为每个activity创建字体的实例。
    • 除了常见的控件外,对Material Design的新控件也有作业
  • 缺陷:
    • 对于alertDialog还没有实现style的默认适配
    • 因为是修改的底层逻辑,相较于方案二,稍复杂。

方案二(顶层方案):自定义布局加载器,在加载layout_xml时对view tree的 view做字体的逻辑处理

  • 使用:如下方代码所示,在oncreatview的回调中,对view做类型判断,设置view的字体。
  • 优点:该方案代码逻辑清晰,使用简单,几行代码就可以搞定问题,不用修改xml等。
  • 缺陷:
    • 在一些第三方的控件或者自定义控件上可能使用会有限制,如果控件没有提供修改控件字体的接口的话(待验证)
    • 需要注意的是,对于Material Design的android.support.design.widget.TextInputLayoutandroid.support.design.widget.TabLayout这样的控件不起作用,需要对这种类型设置*textAppearance这样的属性。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      private void replaceFont() {
      final Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/fangzheng.ttf");
      LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
      @Override
      public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
      // TODO Auto-generated method stub
      AppCompatDelegate delegate = getDelegate();
      View view = delegate.createView(parent, name, context, attrs);
      if(view != null ){
      if(view instanceof TextView){
      ((TextView)view).setTypeface(typeface);
      }else if(view instanceof Button){
      ((Button)view).setTypeface(typeface);
      }else if(view instanceof RadioButton){
      ((RadioButton)view).setTypeface(typeface);
      }
      }
      return view;
      }
      });
      }
      /**
      * BaseActivity.java
      */
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      replaceFont();//注意需要在super方法之前调用,否则会报异常
      super.onCreate(savedInstanceState);
      }

总结:

  • 通过这个方案,其实我们应该学习到一种统一对xml viewTree中某种控件设置某种属性的方法。
  • 举一反三,针对刚才上述的缺陷,我们其实也可以尝试设置textAppearance属性(相较于设置typeface麻烦些),感兴趣的同学可以去试验下。
扫描加群
好好学习,天天向上!