换个思路理解px dp dpi
android中的dp在渲染前会将dp转为px,计算公式:
density = dpi (手机屏幕dpi)/ 160(UI标注图dpi);
px = density * dp;
从公式中我们可以知道,事实上我们做适配(android系统自带的dp做适配)时,都是使用计算的自身手机的dpi(这个在android系统运行时时会读取设备信息进行设置) 与 UI标注时的dpi比值(缩放比例),然后在渲染时得到我们真正的px大小,其实可以考虑下,为什么我们要用dpi比值来代表这个缩放比例,为什么不能用更直观的,比如,直接宽,或者高的px大小比值呢(这里也是引出头条适配方案)?
事实上,对于一界面,比如图片与图片之间的间隔、图片的大小等,用户往往对其物理尺寸是敏感的,但是这张图片下有多少个像素,你知道吗?答案是显然的。
屏幕像素(screen pixel)是显示设备成像的最小单位。和图像不同,我们看到的屏幕像素都是真实发光的物理元件,既然是物理原件就不可能无缝填满整个屏幕,当然像素大小、形状也可以自由定义。
对于不同的手机厂商,我们无法保证其单个像素的长宽比是一致的,如果是的话,估计android的适配真的可能就是采用px大小比值来做了。
因此,android 才会使用dpi,计算斜角下的每物理尺寸下的像素个数的比值来作为适配的缩放系数,来屏蔽这个问题。
现有dp方案的缺陷
那使用dp的话,是不是真的就能做到完美适配呢?这好像是废话。事实上,对于一定dpi下的屏幕适配是能够做到较完美的适配的,然后对于dpi相差较大时,使用dp来进行屏幕适配的话,就会出现很多的问题。
我们可以查看源码得知android获取dpi过程:
其中ro.sf.lcd_density中获取dpi如下代码:
#define VIRTUAL_SIZE "/sys/class/graphics/fb0/virtual_size"
#define BUF_SIZE 64
void init_msm_properties(unsigned long msm_id, unsigned long msm_ver, char *board_type)
{
char platform[PROP_VALUE_MAX];
int rc;
unsigned long virtual_size = 0;
char str[BUF_SIZE];
UNUSED(msm_id);
UNUSED(msm_ver);
rc = property_get("ro.board.platform", platform);
if (!rc || !ISMATCH(platform, ANDROID_TARGET)){
return;
}
rc = read_file2(VIRTUAL_SIZE, str, sizeof(str));
if (rc) {
virtual_size = strtoul(str, NULL, 0);
}
if(virtual_size >= 1080) {
if (ISMATCH(board_type, "SBC")) {
property_set(PROP_LCDDENSITY, "240");
property_set(PROP_QEMU_NAVKEY, "0");
} else
property_set(PROP_LCDDENSITY, "480");
} else if (virtual_size >= 720) {
// For 720x1280 resolution
property_set(PROP_LCDDENSITY, "320");
} else if (virtual_size >= 480) {
// For 480x854 resolution QRD.
property_set(PROP_LCDDENSITY, "240");
} else
property_set(PROP_LCDDENSITY, "320");
if (msm_id >= 239 && msm_id <= 243) {
property_set("media.msm8939hw", "1");
}
if (msm_id >= 268 && msm_id <= 271) {
property_set("media.msm8929hw", "1");
}
if (msm_id == 206) {
property_set("vidc.enc.narrow.searchrange", "0");
}
}
kernel/drivers/video/fbsysfs.c
static ssize_t show_virtual(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xres_virtual,
fb_info->var.yres_virtual);
}
在Msm_fb.c (drivers\video\msm) 定义fb_info->var.xres_virtual
var->xres_virtual = panel_info->xres;
var->yres_virtual = panel_info->yres * mfd->fb_page +
((PAGE_SIZE - remainder)/fix->line_length) * mfd->fb_page;
所以手机屏幕dpi 最终是由分辨率决定的。
到这里我们能够发现一个问题,若我们不认为修改dpi值,则系统获取的最大值为480,即缩放系数为480/160 = 3。假设UI标注使用的是320 * 480的分辨率,那么对于1080 * 1920分辨率手机而言,UI并未完全设计整个屏幕。
目前主流设计分辨率为 720 x 1280(@2X)、1080 x 1920(@3X)两个分辨率。假设UI标注使用的是1080 * 1920 的分辨率其dpi为480,而我们手机屏幕相同分辨率,但是其dpi<480(这是极有可能的),那界面则即有可能会溢出,当相同分辨率下我们手机屏幕dpi大于UIdpi时,则手机屏幕不会被完全填充。
头条适配方案的可行性
从上面分析看以看出,我们使用android 提供的使用dp单位来进行屏幕适配,想要完美进行适配必须尽量满足两个条件,
- 手机屏幕dpi需要与UI标注时提供的标准dpi尽量保持一致,这也是为什么当dpi相差较大时我们只能够使用宽度限定符适配,针对不同的dpi书写各自的布局,达到适配效果
- 手机屏幕dpi与UI标注时提供的标注图一致时,手机屏幕的分辨率需要与标注图的一致
头条的适配方案核心代码如下:
// targetDensity 手动计算的缩放倍数,sizeInDp 为标注图的分辨率
if (isBaseOnWidth) {
targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
} else {
targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
}
// targetScaledDensity 字体的缩放倍数,默认与targetDensity 保持一致,随着设置中的字体大小被修改时而被修改
if (AutoSizeConfig.getInstance().getPrivateFontScale() > 0) {
targetScaledDensity = targetDensity * AutoSizeConfig.getInstance().getPrivateFontScale();
} else {
float systemFontScale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance().
getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity();
targetScaledDensity = targetDensity * systemFontScale;
}
// 手动计算dpi
targetDensityDpi = (int) (targetDensity * 160);
targetScreenWidthDp = (int) (AutoSizeConfig.getInstance().getScreenWidth() / targetDensity);
targetScreenHeightDp = (int) (AutoSizeConfig.getInstance().getScreenHeight() / targetDensity);
可以看出头条适配方案,主要是针对单一维度来进行适配的,通过宽或者高(对于目前的手机而言,基本只会基于宽,原因手机宽高比从以前的2:3、3:5、9:16、9:18、9:18.5,如果对高进行适配的话,一定程度上设计会超出屏幕)来手动计算缩放比列,从而修改系统的dpi属性,从而来达到至少在一个维度上的完美适配,在正常情况下,另一个维度上也能达到很好的效果。
其优缺点如下:
优点:
- 只需要在初始化时调用上述方法修改系统dpi值即可,使用成本非常低,操作非常简单。
- 侵入性非常低,完全不影响android 其他api,如bitmap等内部逻辑。
- 适用所有手机屏幕分辨率,都能做到一定程度的适配。
缺点:
- 个人觉得在一个维度上进行适配,能够让界面的控件大小,相对位置等都与设计图保持一致,但是在另一个维度上的话,可能并不能达到良好的效果。
对于另一个维度上的适配效果的讨论,我们可以在一个case进行,
两种手机屏幕,其一物理尺寸一致, 其中一种为1080 * 1920
其二,当宽的像素个数保持一致,像素的物理尺寸宽高比>1时,其分辨率为1080 * (>1920)
此时,我们能够得出,对于缩放系数保持不变,但是实际上对于界面的高,实际会比标注的图片高度会变低。
反之若像素的物理尺寸宽高比<1时,其分辨率为1080 * (<1920),对于界面的高,实际会比标注的图片高度会变高。
转载我博客应当经我允许,至少要把原文链接放在文章最前面,这是对本人辛苦原创基本的尊重。