Android N新特性-屏幕缩放
Android 7.0 支持用户设置显示尺寸,以放大或缩小屏幕上的所有元素,从而提升设备对视力不佳用户的可访问性。用户无法将屏幕缩放至低于最小屏幕宽度 sw320dp,该宽度是 Nexus 4 的宽度,也是常规中等大小手机的宽度。
当设备密度发生更改时,系统会以如下方式通知正在运行的应用:
如果是面向 API 级别 23 或更低版本系统的应用,系统会自动终止其所有后台进程。这意味着如果用户切换离开此类应用,转而打开 Settings 屏幕并更改 Display size 设置,则系统会像处理内存不足的情况一样终止该应用。如果应用具有任何前台进程,则系统会如处理运行时更改中所述将配置变更通知给这些进程,就像对待设备屏幕方向变更一样。
如果是面向 android 7.0 的应用,则其所有进程(前台和后台)都会收到有关配置变更的通知,如处理运行时更改中所述。
大多数应用并不需要进行任何更改即可支持此功能,不过前提是这些应用遵循 Android 最佳做法。具体要检查的事项:
- 在屏幕宽度为 sw320dp 的设备上测试您的应用,并确保其充分运行。
- 当设备配置发生变更时,更新任何与密度相关的缓存信息,例如缓存位图或从网络加载的资源。当应用从暂停状态恢复运行时,检查配置变更。
注:如果您要缓存与配置相关的数据,则最好也包括相关元数据,例如该数据对应的屏幕尺寸或像素密度。保存这些元数据便于您在配置变更后决定是否需要刷新缓存数据。 - 避免用像素单位指定尺寸,因为像素不会随屏幕密度缩放。应改为使用与密度无关像素 (dp) 单位指定尺寸
Dpi,dp,ppi,sp,in&px参考
Android屏幕适配全攻略
Display Size分析
Display Size 对应手机的Settings->Display->Display size。用来缩放手机屏幕。
流程分析
1. screen_zoom_activity.xml
Display Size调整设置项对应的布局文件,通过seekbar来显示缩放等级,通过按钮来调整等级
2. CustomPreviewSeekBarPreferenceFragment.Java
final int max = Math.max(1, mEntries.length - 1)
final LabeledSeekBar seekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar)
seekBar.setLabels(mEntries)
seekBar.setMax(max)
seekBar.setProgress(mInitialIndex)
mEntries数组长度决定了缩放调整的级数,数组内容为每个等级的标签,mInitialIndex决定了当前所在的级数,第一次获取的则为默认等级。
3. ScreenZoomSettings.java
该类继承自PreviewSeekBarPreferenceFragment.java,该类确定了mEntries和mInitialIndex的值。这两个值都是由DisplayDensityUtils对象获得。
4. DisplayDensityUtils.java
mEntries和mInitialIndex可以看到是通过DisplayDensityUtils的getEntries()和getCurrentIndex()方法获得。而该方法中返回值搜索代码即可看到是在该类的构造方法中确定。
源码如下
public class DisplayDensityUtils {
/** Minimum increment between density scales. */
private static final float MIN_SCALE_INTERVAL = 0.09f;
/** Minimum density scale. This is available on all devices. */
private static final float MIN_SCALE = 0.85f;
/** Maximum density scale. The actual scale used depends on the device. */
private static final float MAX_SCALE = 1.50f;
/** Summary used for "default" scale. */
public static final int SUMMARY_DEFAULT = R.string.screen_zoom_summary_default;
/** Summary used for "custom" scale. */
private static final int SUMMARY_CUSTOM = R.string.screen_zoom_summary_custom;
/**
* Summaries for scales smaller than "default" in order of smallest to
* largest.
*/
private static final int[] SUMMARIES_SMALLER = new int[] {
R.string.screen_zoom_summary_small
};
/**
* Summaries for scales larger than "default" in order of smallest to
* largest.
*/
private static final int[] SUMMARIES_LARGER = new int[] {
R.string.screen_zoom_summary_large,
R.string.screen_zoom_summary_very_large,
R.string.screen_zoom_summary_extremely_large,
};
/**
* Minimum allowed screen dimension, corresponds to resource qualifiers
* "small" or "sw320dp". This value must be at least the minimum screen
* size required by the CDD so that we meet developer expectations.
*/
private static final int MIN_DIMENSION_DP = 320;
private final String[] mEntries;
private final int[] mValues;
private final int mDefaultDensity;
private final int mCurrentIndex;
public DisplayDensityUtils(Context context) {
final int defaultDensity = DisplayDensityUtils.getDefaultDisplayDensity(
Display.DEFAULT_DISPLAY);
if (defaultDensity <= 0) {
mEntries = null;
mValues = null;
mDefaultDensity = 0;
mCurrentIndex = -1;
return;
}
final Resources res = context.getResources();
final DisplayMetrics metrics = res.getDisplayMetrics();
final int currentDensity = metrics.densityDpi;
int currentDensityIndex = -1;
final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);
final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
final float maxScale = Math.min(MAX_SCALE, maxDensity / (float) defaultDensity);
final float minScale = MIN_SCALE;
final int numLarger = (int) MathUtils.constrain((maxScale - 1) / MIN_SCALE_INTERVAL,
0, SUMMARIES_LARGER.length);
final int numSmaller = (int) MathUtils.constrain((1 - minScale) / MIN_SCALE_INTERVAL,
0, SUMMARIES_SMALLER.length);
String[] entries = new String[1 + numSmaller + numLarger];
int[] values = new int[entries.length];
int curIndex = 0;
if (numSmaller > 0) {
final float interval = (1 - minScale) / numSmaller;
for (int i = numSmaller - 1; i >= 0; i--) {
final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
if (currentDensity == density) {
currentDensityIndex = curIndex;
}
entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
values[curIndex] = density;
curIndex++;
}
}
if (currentDensity == defaultDensity) {
currentDensityIndex = curIndex;
}
values[curIndex] = defaultDensity;
entries[curIndex] = res.getString(SUMMARY_DEFAULT);
curIndex++;
if (numLarger > 0) {
final float interval = (maxScale - 1) / numLarger;
for (int i = 0; i < numLarger; i++) {
final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
if (currentDensity == density) {
currentDensityIndex = curIndex;
}
values[curIndex] = density;
entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
curIndex++;
}
}
final int displayIndex;
if (currentDensityIndex >= 0) {
displayIndex = currentDensityIndex;
} else {
int newLength = values.length + 1;
values = Arrays.copyOf(values, newLength);
values[curIndex] = currentDensity;
entries = Arrays.copyOf(entries, newLength);
entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
displayIndex = curIndex;
}
mDefaultDensity = defaultDensity;
mCurrentIndex = displayIndex;
mEntries = entries;
mValues = values;
}
public String[] getEntries() {
return mEntries;
}
public int getCurrentIndex() {
return mCurrentIndex;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
构造函数首先根据默认DPI来计算可缩放的等级数->填充smaller的mEntries标签内容->设置默认密度等级的下标和填充标签->填充larger的mEntries的标签内容
计算等级数
final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);
final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
final float maxScale = Math.min(MAX_SCALE, maxDensity / (float) defaultDensity);
final float minScale = MIN_SCALE;
final int numLarger = (int) MathUtils.constrain((maxScale - 1) / MIN_SCALE_INTERVAL,
0, SUMMARIES_LARGER.length);
final int numSmaller = (int) MathUtils.constrain((1 - minScale) / MIN_SCALE_INTERVAL,
0, SUMMARIES_SMALLER.length);
String[] entries = new String[1 + numSmaller + numLarger];
defaultDensity是通过 DisplayDensityUtils.getDefaultDisplayDensity(Display.DEFAULT_DISPLAY)获取,该默认密度对应代码中ro.sf.lcd_density
numSmaller为最小为0,最大为1,此处值为1
numLarger则为0到3之间,具体取决于maxScale,它又取决于 maxDensity / (float) defaultDensity
此处以测试机5015为例,其ro.sf.lcd_density默认为240dpi,width为480px,height为854px
则defaultDensity为240
maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP
==>160*480/320 =240
则maxScale = 240/240 = 1
则numLarger = 0
修改Android DPI
代码路径:alps/device/sagetel/sr6580_we_n/system.prop,ro.sf.lcd_density修改该值则修改了手机的默认密度
填充smaller
设置默认密度
if (currentDensity == defaultDensity) {
currentDensityIndex = curIndex;
}
values[curIndex] = defaultDensity;
entries[curIndex] = res.getString(SUMMARY_DEFAULT);填充默认等级的描述
填充larger
等级客制化
可通过修改ro.sf.lcd_density值来改变缩放等级数,density越小,等级数越多,但最大5级。为了界面显示正常,我们需调整合适的density,density测试可通过
修改屏幕Dpi
adb shell wm density 240
adb shell wm density reset