今天想跟大家分享的是我在做一个项目需要在Pad端和手机端都能运行时遇到的屏幕适配上的问题, 首先说明一点的是我整个工程90%用的是百分比布局, 但是这篇转来的文章并没有提到, 这篇文章由浅入深的讲解了该如何适配各种各样的屏幕,是我在浩如烟海的网络上捞到的比较实用的并亲测可用的一篇, 这里分享给大家.
2012年到2014年支持Android设备的种类从3997增长到18796。同时各大厂商定制的屏幕尺寸也非常多。这将非常不利于我们进行屏幕适配。这要求我们必须掌握屏幕适配技能,以便使我们的app可以适用于不同屏幕尺寸的设备上。
![](https://i-blog.csdnimg.cn/blog_migrate/b1055ece74700a78f66bfb925a9e10dc.webp?x-image-process=image/format,png)
从上图可以看出,主流的分辨率是前六种:1280×720、1920×1080、800×480、854×480、960×540、1184×720,不过我们有解决方案。看完这篇文章,想必你就可以解决常见的屏幕适配问题。
接下来正式进入正题。
一. 核心概念与单位详解
介绍几个在Android屏幕适配上非常重要的名词:
1. 什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
屏幕尺寸是指屏幕对角线的长度。单位是英寸,1英寸=2.54厘米
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1像素点,一般是纵向像素横向像素,如1280×720
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写,像素密度和屏幕尺寸和屏幕分辨率有关
![](https://i-blog.csdnimg.cn/blog_migrate/1e8159bb989aa9be4a63dbdae46ca3b6.webp?x-image-process=image/format,png)
例如:计算Nexus5的屏幕像素密度:
屏幕尺寸:4.95inch、分辨率:1920×1080,屏幕像素密度:445
![](https://i-blog.csdnimg.cn/blog_migrate/701446267c9a72d29260b1eaefbc1ebb.webp?x-image-process=image/format,png)
和官方给出的一样,说明我们计算正确。
2. 什么是dp、dip、dpi、sp、px?之间的关系是什么?
dip:Density Independent Pixels(密度无关像素)的缩写。以160dpi为基准,1dp=1px
dp:同dip
dpi:屏幕像素密度的单位,“dot per inch”的缩写
px:像素,物理上的绝对单位
sp:Scale-Independent Pixels的缩写,可以根据文字大小首选项自动进行缩放。Google推荐我们使用12sp以上的大小,通常可以使用12sp,14sp,18sp,22sp,最好不要使用奇数和小数。
![](https://i-blog.csdnimg.cn/blog_migrate/244593cbae8a356440bc5ff67bb01b9e.webp?x-image-process=image/format,png)
说明:如果A设备的参数为480×320,160dpi,B设置的参数为800×480,240dpi。我们要画出一条和屏幕宽度一样长的直线,如果使用px
作为单位,必须在A设备上设置为320px,在B设备上设置480px。但是如果我们使用dp
作为单位,由于以160dpi为基准,1dp=1px
,所以A设备上设置为320dp就等于屏幕宽度(320px),在B设备上设置为320dp就等于320×(240/160)=480px,即B设备的屏幕宽度。这样,使用dp
作为单位就可以实现简单的屏幕适配。这知识一种巧合,也有B设备的像素密度不是这样刚刚好的,就需要我们运用别的屏幕适配技术。
3. 什么是mdpi、hdpi、xdpi、xxdpi、xxxdpi?如何计算和区分?
用于区分不同的像素密度。
名称 | 像素密度范围 | 图片大小 |
---|---|---|
mdpi | 120dp~160dp | 48×48px |
hdpi | 160dp~240dp | 72×72px |
xhdpi | 240dp~320dp | 96×96px |
xxhdpi | 320dp~480dp | 144×144px |
xxxhdpi | 480dp~640dp | 192×192px |
![](https://i-blog.csdnimg.cn/blog_migrate/29717e58d0dba415cc28ca5c76a9766a.webp?x-image-process=image/format,png)
在Google官方开发文档中,说明了 mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 的尺寸比例进行缩放。例如,一个图标的大小为48×48dp,表示在mdpi上,实际大小为48×48px,在hdpi像素密度上,实际尺寸为mdpi上的1.5倍,即72×72px,以此类推。
二. 解决方案-支持各种屏幕尺寸
我们可以通过以下几种方式来支持各种屏幕尺寸:
1. 使用wrap_content、math_parent、weight
wrap_content:根据控件的内容设置控件的尺寸
math_parent:根据父控件的尺寸大小设置控件的尺寸
weight:权重,在线性布局中可以使用weight属性设置控件所占的比例
例如,我们要实现下图所显示的效果:当屏幕尺寸改变时,new reader控件两边的控件大小不变,new reader控件会占完剩余的空间。
![](https://i-blog.csdnimg.cn/blog_migrate/08835c4dba9f7c66c581ef21fecd5a2d.webp?x-image-process=image/format,png)
实现:通过给new reader控件设置属性:android:layout_width="wrap_content"
,android:layout_weight="1"
,两边两个控件为固定大小,android:layout_weight="0"
具体布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_weight="0"
android:background="#028330"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="new
reader"
android:textSize="22sp"
android:layout_weight="1"/>
<TextView
android:layout_width="160dp"
android:layout_height="80dp"
android:text="Politics"
android:textSize="18sp"
android:layout_weight="0"
android:background="#028330"/>
</LinearLayout>
小插曲:关于android:layout_weight
属性
公式:所占宽度=原来宽度+剩余空间所占百分比的宽度
一般情况,我们都是设置要进行比例分配的方向的宽度为0dp,然后再用权重进行分配。如下:
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button1" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Button2" />
效果为:
![](https://i-blog.csdnimg.cn/blog_migrate/85fe43bcb0b37265785e5d170ac2ba4d.webp?x-image-process=image/format,png)
设屏幕宽度为L,
根据公式,
button1宽度=0+L×1/(1+2)=1/3L
button2宽度=0+L×2/(1+2)=2/3L
但如果设置为match_parent
:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button1" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Button2" />
效果为:
![](https://i-blog.csdnimg.cn/blog_migrate/f2f512b5998e09a4e6395f052c6889a4.webp?x-image-process=image/format,png)
button1宽度=L+(L-2L)×1/3=2/3L
button2宽度=L+(L-2L)×2/3=1/3L
当然,还有其他的方式,都可以运用此公式进行计算。
在实际开发中,我们一般使用0dp的方式,而不使用其他方式。
2. 使用相对布局,禁用绝对布局
简单的布局一般都使用线性布局
,而略微复杂点的布局,我们使用相对布局
,大多数时候,我们都是使用这两种布局的嵌套。
我们使用相对布局
的原因是,相对布局
能在各种尺寸的屏幕上保持控件间的相对位置。
3. 使用限定符
-
使用尺寸限定符
当我们要在大屏幕上显示不同的布局,就要使用large
限定符。例如,在宽的屏幕左边显示列表右边显示列表项的详细信息,在一般宽度的屏幕只显示列表,不显示列表项的详细信息,我们就可以使用large
限定符。res/layout/main.xml
单面板:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 列表 --> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout-large/main.xml
双面板:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <!-- 列表 --> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <!-- 列表项的详细信息 --> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /> </LinearLayout>
如果这个程序运行在屏幕尺寸大于7inch的设备上,系统就会加载
res/layout-large/main.xml
而不是res/layout/main.xml
,在小于7inch的设备上就会加载res/layout/main.xml
。需要注意的是,这种通过
large
限定符分辨屏幕尺寸的方法,适用于android3.2之前。在android3.2之后,为了更精确地分辨屏幕尺寸大小,Google推出了最小宽度限定符。 -
使用最小宽度限定符
最小宽度限定符的使用和large
基本一致,只是使用了具体的宽度限定。
res/layout/main.xml
,单面板(默认)布局:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout-sw600dp/main.xml
,双面板布局: Small Width 最小宽度<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /> </LinearLayout>
这种方式是不区分屏幕方向的。这种最小宽度限定符适用于android3.2之后,所以如果要适配android全部的版本,就要使用
large
限定符和sw600dp
文件同时存在于项目res
目录下。这就要求我们维护两个相同功能的文件。为了避免繁琐操作,我们就要使用布局别名。
-
使用布局别名
res/layout/main.xml
: 单面板布局
res/layout-large/main.xml
: 多面板布局
res/layout-sw600dp/main.xml
: 多面板布局由于后两个文具文件一样,我们可以用以下两个文件代替上面三个布局文件:
res/layout/main.xml
单面板布局
res/layout/main_twopanes.xml
双面板布局然后在
res
下建立
res/values/layout.xml
、
res/values-large/layout.xml
、
res/values-sw600dp/layout.xml
三个文件。默认布局
res/values/layout.xml
:<resources> <item name="main" type="layout">@layout/main</item> </resources>
Android3.2之前的平板布局
res/values-large/layout.xml
:<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
Android3.2之后的平板布局
res/values-sw600dp/layout.xml
:<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
这样就有了
main
为别名的布局。
在activity中setContentView(R.layout.main);
这样,程序在运行时,就会检测手机的屏幕大小,如果是平板设备就会加载
res/layout/main_twopanes.xml
,如果是手机设备,就会加载res/layout/main.xml
。我们就解决了只使用一个布局文件来适配android3.2前后的所有平板设备。 -
使用屏幕方向限定符
如果我们要求给横屏、竖屏显示的布局不一样。就可以使用
屏幕方向限定符
来实现。
例如,要在平板上实现横竖屏显示不用的布局,可以用以下方式实现。
res/values-sw600dp-land/layouts.xml
:横屏<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
res/values-sw600dp-port/layouts.xml
:竖屏<resources> <item name="main" type="layout">@layout/main</item> </resources>
4. 使用自动拉伸位图
自动拉伸位图,即android下特有的.9.png
图片格式。
当我们需要使图片在拉伸后还能保持一定的显示效果,比如,不能使图片中的重要像素拉伸,不能使内容区域受到拉伸的影响,我们就可以使用.9.png
图来实现。
要使用.9.png
,必须先得创建.9.png
图片,androidSDK给我们提供了的工具就包含.9.png
文件的创建和修改工具。双击SDK安装目录 oolsdraw9patch.bat
,就会打开下图所示的窗口。
![](https://i-blog.csdnimg.cn/blog_migrate/050f135e22f2bc6c1ce5762006f3db09.webp?x-image-process=image/format,png)
-
直接把图片拖进去,选择Filesave.9.png,即可保存为
.9.png
图片。
不过,这和原图没有什么区别,我们要制作成自动拉伸的图片,还需要进行简单的处理。 -
在左边和上边点下一个像素或多个像素点,是选择了拉伸的像素,即把选择的像素点进行拉伸。选择的像素点越多,拉伸的程度也越大。
-
右边和下边是选择内容区域,在右边和下边画上直线,交叉的区域就是内容区域。
-
什么事内容区域呢?
比如,我们给Button设置了一个.9.png
作为背景,还要设置其android:text
属性,设置的text所占的位置就是内容区域。 -
我们必须保证内容区域包含text文本,才会正常显示出text文本的内容。
下面是一个例子:
![](https://i-blog.csdnimg.cn/blog_migrate/11503e7f9e54f700e8d6e054b92bd1fe.webp?x-image-process=image/format,png)
上图是我们制作的.9.png
,设置好了左上拉伸像素点和内容区域。
Button属性设置:
<Button
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@drawable/button"
android:text="Button" />
![](https://i-blog.csdnimg.cn/blog_migrate/d02b7e4046d1f8c6290a93482d1beb8e.webp?x-image-process=image/format,png)
如果我们选择的内容区域偏差太大,可能就不会显示出text值BUTTON
。
未完待续
结束语:个人心得就是, 屏幕适配是个每个开发者必须面对的问题,值得花出点时间研究通透,如果你在小公司,一般都会很幸苦而且没空,那就选自己最需要的地方研究明白,应用到项目上,然后Mark一下,以后慢慢看
![大笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
原文链接:http://www.jianshu.com/p/6319e43572fa