Android App修改字体大小,且不随系统字体大小更改 - Go语言中文社区

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 的源码中获取佐证。
在这里插入图片描述在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/chy555chy/article/details/104015552
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 03:51:38
  • 阅读 ( 693 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢