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() 去确定获取资源的路径,同时获取资源。

因此,要想不依赖系统的字体大小,我们就可以创建一个 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;
        }
	}
}

这样Activity的字体,就可以独立于系统了。但是发现一个问题,Fragment 中的字体还是跟随系统变化。

查看 fragment 源码,发现 getResources 被声明为 final,也就是不可被重写。
在这里插入图片描述
于是我们就只能在 fragment 的 context 被赋值时,也就是 onAttach 方法修改字体放大比例了。

updateConfiguration 方法已经被 deprecated 了,google 推荐我们使用 createConfigurationContext 方法来更新配置。但 androidx 里面的 Fragment 的 getResources 方法却被声明为 final,这应该是 google 疏忽导致的一个 BUG,因此只能使用旧版的 updateConfiguration 方法进行强制更新。

在这里插入图片描述

public class BaseFragment extends Fragment {

    @Override
    public void onAttach(@NonNull Context context) {
        Resources resources = context.getResources();
        Configuration config = resources.getConfiguration();
        config.fontScale = fontScale;
        resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
        super.onAttach(context);
    }
}

接下来,我们要仿微信那样,动态修改字体大小。

第一步那便是刷新 Activity,让 getResources 方法被重新调用,有如下几个可选方案(最后发现只有一个可行)

  1. activity.getWindow().getDecorView().invalidate()
  2. activity.getWindow().getDecorView().requestLayout()
  3. activity.recreate()

recreate 之后会立马触发 attachBaseContext 绑定 context,然后重新调用 getResources 重新获取资源。
在这里插入图片描述
利用这一点,我们就可以补全 BaseActivity 里面的方法,使之动态改变字体大小了。

将公共方法提取到一个公用类里面,DisplayUtil.java

public class DisplayUtil {
    /**
     * 保持字体大小不随系统设置变化(用在界面加载之前)
     * 要重写Activity的attachBaseContext()
     */
    public static Context changeActivityfontScale(Context context, float fontScale) {
        Configuration config = context.getResources().getConfiguration();
        if(config.fontScale != fontScale) {
            config.fontScale = fontScale;
            return context.createConfigurationContext(config);
        } else {
            return context;
        }
    }

    /**
     * 保持字体大小不随系统设置变化(用在界面加载之前)
     * 要重写Activity的getResources()
     */
    public static Resources changeActivityfontScale(Context context, Resources resources, float fontScale) {
        Configuration config = resources.getConfiguration();
        if(config.fontScale != fontScale) {
            config.fontScale = fontScale;
            return context.createConfigurationContext(config).getResources();
        } else {
            return resources;
        }
    }

    /**
     * 保持字体大小不随系统设置变化
     * (用在Activity的attachBaseContext)
     */
    public static Context changeFragmentfontScale(Context context, float fontScale) {
        Resources resources = context.getResources();
        Configuration config = resources.getConfiguration();
        config.fontScale = fontScale;
        resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
        return context;
    }

    /**
     * 保存字体大小,后通知界面重建,它会触发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.changeActivityfontScale(this, resources, fontScale);
    }

    @Override
    protected void attachBaseContext(Context base) {
        Log.i(TAG, "attachBaseContext");
        super.attachBaseContext(DisplayUtil.changeActivityfontScale(base, BaseActivity.fontScale));
    }

    /**
     * 设置字体大小,同时通知界面重绘
     */
    public void setfontScale(float fontScale) {
        Log.i(TAG, "setfontScale " + fontScale);
        this.fontScale = fontScale;
        DisplayUtil.recreate(this);
    }
}

BaseFragment.java

public class BaseFragment extends Fragment {
    private static final String TAG = "BaseFragment";

    @Override
    public void onAttach(@NonNull Context context) {
        Log.i(TAG, "onAttach");
        context = DisplayUtil.changeFragmentfontScale(context, BaseActivity.fontScale);
        super.onAttach(context);
    }
}

测试代码节选

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

发布了471 篇原创文章 · 获赞 565 · 访问量 188万+

猜你喜欢

转载自blog.csdn.net/chy555chy/article/details/104015552
今日推荐