A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 就业部_安卓组 于 2016-11-25 16:04 编辑




dp 适配

      我们总是在xml布局里写dpdp到底是什么意思呢?

      这是Android开发中特有的一种度量,称作屏幕无关像素,它不表示任何具体的长度或者像素点,这个值只有在具体屏幕密度的手机上,才会被转换为具体的像素值。
      它跟px不一样,咱们在xml里面写上px,那无论运行到任何设备上,就是固定的px,不会发生什么变化,但是dp就不一样了。

      比如拿以下几种来说明一下。

      分辨率(px      系统密度(dpi
      240x320                    120
      320x480                    160
      480x800                    240
      720x1280                  320
      1080x1920                480

      1dp转换的px是多少呢?
      其实就跟dpi有关,而基准线是160dpi,这就意味着1dp320x480这款手机上就是1px,在480x8001.5px720x1280上是2px1080x1920上是3px

      这就是dp

      比如现在有这么个需求,要求一个控件的宽度占屏幕的一半,我们用dp怎么来实现呢?
      首先进行计算,比如320x480,它的宽是320px,那我们就要写160px,用dp来表示就是160dp。所以我们可以在xml布局里写160dp就可以了,同样的方式试一试其他的手机:

      比如240x320,你写160dp在这种手机上转换的px就是 160dp*120/160 = 120px,所以在这种手机上160dp代表的就是120px,刚好是240的一半,也能适配。
      再来试一试480x800,想在这种手机上也占一半,必须是240px,而咱们写的是160dp,它转换的px就是 160dp*240/160=240px,刚好也能符合需求。

      其实Googledp这个东西本身就能达到适配的概念,你看,咱们写了一个160dp,跑到这几种手机上都能达到需求,这不就是适配了吗?

      但是我之所以提出来了,就说明dp并不能保证完美适配,比如咱们试试720x1280
      720的一半是360px,而咱们写的160dp,转换的px160dp*320/160=320px,明显就不是一半,差了40px。也就是说咱们的代码运行到这种手机上的时候就没有适配好,没有达到需求。
      
      再试试1080x1920的,同样的计算,160dp*480/160=480,而1080的一半是540480540差了60px,也不行,没有达到需求。

      由此可见,当在240x320320x480480x800上面的时候写160dp是正常的,可以满足需求。而在720x12801080x1920上面却不能完美适配。
      问题抛出来了,该怎么解决这个问题呢?
      比如720p1080p,我们通过计算,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" />

      可是,这么一写又有问题出现了,在240x320320x480480x800上面又坏了,这可怎么办呢?

      千呼万唤始出来,dimens+dp
      
      我的天,说了这么多废话终于到正题了。
      Android values文件夹下面可以创建一个dimens的文件。这个dimens的作用就跟mipmapdrawable的引用方式一样,可以通过@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_widthdimen,值为160dp,如图:


      同时再打开values-1280x720文件夹下面的dimens,创建一个相同的名为activity_horizontal_width,但是值为180dp,如图:


      同样的步骤,在values-1920x1080文件夹下面的dimens里也创建一个同样的180dpdimen


      做好了这些之后,接下来我们需要在xml布局里引用,还记得之前的TextViewwidth写的多少吧?

      之前写的是160dp,是一个固定值,而现在就不能这么写了,我们需要写成“@dimen/activity_horizontal_width”,这个什么意思呢?其实这里只是一个引用,当我们点击想看看具体是多少的值的时候,会发现这样的情况,如图:


      这个意思就是,当前我们写的“@dimen/activity_horizontal_width”引用了三个地方,问我们想去查看哪一个文件夹下面的dimens
      做好了这一切之后,这个TextView的值就是一个变的,下次再运行到手机上的时候,720p手机上运行时TextView的宽度就是180dp1080p手机上也是180dp,其他的手机就是160dp(因为我们没有创建其他分辨率的文件夹,所以会走默认的values)
      这就达到了指定机型的屏幕适配。

百分比适配

      虽然上述的dimens+dp能达到适配的目的,但是项目中如果要适配市场上常见的机型时,我们只能一个个的去计算,然后写上我们计算好的dp值,而且ui妹子在效果图上标记的都是px,我们还要根据这个再计算转换,很麻烦,有没有那种一劳永逸的?
      参考了hongyang的博客之后,有这么一套适配方案,如下:
      思路:把任何设备的手机宽度像素均分为320份,高度像素均分为480份,使用我们写好的程序自动生成资源values-***×***文件夹,里面包含lay_x.xmllay_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.375px1920/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.xmllay.xml里面,这样就可以统一所有你想要的分辨率的单位了,无论在什么分辨率下,x320都是代表屏幕宽度,y480都是代表屏幕高度。
      当执行了上述代码之后,会在本地生成很多个values文件夹,我们把这些values拷贝到项目的res下面,如下所示:

注:
      分辨率为480x320的资源文件应放在res/values-480x320文件夹中;同理分辨率为1920x1080的资源文件应放在res/values-1920x1080文件夹中。(其中values-480x320是分辨率限定符)
必须在默认values里面也创建对应默认lay_x.xmllay_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"/>

      关于这种适配方案的详情,请查看hongyang的博客Android 百分比适配


       点此进入:Android 人事+技术总贴
       点此进入:Android 基础篇总贴
       点此进入:Android 进阶篇总贴
       点此进入:Android 讨论区

       以上言论,如有错误或者表达不准确或者有纰漏的地方还请指正,同时欢迎大家在评论区留言给予一定的建议,我会根据大家的意见进行相应的改正,谢谢各位!
更多图片 小图 大图
组图打开中,请稍候......

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马