在使用TextView及相关控件的时候,设置字体大小时Google推荐sp。设置空间的宽高推荐dp。当修改系统字体大小的时候,字体大小以sp为单位时,大小随系统变化。当修改系统字体大小的时候,字体大小以dp为单位时,大小不变。
无论采用sp或者dp,最终都需要转成px, 在TypedValue类中,有转换关系的代码如下:
/**
* Converts an unpacked complex data value holding a dimension to its final floating
* point value. The two parameters <var>unit</var> and <var>value</var>
* 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;
}
可以看到dp和density有关,sp和scaleDensity有关。而scaleDensity随着系统字体的大小而改变,而density不会改变。
在改变系统默认字体大小的时,如果系统字体过大会导致布局混乱,很多时候为了简单适配,将字体的大小固定。一般采用如下方法
- 将字体大小单位改为dp
- 在Application(不要在BaseActivity中设置,特别是覆盖getResource方法)中设置fontScale属性。如下
val resources = super.getResources()
if (resources.configuration.fontScale!=1f) {
val configuration = resources.configuration
configuration.setToDefaults()
resources.updateConfiguration(configuration, resources.displayMetrics)
}
当设置完以上内容后,发现在一部分手机上,使用dp和sp显示不一样。
通过分析代码,发现两种获取scaledDensity的方法。
方法一
Resources resources = context.getApplicationContext().getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
displayMetrics.scaledDensity;
方法二
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager == null) {
return 0;
}
windowManager.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity;
通过打印发现在部分手机上两个值是不一样的。继续查看源码,看到如下方法
方法一中可以看到 scaledDensity等于fontscale.
public void updateConfiguration(Configuration config, DisplayMetrics metrics,
CompatibilityInfo compat) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
Slog.i(TAG, "**** Updating config of " + this + ": new config is "
+ config + " new compat is " + compat);
}
if (compat != null) {
mDisplayAdjustments.setCompatibilityInfo(compat);
}
if (metrics != null) {
mMetrics.setTo(metrics);
}
// NOTE: We should re-arrange this code to create a Display
// with the CompatibilityInfo that is used everywhere we deal
// with the display in relation to this app, rather than
// doing the conversion here. This impl should be okay because
// we make sure to return a compatible display in the places
// where there are public APIs to retrieve the display... but
// it would be cleaner and more maintainable to just be
// consistently dealing with a compatible display everywhere in
// the framework.
mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
final @Config int configChanges = calcConfigChanges(config);
// If even after the update there are no Locales set, grab the default locales.
LocaleList locales = mConfiguration.getLocales();
if (locales.isEmpty()) {
locales = LocaleList.getDefault();
mConfiguration.setLocales(locales);
}
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
// The LocaleList has changed. We must query the AssetManager's available
// Locales and figure out the best matching Locale in the new LocaleList.
String[] availableLocales = mAssets.getNonSystemLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
// No app defined locales, so grab the system locales.
availableLocales = mAssets.getLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
availableLocales = null;
}
}
if (availableLocales != null) {
final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
availableLocales);
if (bestLocale != null && bestLocale != locales.get(0)) {
mConfiguration.setLocales(new LocaleList(bestLocale, locales));
}
}
}
}
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);
final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
//noinspection SuspiciousNameCombination
width = mMetrics.heightPixels;
//noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
final int keyboardHidden;
if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
&& mConfiguration.hardKeyboardHidden
== Configuration.HARDKEYBOARDHIDDEN_YES) {
keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
} else {
keyboardHidden = mConfiguration.keyboardHidden;
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ mConfiguration + " final compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
}
mDrawableCache.onConfigurationChange(configChanges);
mColorDrawableCache.onConfigurationChange(configChanges);
mComplexColorCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
flushLayoutCache();
}
synchronized (sSync) {
if (mPluralRule != null) {
mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
第二种方法,发现scaledDensity等于density
private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
Configuration configuration, int width, int height) {
outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
outMetrics.density = outMetrics.noncompatDensity =
logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
final Rect appBounds = configuration != null
? configuration.windowConfiguration.getAppBounds() : null;
width = appBounds != null ? appBounds.width() : width;
height = appBounds != null ? appBounds.height() : height;
outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
compatInfo.applyToDisplayMetrics(outMetrics);
}
}
而Context.getResources().getDisplayMetrics()依赖于手机系统,获取到的是系统的屏幕信息;
WindowManager.getDefaultDisplay().getMetrics(dm)是获取到Activity的实际屏幕信息。存在虚拟键的手机会出现此类问题。
网友评论