Android屏幕适配大总结

1.概述

        大家在Android开发时,肯定会觉得屏幕适配是个尤其痛苦的事,各种屏幕尺寸适配起来十分头疼

2. Android屏幕适配出现的原因

        1.屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。

        一般以纵向像素*横向像素,如1960*1080。 由于Android系统的开放性,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,修改成他们想要的样子。 屏幕尺寸这么多,为了让我们开发的程序能够比较美观的显示在不同尺寸、分辨率、像素密度(这些概念我会在下面详细讲解)的设备上,那就要在开发的过程中进行处理,至于如何去进行处理,这就是我们今天的主题。

        2.dp比较百分比(网页前端提供百分比,所以无需适配)

        只要记住一点dp是与像素无关的,在实际使用中1dp大约等于1/160inch

        那么dp究竟解决了适配上的什么问题?可以看出1dp = 1/160inch;那么它至少能解决一个问题,就是你在布局文件写某个View的宽和高为160dp*160dp,这个View在任何分辨率的屏幕中,显示的尺寸大小是大约是一致的(可能不精确),大概是 1 inch * 1 inch。

        1.呈现效果仍旧会有差异,仅仅是相近而已

        2. 当设备的物理尺寸存在差异的时候,dp就显得无能为力了。为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。

        一句话,总结下,dp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备的尺寸差异较大的时候,就无能为力了。

3.理解重要概念

        #1.什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?

        #2.什么是dp、dip、dpi、sp、px?他们之间的关系是什么?

        #3.什么是mdpi、hdpi、xdpi、xxdpi?如何计算和区分?

        1.屏幕尺寸

        屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米; 比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

        2.屏幕分辨率       

        屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素,如1960*1080

        3.屏幕像素密度

        屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

        4.dp、dip、dpi、sp、px

        dp: dip和dp是一个意思,都是Density Independent Pixels的缩写,即密度无关像素

dp(dip):px = dp * 密度比

开方(宽度平方 + 高度平方) / 手机的尺寸;dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi

        sp: 可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。

像素,px是比较熟悉,前面的分辨率就是用的像素为单位,大多数情况下,比如UI设计、Android原生API都会以px作为统一的计量单位,像素是获取屏幕宽高等。

        问题: dp和px如何换算呢?

        在Android中,规定以160dpi为基准,1dip=1px,如果密度是320dpi,则1dip=2px,以此类推。

        5.mdpi、hdpi、xdpi、xxdpi

        #1.作用: mdpi、hdpi、xdpi、xxdpi用来修饰Android中的drawable文件夹及values文件夹,用来区分不同像素密度下的图片和dimen值。

名称像                    素密度范围

ldpi                        0dpi~120dpi

mdpi                      120dpi~160dpi

hdpi                       120dpi~160dpi

xdpi                       160dpi~240dpi

xxdpi                      240dpi~320dpi

xxxdpi                    480dpi~640dpi

#2.在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面。下面以图标设计为例进行介绍

android values 屏幕适配 android屏幕适配原理_ui

        #3.在设计图标时,对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。

        例如,一个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。

        #4. 下图为图标的各个屏幕密度的对应尺寸:

屏幕密度                 图标尺寸

mdpi                      48X48px

hdpi                       72X72px

xdpi                        96X96px

xxdpi                      144X144px

xxxdpi                    192X192px

4.尺寸适配(用到最多)

        1.布局文件设置宽高

        * 宽高设置参数:有的时候用dp,有的时候用px,大多数用dp

dp(dip):px = dp * 密度比,与屏幕像素有对应关系,设置成dp后,在不同分辨率的手机上有可能尺寸会不一样

        px:像素,比如小分辨率手机上一像素和大分辨率手机上一像素,所显示的图像是不一样的

        理解dp和px之间对应的关系,不同分辨率的手机用不同的dp值来适配

        2.密度比

        * 密度比是固定的,可以查询文档Develop—>API Guides—>Best Practices—>Supporting Multiple

        * mdpi手机:160dpi 是基准线,1px = 1dp * 1,其他手机的密度比 = 自己的dpi/160

        * 代码获取密度比:getResources().getDisplayMetrics().density

        ldip:120px = 160dp * 0.75

        mdpi:160px = 160dp * 1

        hdpi:240px = 160dp * 1.5

        xhdpi:360px = 180dp * 2

        3.dimen适配

1.在默认的values中的dimens文件下声明(类似于Strings.xml)

<dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="harlWidth">160dp</dimen>
 
<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <!-- values-hdpi 480X800 --> 
    <dimen name="imagewidth">120dip</dimen>     
</resources> 
 
<resources> 
    <!-- values-hdpi-1280x800 --> 
    <dimen name="imagewidth">220dip</dimen>     
</resources> 
 
 
<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <!-- values-hdpi  480X320 --> 
    <dimen name="imagewidth">80dip</dimen>     
</resources>

2.在布局文件中引用

<TextView
        android:layout_width="@dimen/harlWidth"
        android:layout_height="wrap_content"
        android:background="#0ff"
        android:text="@string/hello_world" />

3.新建需要适配的values-XXX(比如values-1280x720,注意规范大值在前)

4.在新建values-1280x720中的dimens.xml文件中

<dimen name="harlWidth">180dp</dimen>

5.所有手机适配找对应的默认的dimens

思考:如何计算dpi?如何计算手机密度比?能够用dp适配所有手机吗?

dp不能适配所有手机;

举个例子:按钮占屏幕宽度一半,把宽度设置成160dp,120px和160px和240px可以占屏幕一半,但是360px则小于屏幕一半;

如果把宽度设置成180dp,那么360dp可以占屏幕一半,但其他几个又不行。

如果要适配所有手机的控件宽度为屏幕宽度的一半,该怎么做呢?用dimen

5.图片适配

1.图片的查找顺序

* 注意:一般手机

ldpi<drawable<mdpi<hdpi<xhdpi<xxhdpi     先找自己对应的文件夹,再找大分辨率,再找小分辨率

* 注意:mdpi手机

ldpi<mdpi<drawable<hdpi<xhdpi<xxhdpi     先找自己对应的文件夹,找drawable文件夹,再找大分辨率,再找小分辨率

* 适配主流手机,1920 * 1080 1080 * 720 800 * 480,高清图、背景图(全屏展示)准备多套

* 小图片 准备一套高分辨率的;比如按钮,图标等

为了是apk瘦身,有些图片不要准备多套,Android分辨率种类太多了;即使适配主流手机,展示比较清楚的背景图(比如:欢迎界面),可以准备多套

2.在小分辨率展示高清图,放到大分辨率会出现什么情况呢?

比如:你针对800*480分辨率手机做了背景图图片,正好完全展示;如果把它放到大分辨率1280*720上,会对图片进行拉伸,会使像素点变大,可能会看到锯齿或者模糊的东西

6.布局适配

1.有可能在不同的手机布局中,控件排列的位置不一样

1.位置不一样

不同的手机在运行的时候选择不同的布局(布局名称一样,类似于dimens),比如:

2.控件不一样

不能用布局适配了;为什么?

布局能够实现界面效果,但是完成布局后在代码中,由于控件都不一样,所以会找这两套布局的id,还要做判断,根据不同的布局做两套代码(如果页面复杂,给控件设置参数等十分繁琐)

3.适用场景

不同的手机的控件的位置不一样,发生了位置变化才会用到布局适配,实际开发中用的很少

7.代码适配

思考:什么时候需要在代码里面做适配呢?

1.做第三方sdk

做第三方sdk不会给别人提供src,会提供jar包,让别人去调用。jar里面没有放控件,没有layout,只能通过代码去new控件,在代码中设置属性

2.在代码中适配(比如引导页面红点适配 ) 方案1

* 引导红点

1.布局(引导红点和背景灰点)

<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="40dp" >
 
    <LinearLayout
        android:id="@+id/ll_guide_points"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" />
 
    <ImageView
        android:id="@+id/iv_guide_redpoint"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:background="@drawable/guide_point_red" />
</RelativeLayout>
2.代码(注意代码里写的宽高10,单位是像素,而布局中写的宽高单位是dp)
// 添加灰点
ImageView point = new ImageView(this);
point.setBackgroundResource(R.drawable.guide_point_normal);
// 设置宽高
LayoutParams params = new LayoutParams(10, 10);
point.setLayoutParams(params);
// 让中间的灰点显示在屏幕中间
if (i != 0) {
    params.leftMargin = 10;
}
ll_guide_points.addView(point);
3.写转化工具类
public class CommonUtil {
    // dp转换成px
    public static int dp2px(Context context, int dp) {
        // px = dp * 密度比 0.75 1 1.5 2
        float density = context.getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5f); // 做4舍5入
    }
}
4.修改适配后代码
int dp2px = CommonUtil.dp2px(this, 10);
LayoutParams params = new LayoutParams(dp2px, dp2px);
point.setLayoutParams(params);
// 让中间的灰点显示在屏幕中间
if (i != 0) {
    params.leftMargin = dp2px;
}
ll_guide_points.addView(point);
3.在代码中适配(比如布局已经写好了)     方案2
* 在不同手机分辨率上展示控件为屏幕1/4,1/2,3/4,1
1.布局
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#f00"
android:text="@string/hello_world" />
2.代码
TextView tv1 = (TextView) findViewById(R.id.tv1);
TextView tv2 = (TextView) findViewById(R.id.tv2);
TextView tv3 = (TextView) findViewById(R.id.tv3);
TextView tv4 = (TextView) findViewById(R.id.tv4);
 
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
 
tv1.setLayoutParams(new LayoutParams((int)(width*0.25f), (int)(height*0.1f)));
tv2.setLayoutParams(new LayoutParams((int)(width*0.5f), (int)(height*0.1f)));
tv3.setLayoutParams(new LayoutParams((int)(width*0.75f), (int)(height*0.1f)));
tv4.setLayoutParams(new LayoutParams((int)(width), (int)(height*0.1f)));

8.权重适配(常用)

1.不同的手机展示的图片比例是一定的,但是尺寸却不同

 

 

 

9.适配小技巧

  1. 多用match_parent
  2. 多用weight
  3. 自定义view解决

归根结底还是利用百分比,match_parent相当于100%参考父控件;weight即按比例分配;自定义view无非是因为里面多数尺寸是按照百分比计算的

 

1.百分比的引入

 

然后我们根据一个基准,为基准的意思就是:

比如480*320的分辨率为基准

  • 宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320
  • 高度为480,将任何分辨率的高度分为480份,取值为y1-y480
  • 例如对于800*480的宽度480: 可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;

2. 假设我现在需要在屏幕中心有个按钮,宽度和高度为我们屏幕宽度的1/2,我可以怎么编写布局文件呢?

<Button
        android:layout_gravity="center"
        android:gravity="center"
        android:text="@string/hello_world"
        android:layout_width="@dimen/x160"

        android:layout_height="@dimen/x160"/>

3.自动生成工具,编写自动生成文件的程序

 

   

import java.io.File;
        import java.io.FileNotFoundException;
        import java.io.FileOutputStream;
        import java.io.PrintWriter;
 
        /**
         * Created by zhy on 15/5/3.
         */
        public class GenerateValueFiles {
 
            private int baseW;
            private int baseH;
 
            private String dirStr = "./res";
 
            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";
 
            /**
             * {0}-HEIGHT
             */
            private final static String VALUE_TEMPLATE = "values-{0}x{1}";
 
            private static final String SUPPORT_DIMESION = "320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";
 
            private String supportStr = SUPPORT_DIMESION;
 
            public GenerateValueFiles(int baseX, int baseY, String supportStr) {
                this.baseW = baseX;
                this.baseH = baseY;
 
                if (!this.supportStr.contains(baseX + "," + baseY)) {
                    this.supportStr += baseX + "," + baseY + ";";
                }
 
                this.supportStr += validateInput(supportStr);
 
                System.out.println(supportStr);
 
                File dir = new File(dirStr);
                if (!dir.exists()) {
                    dir.mkdir();
 
                }
                System.out.println(dir.getAbsoluteFile());
 
            }
 
            /**
             * @param supportStr
             *            w,h_...w,h;
             * @return
             */
            private String validateInput(String supportStr) {
                StringBuffer sb = new StringBuffer();
                String[] vals = supportStr.split("_");
                int w = -1;
                int h = -1;
                String[] wh;
                for (String val : vals) {
                    try {
                        if (val == null || val.trim().length() == 0)
                            continue;
 
                        wh = val.split(",");
                        w = Integer.parseInt(wh[0]);
                        h = Integer.parseInt(wh[1]);
                    } catch (Exception e) {
                        System.out.println("skip invalidate params : w,h = " + val);
                        continue;
                    }
                    sb.append(w + "," + h + ";");
                }
 
                return sb.toString();
            }
 
            public void generate() {
                String[] vals = supportStr.split(";");
                for (String val : vals) {
                    String[] wh = val.split(",");
                    generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
                }
 
            }
 
            private void generateXmlFile(int w, int h) {
 
                StringBuffer sbForWidth = new StringBuffer();
                sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
                sbForWidth.append("<resources>");
                float cellw = w * 1.0f / baseW;
 
                System.out.println("width : " + w + "," + baseW + "," + cellw);
                for (int i = 1; i < baseW; i++) {
                    sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
                            change(cellw * i) + ""));
                }
                sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
                        w + ""));
                sbForWidth.append("</resources>");
 
                StringBuffer sbForHeight = new StringBuffer();
                sbForHeight.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
                sbForHeight.append("<resources>");
                float cellh = h *1.0f/ baseH;
                System.out.println("height : "+ h + "," + baseH + "," + cellh);
                for (int i = 1; i < baseH; i++) {
                    sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
                            change(cellh * i) + ""));
                }
                sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
                        h + ""));
                sbForHeight.append("</resources>");
 
                File fileDir = new File(dirStr + File.separator
                        + VALUE_TEMPLATE.replace("{0}", h + "")//
                                .replace("{1}", w + ""));
                fileDir.mkdir();
 
                File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
                File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
                try {
                    PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
                    pw.print(sbForWidth.toString());
                    pw.close();
                    pw = new PrintWriter(new FileOutputStream(layyFile));
                    pw.print(sbForHeight.toString());
                    pw.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
 
            public static float change(float a) {
                int temp = (int) (a * 100);
                return temp / 100f;
            }
 
            public static void main(String[] args) {
                int baseW = 320;
                int baseH = 400;
                String addition = "";
                try {
                    if (args.length >= 3) {
                        baseW = Integer.parseInt(args[0]);
                        baseH = Integer.parseInt(args[1]);
                        addition = args[2];
                    } else if (args.length >= 2) {
                        baseW = Integer.parseInt(args[0]);
                        baseH = Integer.parseInt(args[1]);
                    } else if (args.length >= 1) {
                        addition = args[0];
                    }
                } catch (NumberFormatException e) {
 
                    System.err
                            .println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
                    e.printStackTrace();
                    System.exit(-1);
                }
 
                new GenerateValueFiles(baseW, baseH, addition).generate();
            }
 
        }