Android APP Layout

1、引言

最近有测试发现,之前在tv端工作正常的app放到车机上就无法打开了,由于开发app的同仁最近离职了,所以工作落我身上了。我平时很少做应用开发,相关的知识十分匮乏,为了更深刻地记住看到的知识,遂在此记录。

报错log如下:

07-23 10:11:10.913  3936  3936 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxx.file/com.xxx.file.PreViewActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7f080013

看起来是找不到资源文件,导致app无法打开。到res目录下查看现有的layout文件:

res/layout-sw1080dp-mdpi/
res/layout-sw720dp/
res/layout-sw576dp/
res/layout-sw480dp-hdpi/

看起来各个分辨率的layout都有了,询问测试,屏幕分辨率是1920x720,为什么就匹配不到呢?

layout中的sw、dp、mdpi、hdpi都是什么意思呢?

2、px

我们平时讲的分辨率为1920x1080,可以理解为水平方向一行有1920个像素点,垂直方向上有1080个像素点。像素(Pixel, px),这是屏幕上的最小显示单位。

3、dpi、mdpi、hdpi

先了解屏幕密度(Screen Density)的概念,屏幕密度是指屏幕上每英寸包含的像素数,单位是dpi(Dots Per Inch)。

既然知道了每英寸包含的像素数,又知道分辨率是1920x1080,那我们是不是可以计算得到屏幕的水平长度和垂直长度了呢?公式如下:

水平长度(宽)= 水平像素个数 / 屏幕密度
垂直长度(高)= 垂直像素个数 / 屏幕密度

屏幕密度的高低会影响图像和文本的呈现效果。

我们常在网吧看到一种27寸,1920x1080分辨率的显示器,屏幕大但是显示效果差,根据上述公式我们就知道这种屏幕的屏幕密度比较低。

现在的笔记本电脑分辨率一般会比较高,可以做到2560×1440或者是更高,但是他们的尺寸却很小,一般是14寸。这种屏幕的显示效果会很细腻,根据公式我们可以算到屏幕密度会很高。

Android对不同的dpi做了等级划分,以下是屏幕密度等级和对应的dpi值:

  • ldpi (low density): 120 dpi
  • mdpi (medium density): 160 dpi
  • hdpi (high density): 240 dpi
  • xhdpi (extra-high density): 320 dpi
  • xxhdpi (extra-extra-high density): 480 dpi
  • xxxhdpi (extra-extra-extra-high density): 640 dpi

在Android中,我们可以通过命令来查看屏幕密度和屏幕分辨率

$ wm density    # 屏幕密度
$ wm size       # 屏幕分辨率

我们根据wm density命令得到的结果就能够判断出设备是mdpi还是hdpi。

Android将mdpi(160dpi)作为系统的基准密度,即标准密度。mdpi主要用于较低分辨率的设备(诸如早期的中端手机),hdpi主要用于较高分辨率的设备(大多数现代中端手机)。

不同的屏幕密度会带来一些问题,我们举个例子来说明:

假设我们有两块正方形手机屏幕,他们都是1英寸,但是一块屏幕是mdpi,另一块是hdpi。这时候有一个160px的正方形图像需要显示,显示在mdpi屏幕上效果是铺满屏幕,但是显示在dhpi屏幕上却占不满,长宽只能占掉三分之二(160/240)。

为了能让hdpi也能满屏显示,那么被显示的图像分辨率需要提高:
mdpi:满屏显示图像分辨率为160px x 160px
hdpi:满屏显示图像分辨率为240px x 240px

根据以上例子,相同尺寸的图片在不同屏幕密度的屏幕上显示的图像大小(物理大小)是不一样的。现实中最常见的一个例子,在高分辨率的电脑上,如果不开缩放,显示的图标和文字将特别小,就是因为dpi很高,相同数量的像素点挤在一起,导致显示的大小比较小。

4、dp

上述问题会让开发者比较头大,如何在相同尺寸但不同dpi的屏幕上UI保持一致呢?为了解决这个问题,Android引入了dp的概念。

独立像素密度(Density-Independent Pixels, dp 或 dip):Android规定1dp在mdpi设备上等于1 px,但是在其他密度的设备上需要转换,转换公式如下:

1 dp = 1 x (dpi/160) px
  • ldpi: 1dp = 0.75px
  • mdpi: 1dp = 1 px
  • hdpi: 1dp = 1.5 px
  • xhdpi: 1dp = 2 px
  • xxhdpi: 1dp = 3 px
  • xxxhdpi: 1dp = 4 px

使用dp可以保证UI在不同dpi的设备上物理长度相同,我们来计算一下:
在mdpi设备上,1dp = 1/160 Inch
在hdpi设备上,1dp = 1.5/240 Inch = 1/160 Inch

5、sw

上面的dp是用于解决相同尺寸不同dpi的情况下显示能够统一的问题。不同尺寸的情况下,APP才能被良好显示呢?

举个例子,假设有个1Inch和3Inch的手机,它们的dpi相同,此时希望在屏幕上显示出一个表格,每个单元格占用1Inch,那么1Inch可以显示出一列表格,如果使用同样的layout,3Inch的手机也只能显示一列表格,这明显是不恰当的。所以我们也要为3Inch的手机设计一个layout,并且让APP能够自动匹配使用哪一个。

Android引入sw(smallest width),即设备的最小宽度(单位为dp)来处理这个问题。

sw的计算公式如下:

最小宽度 (dp) = (屏幕宽度(px) / (屏幕密度(dpi) / 160))
->
sw = px / (dpi / 160)

其实就是在算屏幕的长和宽分别是多少dp,先算1dp等于多少像素点,然后根据像素点数量算有多少dp。

为什么使用最小宽度?

我们要注意的是,最小宽度表示设备在任意方向上的最小宽度。这个值对所有方向都是恒定的,并且不会随着设备的旋转而改变。另外最小宽度一般可以代表最难布局的场景,通过从这个角度进行设计,能确保更好的适配。

6、资源文件的选择机制

Android系统根据以下优先级选择资源文件:

  1. 最匹配的最小宽度(smallestScreenWidthDp)。
  2. 当前屏幕方向(land 或 port)。
  3. 一般的其他限定符如语言、屏幕尺寸、显示密度等。

7、ppi

行业内用PPI(Pixels Per Inch)来描述对角线方向的分辨率

8、问题解决

回到一开始的问题,我们有如下layout:

res/layout-sw1080dp-mdpi/
res/layout-sw720dp/
res/layout-sw576dp/
res/layout-sw480dp-hdpi/

屏幕分辨率为1920x720:

$ wm size 
physical size: 1920x720

屏幕密度是213:

$ wm density
Physical density: 213

这时候我们可以算到,sw的值为720/213*160=540,为什么它不能匹配到layout-sw480dp-hdpi呢?

我们可以通过代码再来打印一下这些值:

import android.content.res.Configuration;
import android.util.DisplayMetrics;
import android.view.WindowManager;

Configuration configuration = getResources().getConfiguration();
int smallestScreenWidthDp = configuration.smallestScreenWidthDp;
Log.d("DeviceInfo", "smallestScreenWidthDp: " + smallestScreenWidthDp);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
float densityDpi = metrics.densityDpi;

float widthDp = widthPixels / (densityDpi / 160);
float heightDp = heightPixels / (densityDpi / 160);

Log.d("DeviceInfo", "Width in dp: " + widthDp);
Log.d("DeviceInfo", "Height in dp: " + heightDp);
Log.d("DeviceInfo", "densityDpi: " + densityDpi);

int orientation = configuration.orientation;

if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
    Log.d("DeviceInfo", "Orientation: Landscape");
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
    Log.d("DeviceInfo", "Orientation: Portrait");
}

//07-19 19:42:39.996  3407  3407 D DeviceInfo: smallestScreenWidthDp: 445
//07-19 19:42:39.996  3407  3407 D DeviceInfo: Width in dp: 1442.2535
//07-19 19:42:39.996  3407  3407 D DeviceInfo: Height in dp: 540.8451
//07-19 19:42:39.996  3407  3407 D DeviceInfo: densityDpi: 213.0 

嘿,打印的sw值竟然不对,smallestScreenWidthDp为445,这肯定就无法匹配了呀!

尝试拷贝一份layout-sw480dp-hdpi,改成layout-sw440dp,可以成功启动。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值