如何简单的实现App内字体大小设置

2,780 阅读3分钟

前言

app内字体大小调整应该是一个很常见的功能,绝大部分app里面都有可以设置字体大小的选项,网上实现方案也有很多,我这边给大家分享的是通过操作LayoutInflater.Factory2来实现

效果

设置完之后重启app字体全局生效【webp图有点简陋将就看下】

具体实现代码

主要实现类:

public class AppLayoutFactory implements LayoutInflater.Factory2 {
    private static final String[] prefix = {"android.widget.", "com.juejin.widget"};

    //系统inflater
    private LayoutInflater inflater;

    public AppLayoutFactory(Activity activity) {
        this(activity.getLayoutInflater());
    }

    AppLayoutFactory(LayoutInflater inflater) {
        this.inflater = inflater;
        Log.i("AppLayoutFactory", "AppLayoutFactory: " + inflater);
        LayoutInflaterCompat.setFactory2(inflater, this);
    }

    @Nullable
    @Override
    public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        return onCreateView(name, context, attrs);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        Log.i("AppLayoutFactory", "onCreateView: name=====>" + name);
        try {
            View view;
            if (name.startsWith(prefix[1])) {
                view = getClazzView(context, name, attrs);
            } else {
                view = inflater.createView(name, prefix[0], attrs);
            }
            if (view instanceof TextView) {
                boolean stableTextView = view instanceof StableFontSizeTextView;
                Object tag = view.getTag();
                Log.i("AppLayoutFactory",
                        "onCreateView: tag = " + (tag == null ? null : tag.toString()) + ", stableTextView = " + stableTextView);
                    //通过tag或者自定义TextView的方式不受全局字体大小设置而控制
                if (!context.getString(R.string.stable_font_size).equals(tag)
                        && !stableTextView) {
                    Log.i("AppLayoutFactory", "onCreateView: class====>" + view.getClass().getSimpleName());
                    //设置字体大小
                    AppFontHelper.getInstance().setAppTextSize((TextView) view);
                }
            }
            return view;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /////////////////////////反射获取View///////////////////////////

    //缓存构造方法
    private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
            new HashMap<String, Constructor<? extends View>>();

    private static final Class<?>[] constructorSignature = new Class[]{
            Context.class, AttributeSet.class};

    /**
     * @param context
     * @param name
     * @param attrs
     * @return 返回view
     */
    private static View getClazzView(Context context, String name, AttributeSet attrs) {
        View view = null;
        try {
            Constructor<? extends View> constructor = sConstructorMap.get(name);
            if (constructor == null) {
                Class<? extends View> clazz = Class.forName(name, false,
                        context.getClassLoader()).asSubclass(View.class);
                constructor = clazz.getConstructor(constructorSignature);
                constructor.setAccessible(true);

                sConstructorMap.put(name, constructor);
            }

            view = constructor.newInstance(context, attrs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return view;
    }
}

处理不受系统大字体控制【放在基类里面】

     /**
     * 禁止app字体大小跟随系统字体大小调节
     */
    @Override
    public Resources getResources() {
        Resources resources = super.getResources();
        if (resources != null) {
            android.content.res.Configuration configuration = resources.getConfiguration();
            if (configuration != null && configuration.fontScale != 1.0f) {
                configuration.fontScale = 1.0f;
                resources.updateConfiguration(configuration, resources.getDisplayMetrics());
            }
        }
        return resources;
    }

这里有个显示大小的选项还是会影响app内UI的显示,网上搜了一通暂时没找到解决方案,有方案的同学可以分享一下

基类的onCreate方法

这里插一句,网上有说法inflater是一个全局的单例,这个说法是有问题,下面是我两个activity中分别获得的inflater的实例,很明显是两个不一样的实例

2020-02-23 18:43:03.630 25223-25223/? I/AppLayoutFactory1: AppLayoutFactory: com.android.internal.policy.PhoneLayoutInflater@1dcf12f

2020-02-23 18:43:14.130 25223-25223/com.juejin I/AppLayoutFactory1: AppLayoutFactory: com.android.internal.policy.PhoneLayoutInflater@b3d0464

在基类初始化我们的inflater,设置定制的factory

     protected void onCreate(@Nullable Bundle savedInstanceState) {
        new AppLayoutFactory(this);//绑定每个inflater
        super.onCreate(savedInstanceState);
     }

new AppLayoutFactory(this);这行代码必须放在super之前否则会抛A factory has already been set on this LayoutInflater的错误;具体请看源码 activityonCreate代码实现:

     protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
     }

delegate.installViewFactory();此处会去初始化LayoutInflaterfactory

     public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
     }

具体这个错误是在LayoutInflater中的setFactory2抛出来的

     /**
     * Like {@link #setFactory}, but allows you to set a {@link Factory2}
     * interface.
     */
    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
     }

以上是基于compileSdkVersion=29 buildToolsVersion=29.0.2的代码,写的有问题的地方还望同学们指点一下