欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android屏幕适配

程序员文章站 2022-05-24 13:25:01
...

为什么要进行屏幕适配

背景
由于android系统开源导致任何手机厂家或者用户都可以定制系统,修改成他们想要的样子从而导致屏幕尺寸的碎片化而并不是统一宽高尺寸和统一像素密度的屏幕。当我们开发一款app时,如果只对一款屏幕进行适配那,在其他尺寸或者分辨率的手机上肯定会导致显示有问题,因此如果我们想要在每个手机尺寸或者分辨上的手机都能显示正常那就必须要做屏幕适配。

基本概念:

pixel:

像素,即手机屏幕上显示的最小单元,可以看成一很小的灯泡,类似与LED 屏幕上的灯泡。一般我们所说的屏幕分辨率比如1080*1920即代表:横向的屏幕上有1080了像素,即小灯泡,纵向上有1920个像素。

dpi:

dense per inch像素密度(一般也会叫做ppi:px per inch),即是指屏幕上每英寸(1英寸 = 2.54 厘米)距离中有多少个像素点。谷歌规定的有ldpi,mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi,不过手机厂商一般没有按照规矩来过;因此我们要会计算像素密度,比如一个手机的分辨率为x*y,尺寸为z(手机屏幕斜对角的尺寸长度),那么手机的像素密度为:
(x²+y²)开根号/屏幕尺寸(其中√(x²+y²)是算出斜对角上有多少个像素)。
谷歌设置的一一对应的默认值:

|分辨率|240x320|320x480|480x800|720x1280|1080x1920|
|系统dpi|120|160|240|320|480|
|基准比例|0.75|1|1.5|2|3|

dip:
device independent pixels 不依赖像素尺寸,一般开发时需要使用这个单位,不过在使用中会写成dp。
规定在mdpi分辨率的手机上1dp = 1px,具体大小跟屏幕分辨率有关,与pixel的换算公式为:

1dp = 像素密度/160dpi

因此换算关系为:

  • 在ldpi分辨率的手机上:
    1dp = 120dpi/160dpi = 0.75px
  • 在mdpi分辨率的手机上:
    1dp = 160dpi/160dpi = 1px
  • 在hdpi分辨率的手机上:
    1dp = 240dpi/160dpi = 1.5px
  • 在xhdpi的手机上:
    1dp = 320dpi/160dpi = 2px
  • 在xxhdpi的手机上:
    1dp = 480dpi/160dpi = 3px

不过慢慢有了更高分辨率的手机比如3840*2160分辨率的,如果屏幕尺寸为5英寸则像素密度为(3840*3840+2160*2160)/5 = 881多dpi,则1dp = 5px多了可以屏幕精度非常好了,当然了也非常贵了。

具体实现:

一、图片适配:

对应的图片放到对应分辨率的文件夹中:

由于目前手机主流的分辨率为高分辨率(xhdpi)与超高分辨率(xxhpi)因此UI会给出两套图就够用了。
解释:低于xhdpi分辨率的手机会使用xhdpi的图片而且是把图片进行缩小后使用,而xxxhdpi的手机会使用xxhdpi的图片并且把图片进行放大后使用,放大倍数为(1.5倍,怎么样数字是不是很熟悉,是的,如果图片放置的分辨率文件夹不正确的化,系统会使用最接近的分辨率图片并且会把图片按照像素密度的倍数把图片放大或者缩小相应的倍数然后使用)。
因此比如说UI说给的xhdpi的图片大小为48*48的,那么给的xxhdpi的图片尺寸大小应该为72*72的(48*1.5),因此不要把图片放错了,否则图片的内存占用可能成倍增加;而如果UI只给了一套图片的话最好放到xxhdpi中,让系统缩小图片总比让系统放大图片好。

另外一个就是使用点九图片或者自定义的xml背景:

在布局中如果想使用同一张图片进行拉伸适配时,比如一个按钮添要添加背景图片或者默认的占位图片可以使用点九图片或者使用自己用xml实现的图片效果,一个是减少apk大小另外一个就是可以拉伸图片部分填充满控件。

二、布局适配:

1.布局适配:
一般的话我们会使用layout布局文件夹即可,可是横屏的布局跟纵向的布局总是不一样的,比如视屏播放布局,而且对于游戏开发来说屏幕适配就更重要了。
适配方式:

  • 横屏的添加布局文件夹:
    layout-land
  • 大屏的添加文件夹:
    layout-large
  • 对于最小宽度的屏幕适配比如定义了这些文件夹:
    layout-sw720dp
    layout-sw800dp
    layout-sw1080dp
    则宽度大于1080dp的手机会使用layout-sw1080dp这里边的布局,小于layout-sw1080dp而大于800dp的会使用layout-sw800dp这里边的xml布局。
  • 对于指定宽度的手机适配:
    layout-w720dp:代表宽度为720dp的手机会使用该文件夹下的xml布局。

对于sh与h跟sw与w的定义类似。
其他的还有

2.尺寸适配
因为屏幕密度(分辨率)不一样,所以不能用固定的px,而又因为屏幕宽度不一样,所以要小心的用dp。那怎么才能做好尺寸适配呢,实际上可以即根据不同屏幕密度,控件选择对应的像素值大小的。具体可以根据一个分辨率做基准生成不同分辨率下的基准,比如以320*480为基准:
现在我们以320x480的分辨率为基准:

  • 将屏幕的宽度分为320份,取值为x1~x320
  • 将屏幕的高度分为480份,取值为y1~y480

然后生成该分辨率对应像素数的列表,如下图:

lay_x.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="x10">10.0px</dimen> 
  ...
  <dimen name="x300">300.0px</dimen> 
  <dimen name="x302">302.0px</dimen> 
  <dimen name="x304">304.0px</dimen> 
  <dimen name="x306">306.0px</dimen> 
  <dimen name="x308">308.0px</dimen>
  <dimen name="x309">309.0px</dimen>
  <dimen name="x311">311.0px</dimen>
  <dimen name="x313">313.0px</dimen>
  <dimen name="x315">315.0px</dimen>
  <dimen name="x317">317.0px</dimen>
  <dimen name="x319">319.0px</dimen>
  <dimen name="x320">320px</dimen> 
</resources>

lay_y.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>

然后把其他分辨率不全即可,比如1080x1920分辨率的尺寸应该为:

lay_x.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 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>

生成相应尺寸的代码:

package image.similarity;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

/**
 * create by davaid on 2018/7/4.
 */
public class MakeXml {
    private final static String rootPath = "F:\\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";

    private static String[] dimens = new String[]{"480*800","540*960","600*1024","720*1184","720*1280",
            "800*1280","1080*1920","1440*2560"};//TODO 有新的分辨率时在此添加即可

    public static void main(String[] args) {
        for(String dimen:dimens){
            makeString(Integer.valueOf(dimen.split("\\*")[0]),Integer.valueOf(dimen.split("\\*")[1]));
        }
    }

    private static void makeString(int w, int h) {
        StringBuilder sb = new StringBuilder();
        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>");
        StringBuilder sb2 = new StringBuilder();
        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();
        }
    }

    private static float change(float a) {
        int temp = (int) (a * 100);
        return temp / 100f;
    }
}

把生成的布局放到res下:
android屏幕适配
然后在控件中使用,比如:

layout-width=“@dimen/x3“
layout-height=“@dimen/y5“

这样系统就会根据不同分辨率使用不同的尺寸了。

注意:在有新的分辨率时系统会找不到相应的values文件夹从而直接使用values下的尺寸因此values一般直接适配高分辨率即可,添加新的分辨率文件夹后在升级或者热更新时直接替换即可适配所有的分辨率了

三、语言适配:

添加对应的values文件夹:

  • 中文(中國):values-zh-rCN中文(台灣):values-zh-rTW
  • 中文(香港):values-zh-rHK
  • 英語(美國):values-en-rUS
  • 英語(英國):values-en-rGB

比如英文的添加values-en后在xml文件中定义相同name的string即可,比如在values定义了测试,那么在values-en中也应该定义test,然后设置了语音环境之后系统会区寻找相应的string