如何防止系统字体大小更改对Android应用程序的影响?

84

我最近完成了我的安卓应用程序开发。我在所有文本大小上使用了sp(缩放像素)。问题是当我调整系统字体大小时,我的应用程序的字体大小也会改变。我可以使用dp(设备独立像素),但这将需要太长时间来维护我的应用程序。

我参考了这里的文本大小。

有没有办法防止系统字体大小的变化影响到我的应用程序?

14个回答

95

如果您希望文本保持相同的大小,您将需要使用 dp

引用 文档:

sp 是基于相同单位的,但是会根据用户偏好的文本大小进行缩放的(它是一个缩放无关的像素),因此定义文本大小时应使用此测量单位(但永远不要用于布局大小)。

我强调了一下。

因此,如果您将 sp 作为文本大小的单位,您将看到预期的行为。

我不明白您所说的使用 dp 需要太长时间来维护应用程序 - 据我所知,这将需要完全相同的努力?(也许少一些,但可能会使视力差的用户难以使用)


我同意你的解释。但在IOS中,系统限制了特定应用程序(如日历、邮件等)。对于Android来说,没有限制。那么我能否防止我的应用程序更改系统字体大小呢? - james
1
你无法阻止用户更改系统字体大小;你只能使用dp并忽略lint错误!这里有一篇关于如何禁用lint错误的博客文章,可能会有用。 - Adam S
3
虽然您可能需要一种特定的字体大小,并且需要保持不变,但我建议您考虑如何在布局中容纳不同的字体大小,而不是完全避免。这将为您的用户提供更好的使用体验。 - Adam S
3
如果您有一个可工作的应用程序,将所有sp值切换为dp值是容易出错并需要更多时间才能完成。如果选择再次切换以支持它,您需要撤消所有这些更改。覆盖父活动中的fontScale比更改资源需要更少的工作量。这也更易于维护。在下面回答,覆盖Context是正确的方法。 - parohy
1
开发人员可以在Android清单中使用简单的设置,以不遵循系统文本和显示大小设置。 - chitgoks
显示剩余2条评论

67

之前的答案都没能在我的Android 8.1 (API 27)上生效。这是可行的解决方案:将以下代码添加到您的Activity中:

Kotlin代码:

override fun attachBaseContext(newBase: Context?) {
    
    val newOverride = Configuration(newBase?.resources?.configuration)
    newOverride.fontScale = 1.0f
    applyOverrideConfiguration(newOverride)

    super.attachBaseContext(newBase)
}

Java代码:

@Override
protected void attachBaseContext(Context newBase) {
    
    final Configuration override = new Configuration(newBase.getResources().getConfiguration());
    override.fontScale = 1.0f;
    applyOverrideConfiguration(override);

    super.attachBaseContext(newBase);
}

你不需要改变你的 AndroidManifest.xml


3
这是适用于Android API > N的解决方案,未在N以下的设备上进行测试。 - Dr. DS
1
确认它在Android 12上能够正常工作。但是super.attachBaseContext(newBase);需要移动到attachBaseContext()的最后一行。 - Robert
1
它运行得非常好,这个答案节省了我的时间。点赞! - basaveshwar lamture
1
对于Java,您可以使用override.setToDefaults();来确保每个设备的默认设置将被重置,而不必担心每个设备的不同设置。 - Eiri
1
我添加了这个代码,但在onConfigurationChange方法中遇到了问题。屏幕旋转后,屏幕方向没有更新。似乎将此代码添加到活动中会阻止进一步的配置更新。 - mmdreza baqalpour
显示剩余8条评论

65

我最近也遇到了这个问题。我们的用户界面在屏幕尺寸有限的手机上不能很好地缩放,而为了应对用户设置他们的辅助选项为“巨大”时的情况,更改整个用户界面似乎是不明智的。

我发现这篇 StackOverflow 上的问题 最有帮助。

我在我的 BaseActivity(所有活动都会扩展此 Activity 类)中添加了以下代码:

public void adjustFontScale(Configuration configuration) {
    if (configuration.fontScale > 1.30) {
        LogUtil.log(LogUtil.WARN, TAG, "fontScale=" + configuration.fontScale); //Custom Log class, you can use Log.w
        LogUtil.log(LogUtil.WARN, TAG, "font too big. scale down..."); //Custom Log class, you can use Log.w
        configuration.fontScale = 1.30f;
        DisplayMetrics metrics = getResources().getDisplayMetrics();
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);
        metrics.scaledDensity = configuration.fontScale * metrics.density;
        getBaseContext().getResources().updateConfiguration(configuration, metrics);
    }
}

然后就像这样,在我的super.onCreate()之后立即调用它

adjustFontScale(getResources().getConfiguration());

这段代码的作用是判断用户是否将其辅助功能设置中的字体比例放大到了1.30f以上(1.30f在Note 5上是“大号”,但在不同设备上可能会略有不同)。如果用户将字体放得过大(“特大号”,“巨型号”...),我们只将应用程序缩放到“大号”。

这使您的应用程序能够根据用户偏好进行一定程度的缩放,而不会扭曲您的UI。希望这能帮助其他人。祝缩放顺利!

其他提示

如果您希望某些布局随字体大小一起缩放(例如...您用作与字体背景相对的RelativeLayout),则可以使用sp而不是经典的dp来设置其宽度/高度。当用户更改字体大小时,布局将相应地随着应用程序中的字体而变化。这是一个很好的小技巧。


我可以问一下,为什么你的示例与你链接的问题略有不同,涉及到这一行代码:WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);。我是Android新手,正在尝试选择“最佳”答案。谢谢! - Tallboy
2
@Tallboy 很好的问题。我曾经看到过代码通过两种方式获取窗口管理器(像我所做的那样使用getSystemService调用和像我链接的示例一样使用getWindowManager())。我刚刚测试了我的代码,两种方法都可以正常工作。我相信我在某个地方读到过getWindowManager返回一个只读的Window Manager对象,这可能是两种方法之间的关键区别,但对于这个示例来说并不重要。 - welshk91
3
这个在Nougat上不起作用,在Marshmallow及以下版本可以正常工作。 - Nayan
我在整个网络上搜索了几天,这是唯一与电容器兼容的解决方案!谢谢! - nachshon f
updateConfiguration方法已过时! - Suneesh Ambatt
这在我使用 Capacitor 3 后停止工作了。 - nachshon f

11

这就是我们的做法。 在类中像这样覆盖。 如果您希望为不同的活动获取不同的行为-在Activity中覆盖。

不要忘记添加清单标签android:configChanges="fontScale",因为您自己处理此配置更改。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // In some cases modifying newConfig leads to unexpected behavior,
    // so it's better to edit new instance.
    Configuration configuration = new Configuration(newConfig);
    SystemUtils.adjustFontScale(getApplicationContext(), configuration);
}

在一些辅助类中,我们有一个adjustFontScale()方法。

public static void adjustFontScale(Context context, Configuration configuration) {
    if (configuration.fontScale != 1) {
        configuration.fontScale = 1;
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);
        metrics.scaledDensity = configuration.fontScale * metrics.density;
        context.getResources().updateConfiguration(configuration, metrics);
    }
}

警告!这将完全忽略辅助功能字体比例设置,防止您的应用程序字体缩放!


1
它不起作用。onConfigurationChanged从未被触发。 - zed
@zed 在清单文件中的应用程序或活动中设置android:configChanges="orientation|screenSize"。 - speedynomads
此解决方案仅适用于平板电脑,而不适用于智能设备。 - Praveen Kumar Verma
它在Nexus 5-Android 6上运行,我会在其他设备上进行检查。 - Sameer Z.
查看了几个 updateConfiguration 的源代码实现(在 5.1 和 10 中),手动更新 scaledDensity 是不必要的。它们已经为您完成了。 - Peter

6

这是2018年的做法(Xamarin.Android/C# - 在其他语言中采用同样的方法):

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle bundle)
    {
...
    }

    protected override void AttachBaseContext(Context @base)
    {
        var configuration = new Configuration(@base.Resources.Configuration);

        configuration.FontScale = 1f;
        var config =  Application.Context.CreateConfigurationContext(configuration);

        base.AttachBaseContext(config);
    }
}

你只需要重写活动的 attachBaseContext 方法并在那里更新配置即可。

getBaseContext()。getResources()。updateConfiguration()虽然有许多使用此方法的示例,但已被弃用。如果您使用此方法,除了IDE警告之外,您可能会发现应用程序的某些部分没有按比例缩放。


4

还有一种方法可以防止应用程序布局问题/字体问题在设置字体大小更改时出现。你可以尝试

// ignore the font scale here
final Configuration newConfiguration = new Configuration(
    newBase.getResources().getConfiguration()
);

newConfiguration.fontScale = 1.0f;
applyOverrideConfiguration(newConfiguration);

其中newBase来自于attachBaseContext函数。你需要在你的Activity中覆盖这个回调函数。

但是,副作用是,如果你想要使用动画(objectanimator/valueanimator),那么它会导致奇怪的行为。


3

我尝试过在整个应用中禁用fontScale,虽然它能起到效果,但是我认为这是一个糟糕的想法,因为只有一个原因:

你并没有为视力障碍者提供更好的app体验。

我认为更好的方式是可以允许字体缩放,但只限于一些地方,在这些地方,你无法调整文本大小使其易读。


实现10.02.22

经过一天的思考后,我创建了一个TextViewKotlin扩展(也可以用于EditText,因为它是子类):

fun TextView.removeFontScale() {
    val fontScale = resources.configuration.fontScale
    if (fontScale != 1f) {
        val scaledTextSize = textSize
        val newTextSize = scaledTextSize / fontScale / fontScale
        textSize = newTextSize
    }
}

需要将scaledTextSize分成两部分,因为在设置TextView的新textSize后,字体缩放会发生。


更新/修复14.02.22

以前的解决方案在真实设备上不起作用,只在模拟器上起作用(反之亦然增加文本大小)。所以,我找到了另一种方法:

val scaledTextSize = textSize
val newTextSize = scaledTextSize / fontScale
setTextSize(TypedValue.COMPLEX_UNIT_PX, newTextSize)

在设置之后,TextView 的新的 textSize 不会根据 fontScale 值进行缩放。

使用示例:

titleText.removeFontScale()
subtitleText.removeFontScale()

工作日志,大小以1.3/1.5比例缩小后就和1.0比例一样了:

  • Old realization (10.02.22):

    fontScale: 1.0 | oldSize: 20    
    fontScale: 1.3 | oldSize: 26 | newSize: 20.0
    
  • New realization (14.02.22):

    fontScale: 1.5 | oldSize: 83.0 | newSize: 55.333332 | textSize: 55.333332
    fontScale: 1.5 | oldSize: 58.0 | newSize: 38.666668 | textSize: 38.666668
    

顺便说一句,我注意到标准的 工具栏 没有缩放字体,后来我明白这种做法是可以的(在真正需要禁用缩放的地方)。


关于“您不会为视力受损的人改进应用程序。”,有时恰恰相反。Android对xsmall和xbig字体大小的支持几乎为零,大多数视图将简单地切断文本到某种程度。自动缩小组件、多行和宽度&高度计算无法正常工作。有时候这会对用户造成更坏的影响而非帮助。 - htafoya

3

我遇到了相同的问题,并通过更改XML文件中的 spdp 来解决它。然而,我还需要调整 WebViews 的文本大小。

通常情况下,要调整 WebView 的文本大小,使用 setDefaultFontSize() 函数。但是,它的默认值单位是 sp

在我的项目中,我使用 setTextZoom(100) 来修正 WebView 的文本和图标大小。*(stackoverflow 上有其他方法,但几乎全部废弃不用)*

WebSettings settings = mWebView.getSettings();
settings.setTextZoom(100);

有关setTextZoom()的进一步详情请查看此处


3

您可以使用基础活动配置强制调整应用程序的文本大小,使所有活动都继承基础活动。将1.0f设置为字体大小将忽略系统设置并强制应用程序的字体大小为正常大小。

public  void adjustFontScale( Configuration configuration,float scale) {

configuration.fontScale = scale;
DisplayMetrics metrics = getResources().getDisplayMetrics();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity = configuration.fontScale * metrics.density;
getBaseContext().getResources().updateConfiguration(configuration, metrics);

}

@Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     adjustFontScale( getResources().getConfiguration(),1.0f);
}

0

我认为最接近的最佳答案来自以下用户:“如果您有一个可用的应用程序,并且将所有 sp 值切换为 dp 值很容易出错,需要更多时间来完成。然后,如果选择再次切换以支持它,您需要恢复所有这些更改。覆盖父活动中的 fontScale 才更合理。这比更改资源所需的工作量要小。这也更易于维护。在下面的回答中,覆盖 Context 是正确的方法。” - parohy" 2021年4月7日 5:18


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接