替换字体的方式
1.Android原生的适配方案:按照区域(values-ko values-it-rIT values-zh-rCN) 按照机器的Android版本 按照设备分辨率来加载不同的font
2.利用反射替换安卓原生字体
例子
<style name="fontMedium">
<item name="android:fontFamily">monospace</item>
<item name="android:textStyle">normal</item>
</style>
<TextView
style="@style/fontMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="ABCDEFGHIGKLIMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" />
/**
* 替换字体,其本质是将系统底层的字体变量进行替换自己的字体引用 ,
* 只会替换控件中没有自定义字体的控件,已自定义的就是使用的是自定义的字体
*
* @param context
* @param oldFontName 支持的名称有 MONOSPACE、SERIF,NORMAL(程序无法运行)、SANS与DEFAULT和DEFAULT_BOLD与SANS_SERIF(可以运行但是显示字体没有修改成功)
* 而且需要与 需要与AndroidManifest文件application节点的android:theme引用的styles文件中
* <item name="android:typeface">monospace</item> 的值对应
* @param newFontNameFromAssets 新的字体路径,必须要放在assets文件夹下,如:fonts/Nsimsun.ttf
*/
public static void replaceFont(Context context, String oldFontName, String newFontNameFromAssets) {
Typeface newTypeface = Typeface.createFromAsset(context.getAssets(), newFontNameFromAssets);
try {
//android 5.0及以上我们反射修改Typeface.sSystemFontMap变量
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Map<String, Typeface> newMap = new HashMap<>();
newMap.put(oldFontName, newTypeface);
final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap");
staticField.setAccessible(true);
staticField.set(null, newMap);
} else {
final Field staticField = Typeface.class.getDeclaredField(oldFontName);
staticField.setAccessible(true);
staticField.set(null, newTypeface);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
在activity setContentView之前调用替换字体方法
replaceFont(this,“notosansdisplay_medium”, “fonts/xxx.otf”);//font存放在asset/fonts下,不是res/font下
参考资料:https://www.jianshu.com/p/282716d73c6a
这种方式起作用 但仅限于使用的字体是安卓自带的字体 如果不是安卓自带的字体 则无法替换 例如:
<style name="fontMedium">
<item name="android:fontFamily">@font/notosansdisplay_medium</item>
<item name="android:textStyle">normal</item>
</style>
则无法找到自定义font notosansdisplay_medium
3.递归替换 (性能较差 没有研究)
如果有多种字体 需要 多次遍历
Typeface typeface = getResources().getFont(R.font.myfont);
textView.setTypeface(typeface);
public static void applyFont(final Context context, final View root, final String fontName) {
try {
if (root instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); i++)
applyFont(context, viewGroup.getChildAt(i), fontName);
} else if (root instanceof TextView)
((TextView) root).setTypeface(Typeface.createFromAsset(context.getAssets(), fontName));
} catch (Exception e) {
Log.e(TAG, String.format("Error occured when trying to apply %s font for %s view", fontName, root));
e.printStackTrace();
}
}
4.使用第三方库替换字体
三方库:
https://github.com/chrisjenx/Calligraphy 如何使用及原理
一个APP内使用多种字体时替换多种字体
基本原理 使用反射 即上面的方法二
背景:
<style name="fontLight">
<item name="android:fontFamily">@font/font_light</item>
<item name="android:textStyle">normal</item>
</style>
<style name="fontMedium">
<item name="android:fontFamily">@font/font_medium</item>
<item name="android:textStyle">normal</item>
</style>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:font="@font/notosansdisplay_light"
android:fontStyle="normal"
android:fontWeight="200"
app:font="@font/notosansdisplay_light"
app:fontStyle="normal"
app:fontWeight="200" />
</font-family>
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:font="@font/notosansdisplay_medium"
android:fontStyle="normal"
android:fontWeight="500"
app:font="@font/notosansdisplay_medium"
app:fontStyle="normal"
app:fontWeight="500" />
</font-family>
需求:
在机型A上运行时使用自定义字体A1 A2
在机型B上运行时使用自定义字体B1 B2
思路:使用上面的第二种反射机制替换字体
遇到两个问题
1.该方法只能替换Android原生的字体
2.如何替换多种字体
解决问题1:
既然只能替换Android原生的字体 那么我们在静态xml中就先使用Android字体,然后利用这些字体与A1 A2 B1 B2形成映射的map 在代码里面动态设置为自定义字体
例如:
在A机型
Android字体font1 对应自定义字体A1
Android字体font2 对应自定义字体A2
在B机型
Android字体font1 对应自定义字体B1
Android字体font2 对应自定义字体B2
解决问题2:
对原先的方法的进行改进
final Field staticField = Typeface.class.getDeclaredField(“sSystemFontMap”);
staticField.set(null, newMap);
接受的实际是个map 之前的方法是一个只有一个元素的map 只要对原先的方法进行改进 传入映射的map即可
最终方案及步骤:
1.添加字体A1 A2 B1 B2 放到assert/fonts下
2.修改style的定义
原先定义:
<style name="fontLight">
<item name="android:fontFamily">@font/font_light</item>
<item name="android:textStyle">normal</item>
</style>
<style name="fontMedium">
<item name="android:fontFamily">@font/font_medium</item>
<item name="android:textStyle">normal</item>
</style>
变更为:
<style name="fontLight">
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:textStyle">normal</item>
<item name="android:fontWeight" tools:targetApi="o">200</item>
</style>
<style name="fontMedium">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:fontWeight" tools:targetApi="o">500</item>
</style>
这里sans-serif-light sans-serif-medium即对应Android原生的font A和B
3.根据机型使用字体(使用时机在Application的onCreate或者Activit的setContentView之前)
switch (model) {
case A1:
//caution:
// 1.the key value in map(like sans-serif,sans-serif-medium) should be android font, custom font can't be found by system
// 2.if move the font file or change name, this font also need change, or will cause file not found exception
oldNewFontMap.put("sans-serif-medium", "fonts/A1.OTF");
oldNewFontMap.put("sans-serif-light", "fonts/A2.OTF");
break;
case B1:
oldNewFontMap.put("sans-serif-medium", "fonts/B1.ttf");
oldNewFontMap.put("sans-serif-light", "fonts/B2.ttf");
break;
}
replaceFont(this, oldNewFontMap);
4.对replaceFont进行改进 让其可以同时更改多种字体
public static void replaceFont(Context context, HashMap<String, String> oldNewFontMapping) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Map<String, Typeface> newMap = new HashMap<>();
for (Map.Entry<String, String> entry : oldNewFontMapping.entrySet()) {
newMap.put(entry.getKey(), Typeface.createFromAsset(context.getAssets(), entry.getValue()));
}
final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap");
staticField.setAccessible(true);
staticField.set(null, newMap);
} else {
for (Map.Entry<String, String> entry : oldNewFontMapping.entrySet()) {
final Field staticField = Typeface.class.getDeclaredField(entry.getKey());
staticField.setAccessible(true);
staticField.set(null, Typeface.createFromAsset(context.getAssets(), entry.getValue()));
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}