Android APP字体随系统字体调整造成界面布局混乱问题解决方案 您所在的位置:网站首页 会议主持过程怎么写 Android APP字体随系统字体调整造成界面布局混乱问题解决方案

Android APP字体随系统字体调整造成界面布局混乱问题解决方案

2023-10-21 01:21| 来源: 网络整理| 查看: 265

一、遇到的问题

当用户调整系统字体大小的时候,APP的字体一般也都会跟随改变,进而导致某些界面布局排版混乱。

下面先说一下关于sp单位的理解

sp单位除了受屏幕密度影响外,还受到用户的字体大小影响,通常情况下,建议使用sp来跟随用户字体大小设置。除非一些特殊的情况,不想跟随系统字体变化的,可以使用dp”。按照这么说,布局宽高固定写死的地方应该统一用dp显示字体,因为一旦用户在设置中调大字体,宽高为固定值的布局显示就乱了。

二、 解决方案 1.  全部界面强制使用标准字体,不跟随系统字体大小改变

强制实现所有界面都的字体都不随系统字体大小而改变,在工程的BaseActivity中添加下面的代码。利用Android的Configuration类中的fontScale属性,其默认值为1,会随系统调节字体大小而发生变化,如果我们强制让其等于默认值,就可以实现字体不随调节改变,

@Override public Resources getResources() { Resources resources = super.getResources(); if (resources != null) { Configuration configuration = resources.getConfiguration(); if (configuration != null && configuration.fontScale != 1.0f) { configuration.fontScale = 1.0f;//这里只设置字体,故不使用下面注释的方法 // configuration.setToDefaults(); resources.updateConfiguration(configuration, resources.getDisplayMetrics()); } } return resources; }

注意: Android 8.0后在Application中复写上述方法是无效的 (原因暂不清楚,有知道的大佬欢迎指出)。此外,在任意一个Activity中如上覆盖了getResources方法后,会让其它Activity的字体也变的独立于系统配置(这里的Activity只针对重新create的,如当前 Activity 的 fragment,因为没有重新onCreate,就不会重绘进而改变字体)。我的理解是,新的 Activity 会载入上面更新后的 Configuration,而现有的 Activity 则不会更新。所以此方式我只建议用在BaseActivity中实现全部界面字体不随系统更改。

如果有朋友担心重写这个方法会对界面渲染造成什么影响,那么推荐使用下面的方式:

//建议放在APP的启动Activity中的onCreate() public void initFontScale() { Resources resources = getResources(); Configuration configuration = resources.getConfiguration(); float fontScale = configuration.fontScale; configuration.fontScale = 1.0f;//0.85:小号 1:标准 1.25:大号 1.4:巨无霸 resources.updateConfiguration(configuration, resources.getDisplayMetrics()); Log.d("DisplayMetrics ", "======" + resources.getDisplayMetrics().toString() + "\t,fontScale:" + configuration.fontScale); } 2.  在具体的界面把不想要放大的View字体单位设置为dp 3.  部分界面跟随系统字体放大,部分界面使用标准字体,不跟随系统字体方法

     同一个APP中仅针对部分界面字体不做调整,其他界面跟随系统字体大小改变,下面详细说明思路

     重写BaseActivity中的 getResource() 方法,然后在方法里面,对于需要做字体调整的界面调整字体,不需要调整的使用标准字体,下面放代码:

//下面代码均放在APP的 BaseActivity 中 private boolean fixscaled = false;//是否需要固定字体大小,默认不需要,即跟随系统字体大小调整 //对于需要固定字体大小的界面,重写这个方法,返回true public boolean enableFixSacle() { return fixscaled; } @Override public Resources getResources() { Resources resources = super.getResources(); //拿到启动页Activity中获取并保存的系统字体大小的值 float originScale = SpUtils.getPrefFloat("fontScale", 0); Configuration configuration = resources.getConfiguration(); if (enableFixSacle()) {//需要固定字体大小界面 if (configuration != null && configuration.fontScale != 1.0f) {//如果不是标准大小字体,就改为标准大小 configuration.fontScale = 1.0f; resources.updateConfiguration(configuration, resources.getDisplayMetrics()); } } else if (originScale != 0) {//随系统字体大小变换界面 if (configuration != null && configuration.fontScale != originScale) {//当前字体大小不等于系统字体大小,就改为系统大小 configuration.fontScale = originScale; resources.updateConfiguration(configuration, resources.getDisplayMetrics()); } } //这里可以显示一下是哪些界面 Log.e("fontScle", configuration.fontScale + ",\t"+getClass().getSimpleName()); return resources; }

此外,还需要在启动界面的Activity中获取到当前系统的字体大小的值,代码如下:

//下面的方法建议放在启动页 Activity public void getSysytemFontScale() { float fontScaleSystem = 1.0f;//如果反射取不到系统fontScale,就取标准字体大小 //通過反射framework层的方法,获取当前系统字体大小 try { @SuppressLint("PrivateApi") Class activityManagerNative = Class.forName("android.app.ActivityManagerNative"); try { //调用ActivityManagerNative的getDefault()方法,获取到 IActivityManager 对象 Object defaultMethod = activityManagerNative.getMethod("getDefault").invoke(activityManagerNative); if (defaultMethod != null) { //调用IActivityManager中的getConfiguration()方法,获取到系统的 Configuration Configuration getConfiguration = (Configuration) defaultMethod.getClass().getMethod("getConfiguration").invoke(defaultMethod); if (getConfiguration != null) { fontScaleSystem = getConfiguration.fontScale; } } } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } SpUtils.setPrefFloat("fontScale", fontScaleSystem);//这里应该存系统的字体大小,而不是App中通过getResources()获取的configuration.fontScale //下面这部分代码仅做信息参考和对比学习,可以删除 Resources resources = getResources(); Configuration configuration = resources.getConfiguration(); float fontScale = configuration.fontScale;//修改并且更新或Configuration之后,再次修改系统字体大小,APP这里获取的fontScale不会再变,除非清空所有数据 Log.e("fontScle", "Splash===" + configuration.fontScale + "\tfontScaleSystem:" + fontScaleSystem); }

至此,就可以了,然后哪些界面需要固定字体大小,那么就重新 BaseActivity中的 enableFixScale() 方法,并返回 true即可。对于 下面这部分获取系统字体大小的方法是通过反射来实现的,因为通过测试后发现,如果 SP中保存的是 getResouce.getConfiguration().fontScale获取的字体大小倍数,并通过resources.updateConfiguration(a,b)更新了一次界面字体后,那么当系统字体大小再次发生变化时,APP中再次调用该方法获取fontScale的值,还是之前改过的值,并没有随系统调整后发生改变(至于原因,不知道怎么去深究,推测可能是因为update这个配置后,在APP的内部存储中会保存这个值,当再次获取得到的就是保存的值,但是如果没有update过这个配置,那么这样获取到的fontScale还是会跟随系统字体大小发生改变,有兴趣的伙伴可以深究一下,顺便分享给我),所以如果清空一下APP的所有数据,再进来字体就又会改变一次。综上,我通过反射直接获取到了系统的fontScale并进行保存,用来对比和操作。这块请务必理解。最后,这种方式,当调整系统字体大小后,APP重启后才会生效,因为系统字体大小的值我是在启动页获取并保存的。

三、原理解析

到底为什么设置为sp,会导致字体随系统字体大小而改变?

从文字设定大小的入口看,TextView.setTextSize(float size)方法来看:

/** * Set the default text size to the given value, interpreted as "scaled * pixel" units. This size is adjusted based on the current density and * user font size preference. * *

Note: if this TextView has the auto-size feature enabled than this function is no-op. * * @param size The scaled pixel size. * * @attr ref android.R.styleable#TextView_textSize */ @android.view.RemotableViewMethod public void setTextSize(float size) { setTextSize(TypedValue.COMPLEX_UNIT_SP, size); } /** * Set the default text size to a given unit and value. See {@link * TypedValue} for the possible dimension units. * *

Note: if this TextView has the auto-size feature enabled than this function is no-op. * * @param unit The desired dimension unit. * @param size The desired size in the given units. * * @attr ref android.R.styleable#TextView_textSize */ public void setTextSize(int unit, float size) { if (!isAutoSizeEnabled()) { setTextSizeInternal(unit, size, true /* shouldRequestLayout */); } } private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { Context c = getContext(); Resources r; if (c == null) { r = Resources.getSystem(); } else { r = c.getResources(); } setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), shouldRequestLayout); } @UnsupportedAppUsage private void setRawTextSize(float size, boolean shouldRequestLayout) { if (size != mTextPaint.getTextSize()) { mTextPaint.setTextSize(size); if (shouldRequestLayout && mLayout != null) { // Do not auto-size right after setting the text size. mNeedsAutoSizeText = false; nullLayouts(); requestLayout(); invalidate(); } } }

可以看到,如果没有设置字体单位的时候,默认会分配 TypedValue.COMPLEX_UNIT_SP ,即 sp 单位,而最终的值会通过TypedValue.applyDimension(unit, size, r.getDisplayMetrics()) 方法计算出来赋值给 setRawTextSize 方法,所以接下来看怎么计算的:

/** * Converts an unpacked complex data value holding a dimension to its final floating * point value. The two parameters unit and value * are as in {@link #TYPE_DIMENSION}. * * @param unit The unit to convert from. * @param value The value to apply the unit to. * @param metrics Current display metrics to use in the conversion -- * supplies display density and scaling information. * * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */ public static float applyDimension(int unit, float value, DisplayMetrics metrics) { switch (unit) { case COMPLEX_UNIT_PX: return value; case COMPLEX_UNIT_DIP: return value * metrics.density; case COMPLEX_UNIT_SP: return value * metrics.scaledDensity; case COMPLEX_UNIT_PT: return value * metrics.xdpi * (1.0f/72); case COMPLEX_UNIT_IN: return value * metrics.xdpi; case COMPLEX_UNIT_MM: return value * metrics.xdpi * (1.0f/25.4f); } return 0; }

可以看到,当单位为 COMPLEX_UNIT_SP时,取值为 value * metrics.scaleDensity;所以接下来看 metrics.scaleDensity 的取值:

/** * A scaling factor for fonts displayed on the display. This is the same * as {@link #density}, except that it may be adjusted in smaller * increments at runtime based on a user preference for the font size. */ public float scaledDensity; public void setTo(DisplayMetrics o) { if (this == o) { return; } widthPixels = o.widthPixels; heightPixels = o.heightPixels; density = o.density; densityDpi = o.densityDpi; scaledDensity = o.scaledDensity; xdpi = o.xdpi; ydpi = o.ydpi; noncompatWidthPixels = o.noncompatWidthPixels; noncompatHeightPixels = o.noncompatHeightPixels; noncompatDensity = o.noncompatDensity; noncompatDensityDpi = o.noncompatDensityDpi; noncompatScaledDensity = o.noncompatScaledDensity; noncompatXdpi = o.noncompatXdpi; noncompatYdpi = o.noncompatYdpi; } public void setToDefaults() { widthPixels = 0; heightPixels = 0; density = DENSITY_DEVICE / (float) DENSITY_DEFAULT; densityDpi = DENSITY_DEVICE; scaledDensity = density; xdpi = DENSITY_DEVICE; ydpi = DENSITY_DEVICE; noncompatWidthPixels = widthPixels; noncompatHeightPixels = heightPixels; noncompatDensity = density; noncompatDensityDpi = densityDpi; noncompatScaledDensity = scaledDensity; noncompatXdpi = xdpi; noncompatYdpi = ydpi; }

注释说明了,scaleDensity 不仅仅受设备的 density 影响,还受用户设定的字体尺寸影响。DisplayMetrics.scaleDensity 在 DisplayMetrics 类中,并没有初始化的地方,可它是一个 public 的字段,也就是说可以被外部赋值初始化。真正为 DisplayMetrics 中各个字段赋值的地方,在 ResourcesImpl 中,有一个 updateConfiguration() 方法,在其中,就有对 scaleDensity 进行初始化的逻辑。

public void updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat) { //省略部分代码 //........... if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { mMetrics.densityDpi = mConfiguration.densityDpi; mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; } // Protect against an unset fontScale. mMetrics.scaledDensity = mMetrics.density * (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f); //省略部分代码 //........... }

可以看到,这里又引入了一个新的计算因子,fontScale。而从 Configuration 的源码又了解到,fontScale 默认值为 1 ,这也就是为什么通常情况下,density 和 scaleDensity 的值是相等的,它们分别影响了 dp 和 sp 最终渲染出来的像素尺寸。

所以,我们要控制字体不随系统字体改变的本质,就是通过修改 fontScale 的值为1,这也就是我们方法1这么做的原因。

 

好文分享:当你在设置里修改字体大小的时候,到底在修改什么

Android View篇之 字体大小 调整滑杆的实现

Android 仿微信/支付宝 字体大小 调整控件

修改Android系统字体大小



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有