Android App修改字体大小,且不随系统字体大小更改

Posted 福州-司马懿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android App修改字体大小,且不随系统字体大小更改相关的知识,希望对你有一定的参考价值。

在做混合开发时发现,无论是APP内的字体大小,还是前端的字体大小,都会随着系统字体大小发生变化。当遇到老人字体(特大号字体)时,有些页面的布局就乱掉了。而玩过游戏的都知道,所有游戏APP的字体都不会随着系统的字体变化而变化。

有两种思路:

  1. 利用 dip(device independent pixels,设备独立像素)作为字体单位。这样的话,一个是在所有手机上字体看起来都差不多大,而且也不随系统字体变化而变化。Unity做出来的游戏就是用这种方案。缺点也很明显,就是都在xml里写死了,无法修改。
  2. 重写 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 方法被重新调用,有如下几个可选方案(最后发现只有一个可行)

  1. activity.getWindow().getDecorView().invalidate()
  2. activity.getWindow().getDecorView().requestLayout()
  3. 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修改字体大小,且不随系统字体大小更改的主要内容,如果未能解决你的问题,请参考以下文章

App字体大小不随系统改变而改变

Android中App控制字体大小

app字体不随系统改变而变化

如何把android textView字体大小固定写死,而不随系统设置字体大小的改变而改变。

华为手机app字体不随系统

设置安卓字体大小,不跟随系统