dp 适配
我们总是在xml布局里写dp,dp到底是什么意思呢?
这是Android开发中特有的一种度量,称作屏幕无关像素,它不表示任何具体的长度或者像素点,这个值只有在具体屏幕密度的手机上,才会被转换为具体的像素值。
它跟px不一样,咱们在xml里面写上px,那无论运行到任何设备上,就是固定的px,不会发生什么变化,但是dp就不一样了。
比如拿以下几种来说明一下。
分辨率(px) 系统密度(dpi)
240x320 120
320x480 160
480x800 240
720x1280 320
1080x1920 480
1dp转换的px是多少呢?
其实就跟dpi有关,而基准线是160dpi,这就意味着1dp在320x480这款手机上就是1px,在480x800是1.5px,720x1280上是2px,1080x1920上是3px。
这就是dp。
比如现在有这么个需求,要求一个控件的宽度占屏幕的一半,我们用dp怎么来实现呢?
首先进行计算,比如320x480,它的宽是320个px,那我们就要写160px,用dp来表示就是160dp。所以我们可以在xml布局里写160dp就可以了,同样的方式试一试其他的手机:
比如240x320,你写160dp在这种手机上转换的px就是 160dp*120/160 = 120px,所以在这种手机上160dp代表的就是120px,刚好是240的一半,也能适配。
再来试一试480x800,想在这种手机上也占一半,必须是240px,而咱们写的是160dp,它转换的px就是 160dp*240/160=240px,刚好也能符合需求。
其实Google出dp这个东西本身就能达到适配的概念,你看,咱们写了一个160dp,跑到这几种手机上都能达到需求,这不就是适配了吗?
但是我之所以提出来了,就说明dp并不能保证完美适配,比如咱们试试720x1280。
720的一半是360px,而咱们写的160dp,转换的px是160dp*320/160=320px,明显就不是一半,差了40px。也就是说咱们的代码运行到这种手机上的时候就没有适配好,没有达到需求。
再试试1080x1920的,同样的计算,160dp*480/160=480,而1080的一半是540,480跟540差了60px,也不行,没有达到需求。
由此可见,当在240x320,320x480,480x800上面的时候写160dp是正常的,可以满足需求。而在720x1280,1080x1920上面却不能完美适配。
问题抛出来了,该怎么解决这个问题呢?
比如720p和1080p,我们通过计算,720/2=360,而根据dpi的比例320/160=2,所以我们要想再720p的手机上占宽度的一半,dp值应该是360px/2=180dp。
反过来算一下,如果我们写180dp,那在720p上转换的像素是180dp*320/160=360px,是720的一半,换到1080p上,180dp*480/160=540px,刚好是1080的一半,那也就是说,咱们写 180dp就可以实现了需求,那咱们就可以在xml布局里这么写:
<TextView
android:layout_width="180dp"
android:layout_height="wrap_content" />
可是,这么一写又有问题出现了,在240x320,320x480,480x800上面又坏了,这可怎么办呢?
千呼万唤始出来,dimens+dp
我的天,说了这么多废话终于到正题了。
Android 的values文件夹下面可以创建一个dimens的文件。这个dimens的作用就跟mipmap和drawable的引用方式一样,可以通过@dimens/xxx的方式来引用。我们的思路就是创建多套dimens,然后xml布局引用dimens,当运行到不同的手机上时,系统会自动去寻找相应的dimens,去加载我们提前准备好的dp值,图文教程:
首先,系统默认的values下面就有个dimens文件
我们先不管这个,仿照系统这个自己创建一个values
选中res文件夹,然后点击右键,选中Androidresource directory
然后在这里给Directory name命名为values-1280x720,这个意思是说,当检测当手机是1280x720的时候,系统会去找此文件夹下面的dimens,而不是默认的values文件夹。
创建好了之后新建一个dimens文件,结果是这个样子的,如图:
我们仿照刚才的过程再创建一个values-1920x1080的文件夹。
做好这几个步骤之后,点击values下面的dimens,然后创建一个dimen,如下图所示,我在此创建了一个名为activity_horizontal_width的dimen,值为160dp,如图:
同时再打开values-1280x720文件夹下面的dimens,创建一个相同的名为activity_horizontal_width,但是值为180dp,如图:
同样的步骤,在values-1920x1080文件夹下面的dimens里也创建一个同样的180dp的dimen
做好了这些之后,接下来我们需要在xml布局里引用,还记得之前的TextView的width写的多少吧?
之前写的是160dp,是一个固定值,而现在就不能这么写了,我们需要写成“@dimen/activity_horizontal_width”,这个什么意思呢?其实这里只是一个引用,当我们点击想看看具体是多少的值的时候,会发现这样的情况,如图:
这个意思就是,当前我们写的“@dimen/activity_horizontal_width”引用了三个地方,问我们想去查看哪一个文件夹下面的dimens。
做好了这一切之后,这个TextView的值就是一个变的,下次再运行到手机上的时候,720p手机上运行时TextView的宽度就是180dp,1080p手机上也是180dp,其他的手机就是160dp(因为我们没有创建其他分辨率的文件夹,所以会走默认的values)。
这就达到了指定机型的屏幕适配。
百分比适配
虽然上述的dimens+dp能达到适配的目的,但是项目中如果要适配市场上常见的机型时,我们只能一个个的去计算,然后写上我们计算好的dp值,而且ui妹子在效果图上标记的都是px,我们还要根据这个再计算转换,很麻烦,有没有那种一劳永逸的?
参考了hongyang的博客之后,有这么一套适配方案,如下:
思路:把任何设备的手机宽度像素均分为320份,高度像素均分为480份,使用我们写好的程序自动生成资源values-***×***文件夹,里面包含lay_x.xml和lay_y.xml,分别对应宽度和高度的像素。
现在我们以320x480的分辨率为基准:
将屏幕的宽度分为320份,取值为x1~x320
将屏幕的高度分为480份,取值为y1~y480
然后生成该分辨率对应像素数的列表,如下:
lay_x.xml(宽):
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">1.0px</dimen>
<dimen name="x2">2.0px</dimen>
<dimen name="x3">3.0px</dimen>
<dimen name="x4">4.0px</dimen>
<dimen name="x5">5.0px</dimen>
<dimen name="x6">6.0px</dimen>
<dimen name="x7">7.0px</dimen>
<dimen name="x8">8.0px</dimen>
<dimen name="x9">9.0px</dimen>
<dimen name="x10">10.0px</dimen>
...
<dimen name="x300">300.0px</dimen>
<dimen name="x301">301.0px</dimen>
<dimen name="x302">302.0px</dimen>
<dimen name="x303">303.0px</dimen>
<dimen name="x304">304.0px</dimen>
<dimen name="x305">305.0px</dimen>
<dimen name="x306">306.0px</dimen>
<dimen name="x307">307.0px</dimen>
<dimen name="x308">308.0px</dimen>
<dimen name="x309">309.0px</dimen>
<dimen name="x310">310.0px</dimen>
<dimen name="x311">311.0px</dimen>
<dimen name="x312">312.0px</dimen>
<dimen name="x313">313.0px</dimen>
<dimen name="x314">314.0px</dimen>
<dimen name="x315">315.0px</dimen>
<dimen name="x316">316.0px</dimen>
<dimen name="x317">317.0px</dimen>
<dimen name="x318">318.0px</dimen>
<dimen name="x319">319.0px</dimen>
<dimen name="x320">320px</dimen>
</resources>
然后lay_y.xml(高):
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="y1">1.0px</dimen>
<dimen name="y2">2.0px</dimen>
<dimen name="y3">3.0px</dimen>
<dimen name="y4">4.0px</dimen>
...
<dimen name="y480">480px</dimen>
</resources>
我们直接在dimens里面写上具体的px值,而不是dp值,这样给定一个写死的值,在手机上运行的时候就是我们所写上的px值。可能到这里各位又有疑问了,这样的话怎么做到适配的呢?别着急,我们假设手机屏幕的宽度都是320某单位,那么我们将一个屏幕宽度的总像素数平均分成320份,每一份对应具体的像素就可以了。我们可以创建多个values文件夹,多到覆盖市面上绝大多数流行的手机机型,并且用相应的比例计算好px值,由于现在是以320x480为基准的,所以如果我们要编写其他的values,要进行如下计算,比如我们以1920x1080为例:
由于基准是320x480,所以1080/320=3.375px,1920/480=4px,所以相应文件应该是这样的
lay_x.xml
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">3.375px</dimen>
<dimen name="x2">6.65px</dimen>
<dimen name="x3">10.125px</dimen>
...
<dimen name="x320">1080px</dimen>
</resources>
lay_y.xml
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="y1">4px</dimen>
<dimen name="y2">8px</dimen>
<dimen name="y3">12px</dimen>
<dimen name="y4">16px</dimen>
...
<dimen name="y480">1920px</dimen>
</resources>
我们可以按照这种计算的方式补全当今市面上流行的机型。
但是我们怎么可能会去做这么蠢的事呢?肯定有工具类之类的东西!在此,再次感谢hongyang提供的工具类,工具类代码如下:
[Java] 纯文本查看 复制代码
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
public class MakeXml {
private final static String rootPath = "C:\\Users\\Administrator\\Desktop\\layoutroot\\values-{0}x{1}\\";
private final static float dw = 320f;
private final static float dh = 480f;
private final static String WTemplate = "[dimen name=\"x{0}\"]{1}px[/dimen]\n";
private final static String HTemplate = "[dimen name=\"y{0}\"]{1}px[/dimen]\n";
public static void main(String[] args) {
makeString(320, 480);
makeString(480,800);
makeString(480, 854);
makeString(540, 960);
makeString(600, 1024);
makeString(720, 1184);
makeString(720, 1196);
makeString(720, 1280);
makeString(768, 1024);
makeString(800, 1280);
makeString(1080, 1812);
makeString(1080, 1920);
makeString(1440, 2560);
}
public static void makeString(int w, int h) {
StringBuffer sb = new StringBuffer();
sb.append("[?xml version=\"1.0\" encoding=\"utf-8\"?]\n");
sb.append("[resources]");
float cellw = w / dw;
for (int i = 1; i < 320; i++) {
sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
change(cellw * i) + ""));
}
sb.append(WTemplate.replace("{0}", "320").replace("{1}", w + ""));
sb.append("[/resources]");
StringBuffer sb2 = new StringBuffer();
sb2.append("[?xml version=\"1.0\" encoding=\"utf-8\"?]\n");
sb2.append("[resources]");
float cellh = h / dh;
for (int i = 1; i < 480; i++) {
sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
change(cellh * i) + ""));
}
sb2.append(HTemplate.replace("{0}", "480").replace("{1}", h + ""));
sb2.append("[/resources]");
String path = rootPath.replace("{0}", h + "").replace("{1}", w + "");
File rootFile = new File(path);
if (!rootFile.exists()) {
rootFile.mkdirs();
}
File layxFile = new File(path + "lay_x.xml");
File layyFile = new File(path + "lay_y.xml");
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
pw.print(sb.toString());
pw.close();
pw = new PrintWriter(new FileOutputStream(layyFile));
pw.print(sb2.toString());
pw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static float change(float a) {
int temp = (int) (a * 100);
return temp / 100f;
}
}
代码应该很好懂,我们将一个屏幕宽度分为320份,高度480份,然后按照实际像素对每一个单位进行复制,放在对应values-widthxheight文件夹下面的lax.xml和lay.xml里面,这样就可以统一所有你想要的分辨率的单位了,无论在什么分辨率下,x320都是代表屏幕宽度,y480都是代表屏幕高度。
当执行了上述代码之后,会在本地生成很多个values文件夹,我们把这些values拷贝到项目的res下面,如下所示:
注:
分辨率为480x320的资源文件应放在res/values-480x320文件夹中;同理分辨率为1920x1080的资源文件应放在res/values-1920x1080文件夹中。(其中values-480x320是分辨率限定符)
必须在默认values里面也创建对应默认lay_x.xml和lay_y.xml文件,如下
lay_x.xml
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8">
<resources>
<dimen name="x1">1.0dp</dimen>
<dimen name="x2">2.0dp</dimen>
...
</resources>
因为对于没有生成对应分辨率文件的手机,会使用默认values文件夹,如果默认values文件夹没有(即没有对应的分辨率、没有对应dimen)就会报错,从而无法进行屏幕适配。
(注意对应单位改为dp,而不同于上面的px。因为不知道机型的分辨率,所以默认分辨率文件只好默认为x1=1dp以保证尽量兼容(又回到dp老方法了),这也是这个解决方案的一个弊端)
而我们怎么用呢?
工作中只需要根据UI妹子给出的某一个分辨率设计图的尺寸,然后找到对应像素数的单位,然后设置给控件就可以了,如下
[XML] 纯文本查看 复制代码
<TextView
android:text="@string/hello_world"
android:layout_width="@dimen/x160"
android:layout_height="@dimen/y160"/>