Android App修改字体大小,且不随系统字体大小更改
Posted 福州-司马懿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android App修改字体大小,且不随系统字体大小更改相关的知识,希望对你有一定的参考价值。
在做混合开发时发现,无论是APP内的字体大小,还是前端的字体大小,都会随着系统字体大小发生变化。当遇到老人字体(特大号字体)时,有些页面的布局就乱掉了。而玩过游戏的都知道,所有游戏APP的字体都不会随着系统的字体变化而变化。
有两种思路:
- 利用 dip(device independent pixels,设备独立像素)作为字体单位。这样的话,一个是在所有手机上字体看起来都差不多大,而且也不随系统字体变化而变化。Unity做出来的游戏就是用这种方案。缺点也很明显,就是都在xml里写死了,无法修改。
- 重写 getResources() 方法,将 fontScale 写死为 1,来避免缩放。优点是,可以在不重启应用的情况下,随时修改字体大小。
下面主要介绍下第2种方案
Context 实际上是一个抽象类,它由 ContextWrapper 来代理。从源码得知,mBase 仅能在构造函数 或 attachBaseContext 函数中被赋值。
android 的顶级组件 Application、Activity、Service 继承自 ContextWrapper。这类组件的生命周期其实是交给 Android 系统来托管的。因此在创建时并不能立马确定上下文,直到被调用 attachBaseContext 时,才被赋予上下文。
在 Activity 中创建的控件,在其访问资源时,会自动调用 getResources() 去确定获取资源的路径,同时获取资源。
如果只是要改变 Activity 的字体,仅重写 getResources()
因此,要想不依赖系统的字体大小,我们就可以创建一个 BaseActivity.java,然后所有 Activity 都作为它的子类
public class BaseActivity extends FragmentActivity
static float fontScale = 1f;
@Override
public Resources getResources()
Resources resources = super.getResources();
Configuration config = resources.getConfiguration();
if(config.fontScale != fontScale)
config.fontScale = fontScale;
return context.createConfigurationContext(config).getResources();
else
return resources;
updateConfiguration 方法已经被 deprecated 了,google 推荐我们使用 createConfigurationContext 方法来更新配置。
这样Activity的字体,就可以独立于系统了。但是发现一个问题,Fragment 中的字体还是跟随系统变化。
查看 fragment 源码,发现 getResources 被声明为 final,也就是不可被重写。
于是我们就只能在 fragment 的 context 被赋值时,也就是 onAttach 方法修改字体放大比例了。而传给 Fragment 函数 onAttach 的 context 源自于 Activity,因此,我们只需要在重写 Activity 的 attachBaseContext 方法。
注意:无论字体缩放比例是否相同,这里一定要重新调用 createConfigurationContext 创建新的 Context,否则虽然打印出的 fontScale 和我们设定的一致,但依然用的是系统的 fontScale
错误写法
@Override
protected void attachBaseContext(Context base)
Log.i(TAG, "attachBaseContext");
Configuration config = base.getResources().getConfiguration();
if(config.fontScale != fontScale)
config.fontScale = fontScale;
Context context = context.createConfigurationContext(config);
super.attachBaseContext(context);
else
super.attachBaseContext(base);
正确写法
@Override
protected void attachBaseContext(Context base)
Log.i(TAG, "attachBaseContext");
Configuration config = base.getResources().getConfiguration();
config.fontScale = fontScale;
Context context = context.createConfigurationContext(config);
super.attachBaseContext(context );
接下来,我们要仿微信那样,动态修改字体大小。
第一步那便是刷新 Activity,让 getResources 方法被重新调用,有如下几个可选方案(最后发现只有一个可行)
activity.getWindow().getDecorView().invalidate()
activity.getWindow().getDecorView().requestLayout()
activity.recreate()
recreate 之后会立马触发 attachBaseContext 绑定 context,然后重新调用 getResources 重新获取资源。
利用这一点,我们就可以补全 BaseActivity 里面的方法,使之动态改变字体大小了。
如果仅需要改变 activity 的字体,仅需要重写 getResources 即可,但如果还需要动态修改
将公共方法提取到一个公用类里面,DisplayUtil.java
public class DisplayUtil
/**
* 保持字体大小不随系统设置变化(用在界面加载之前)
* 要重写Activity的attachBaseContext()
*/
public static Context attachBaseContext(Context context, float fontScale)
Configuration config = context.getResources().getConfiguration();
Log.i(TAG, "changeActivityFontScaleA " + config.fontScale + ", " + fontScale);
//错误写法
// if(config.fontScale != fontScale)
// config.fontScale = fontScale;
// return context.createConfigurationContext(config);
// else
// return context;
//
//正确写法
config.fontScale = fontScale;
return context.createConfigurationContext(config);
/**
* 保持字体大小不随系统设置变化(用在界面加载之前)
* 要重写Activity的getResources()
*/
public static Resources getResources(Context context, Resources resources, float fontScale)
Configuration config = resources.getConfiguration();
Log.i(TAG, "changeActivityFontScaleR " + config.fontScale + ", " + fontScale);
if(config.fontScale != fontScale)
config.fontScale = fontScale;
return context.createConfigurationContext(config).getResources();
else
return resources;
/**
* 保存字体大小,后通知界面重建,它会触发attachBaseContext,来改变字号
*/
public static void recreate(Activity activity)
// activity.getWindow().getDecorView().requestLayout();
// activity.getWindow().getDecorView().invalidate();
//只有这句才有效,其它两句都无效
activity.recreate();
/**
* 保存字体大小,后通知界面重建,它会触发attachBaseContext,来改变字号
*/
public static void recreate(Activity activity)
// activity.getWindow().getDecorView().requestLayout();
// activity.getWindow().getDecorView().invalidate();
//只有这句才有效,其它两句都无效
activity.recreate();
BaseActivity.java
public class BaseActivity extends FragmentActivity
private static final String TAG = "BaseActivity";
static float fontScale = 1f;
@Override
public Resources getResources()
Log.i(TAG, "getResources");
Resources resources = super.getResources();
return DisplayUtil.getResources(this, resources, fontScale);
@Override
protected void attachBaseContext(Context base)
Log.i(TAG, "getResources");
super.attachBaseContext(DisplayUtil.attachBaseContext(base, fontScale));
/**
* 设置字体大小,同时通知界面重绘
*/
public void setFontScale(float fontScale)
Log.i(TAG, "setFontSize " + fontScale);
this.fontScale = fontScale;
DisplayUtil.recreate(this);
测试代码节选
public interface DataCallback1<T>
void onData(T data);
/**
* 显示拖动条对话框
*/
public static AlertDialog showSeekBar(Context context, @StringRes int titleId, int max, int progress, DataCallback1<Integer> callback)
SeekBar seekBar = new SeekBar(context);
seekBar.setMax(max);
seekBar.setProgress(progress);
int padding = 80;
seekBar.setPadding(padding, padding, padding, 0);
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(titleId)
.setView(seekBar)
.setPositiveButton(R.string.ok, (dialog1, which) -> callback.onData(seekBar.getProgress()))
.setNegativeButton(R.string.cancel, null).create();
dialog.show();
return dialog;
private void resizeFont()
AlertDialog dialog = DialogUtil.showSeekBar(activity, data, 5, 0, data14 ->
float value = 0.5f + 0.5f * data14;
activity.setfontScale(value);
);
main_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainFragment 默认" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="MainFragment 12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="MainFragment 20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:text="MainFragment 12dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="MainFragment 20dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
设置字体大小方法:设置 =》显示与亮度 =》字体大小
使用测试程序进行测试
经过测试可以发现,TextView 的默认字体大小是15,而且单位是 sp。
sp 的字体会随着系统字体大小( 或者 Configuration.fontScale )而缩放,而 dp 是设备无关像素单位,不管系统字体怎么修改,它都会保持一致,不会改变。
TextView 的默认字体大小是15,单位是sp,这点也可以从 TextView 的源码中获取佐证。
以上是关于Android App修改字体大小,且不随系统字体大小更改的主要内容,如果未能解决你的问题,请参考以下文章