对于多分辨率的适配,Android官方提供了一个说明文档,原文链接如下:
http://developer.android.com/training/multiscreen/index.html
对其做了一个翻译,方便日后参考学习。
支持不同大小的屏幕
一.使用“wrap_content”和“match_parent”
为了确保布局文件能够灵活的适应于不同大小的屏幕,对于一些视图的宽度和高度属性的值应该使用“wrap_content”和“match_content”。如果使用“wrap_content”,视图的高度和宽度将被设置为满足视图内容的最小值,而“match_parent”(在API 8之前也称为“fill_parent”)使得控件扩展来匹配其父控件的大小。
通过使用“wrap_content”和"match_parent"属性值替代具体的数字值,视图不但能够使用需要的空间,而且能够扩展到能够使用的空间,例如:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation= "vertical" android:layout_width= "match_parent" android:layout_height= "match_parent" > <LinearLayout android:layout_width= "match_parent" android:id= "@+id/linearLayout1" android:gravity= "center" android:layout_height= "50dp" > <ImageView android:id= "@+id/imageView1" android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:src= "@drawable/logo" android:paddingRight= "30dp" android:layout_gravity= "left" android:layout_weight= "0" /> <View android:layout_height= "wrap_content" android:id= "@+id/view1" android:layout_width= "" android:layout_weight= "1" /> <Button android:id= "@+id/categorybutton" android:background= "@drawable/button_bg" android:layout_height= "match_parent" android:layout_weight= "0" android:layout_width= "120dp" style= "@style/CategoryButtonStyle" /> </LinearLayout> <fragment android:id= "@+id/headlines" android:layout_height= "fill_parent" android:name= "com.example.android.newsreader.HeadlinesFragment" android:layout_width= "match_parent" /> </LinearLayout> |
上面的例子中使用了”wrap_content“和”fill_parent“属性而不是具体的数字值。这使得布局能够正确的适应不同大小和方向的屏幕。
二.使用RelativeLayout
使用LinearLayout能够在一定程度上面构成复杂的布局。然而,LinearLayout不能使你精确构建子视图之间的空间关系。在LinearLayout中的视图通常都是简单排成队列。如果你想要视图能够适应不同的情况排列,而不是简单的线性布局,一个更好的选择就是使用RelativeLayout,这能够使你明确指定布局中视图的空间关系。例如,你可以排列一个子视图在另一个子视图的左边或者右边。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<? xml version = "1.0" encoding = "utf-8" ?> < RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent" > < TextView android:id = "@+id/label" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "Type here:" /> < EditText android:id = "@+id/entry" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_below = "@id/label" /> < Button android:id = "@+id/ok" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_below = "@id/entry" android:layout_alignParentRight = "true" android:layout_marginLeft = "10dp" android:text = "OK" /> < Button android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_toLeftOf = "@id/ok" android:layout_alignTop = "@id/ok" android:text = "Cancel" /> </ RelativeLayout > |
三.使用大小相关的布局修饰
上面一节中所述的内容能够通过拉伸视图空间来适应不同大小的屏幕,但不能够提供最好的用户体验。因此,应用不仅要实施灵活的布局,而且针对不同的屏幕配置要提供不同替代的布局文件。达到这个目的,你需要使用配置修饰,使得在运行时能够基于当前设备的配置自动选择最合适的的资源文件。(例如针对不同的屏幕大小设计不同的布局文件)
例如,很多应用程序对于大尺寸的屏幕施行”两个部分“的样式。平板电脑对于在屏幕上同时呈现两个部分是足够大的,但移动设备需要分别显示他们。为了实现这种布局,你需要有以下文件:
01
|
res/layout/main.xml, single-pane (default) layout: |
01
02
03
04
05
06
07
08
09
10
|
< 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 > |
01
|
res/layout-large/main.xml, two-pane layout: |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<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> |
注意在第二级目录中的”large“修饰符。在被定义为large的设备上面就会选择该布局文件。没有large修饰符的布局文件将会被更小的设备选择。
四.使用Smallest-width修饰符
很多应用程序都想要为属于”large“这一范围的不同设备展示不同的布局(例如5寸和7寸的设备)。因此Android从3.2开始引入了”Smallest-width“修饰符。
”Smallest-width“修饰符允许你把最小宽度为width的屏幕作为目标(单位dp)。例如,典型的7寸平板有最小宽度为600dp,因此如果你希望你的界面在这些设备上面展示两个部分,你可以使用这种修饰符。
01
|
res/layout/main.xml, single-pane (default) layout: |
01
02
03
04
05
06
07
08
09
10
|
< 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 > |
01
|
res/layout-sw600dp/main.xml, two-pane layout: |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<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> |
上面的布局意味着宽度高于或等于600dp的设备将会选择layout-sw600dp/main.xml中的布局,而小尺寸的设备将会选择layout/main.xml中的布局。
然而,这种方式在3.2之前的设备上面是无法工作的,因为它们不识别sw600dp的标示符,所以你同时也要使用large标示符。因此,你需要一个res/layout-large/main.xml的文件,而他的内容与res/layout-sw600dp/main.xml的文件相同。
五.使用Layout修饰符需要注意的问题
”smallest-width“修饰符只有在3.2以上的设备才能被使用。因此,你仍然需要使用抽象的尺寸分类(small、normal、large、xlarge)来与之前的设备兼容。例如,如果你想要设计UI,在移动设备上面显示一个部分,而在7寸设备上面同时显示多个部分,你需要提供下面这些资源:
01
02
03
|
res/layout/main.xml: single-pane layout res/layout-large: multi-pane layout res/layout-sw600dp: multi-pane layout |
最后的两个文件是相同的,因为其中一个将会被3.2以上的设备匹配,而另外一个将会为之前版本的平板带来好处。
为了避免对相同文件的复制,可以使用别名文件,例如,你可以定义如下的布局:
01
02
|
res/layout/main.xml, single-pane layout res/layout/main_twopanes.xml, two-pane layout |
同时添加如下两个文件:
01
|
res/values-large/layout.xml: |
01
02
03
|
< resources > < item name = "main" type = "layout" >@layout/main_twopanes</ item > </ resources > |
01
|
res/values-sw600dp/layout.xml: |
01
02
03
|
< resources > < item name = "main" type = "layout" >@layout/main_twopanes</ item > </ resources > |
后面的两个文件具有相同的内容,但他们并没有真实定义布局,他们仅仅建立了一个main_twopanes的别名。
六.使用方向修饰符
一些布局在横向和纵向都能够很好的工作,但根据不同的情况进行调整总是有益的。如下面的例子:
01
02
03
04
05
06
07
|
small screen, portrait: single pane, with logo small screen, landscape: single pane, with logo 7" tablet, portrait: single pane, with action bar 7" tablet, landscape: dual pane, wide, with action bar 10" tablet, portrait: dual pane, narrow, with action bar 10" tablet, landscape: dual pane, wide, with action bar TV, landscape: dual pane, wide, with action bar |
这些中的每一个都应该在res/layout/目录中定义。然后为不同的屏幕配置适配不同的布局文件。
01
|
res/layout/onepane.xml: |
01
02
03
04
05
06
07
08
09
10
|
< 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 > |
01
|
res/layout/onepane_with_bar.xml: |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:orientation = "vertical" android:layout_width = "match_parent" android:layout_height = "match_parent" > < LinearLayout android:layout_width = "match_parent" android:id = "@+id/linearLayout1" android:gravity = "center" android:layout_height = "50dp" > < ImageView android:id = "@+id/imageView1" android:layout_height = "wrap_content" android:layout_width = "wrap_content" android:src = "@drawable/logo" android:paddingRight = "30dp" android:layout_gravity = "left" android:layout_weight = "0" /> < View android:layout_height = "wrap_content" android:id = "@+id/view1" android:layout_width = "wrap_content" android:layout_weight = "1" /> < Button android:id = "@+id/categorybutton" android:background = "@drawable/button_bg" android:layout_height = "match_parent" android:layout_weight = "0" android:layout_width = "120dp" style = "@style/CategoryButtonStyle" /> </ LinearLayout > < fragment android:id = "@+id/headlines" android:layout_height = "fill_parent" android:name = "com.example.android.newsreader.HeadlinesFragment" android:layout_width = "match_parent" /> </ LinearLayout > |
01
|
res/layout/twopanes.xml: |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< 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 > |
01
|
res/layout/twopanes_narrow.xml: |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< 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 = "200dp" 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 > |
既然所有的布局都已经被定义,目前的问题就是为不同的配置选择正确的布局文件。
01
|
res/values/layouts.xml: |
01
02
03
04
|
< resources > < item name = "main_layout" type = "layout" >@layout/onepane_with_bar</ item > < bool name = "has_two_panes" >false</ bool > </ resources > |
01
|
res/values-sw600dp-land/layouts.xml: |
01
02
03
04
|
< resources > < item name = "main_layout" type = "layout" >@layout/twopanes</ item > < bool name = "has_two_panes" >true</ bool > </ resources > |
01
|
res/values-sw600dp-port/layouts.xml: |
01
02
03
04
|
< resources > < item name = "main_layout" type = "layout" >@layout/onepane</ item > < bool name = "has_two_panes" >false</ bool > </ resources > |
01
|
res/values-large-land/layouts.xml: |
01
02
03
04
|
< resources > < item name = "main_layout" type = "layout" >@layout/twopanes</ item > < bool name = "has_two_panes" >true</ bool > </ resources > |
01
|
res/values-large-port/layouts.xml: |
01
02
03
04
|
< resources > < item name = "main_layout" type = "layout" >@layout/twopanes_narrow</ item > < bool name = "has_two_panes" >true</ bool > </ resources > |
一.使用密度无关的像素值
一个通常的误区是在设计布局时使用物理像素单位。不同的设备具有不同的像素密度,因此相同的像素值可能对应不同的物理大小在不同的设备上。因此,当定义单位的时候,常常使用dp或sp。dp是密度无关的像素单位,1dp对应物理屏幕上每英寸160个点数。sp是同样的单位,但他被用来计算文字的大小。
例如,当你需要指定两个视图之间的空间,使用dp而不是px:
01
02
03
04
|
< Button android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "@string/clickme" android:layout_marginTop = "20dp" /> |
当需要指定文字的大小,通常使用sp:
01
02
03
|
< TextView android:layout_width = "match_parent" android:layout_height = "wrap_content" android:textSize = "20sp" /> |
二.提供可替换的图像
因为Android设备的屏幕像素密度不同,需要为不同像素密度的屏幕提供相应的图像资源:low,medium,high,extra-high。
01
02
03
04
|
xhdpi: 2.0 hdpi: 1.5 mdpi: 1.0 (baseline) ldpi: 0.75 |
这意味着如果你为xhdpi的设备生成一个200*200的图像,那你需要为hdpi的设备生成一个150*150的图像,为mdpi设备生成一个100*100的图像,为ldpi设备生成75*75的图像。而在你的layout文件中,只需要定义该图像的width=100dp,height=100dp。
讨论,2.0:1.5:1.0:0.75的比例转换为整数就是3:4:6:8,个人觉得当设计xhdpi的图片时物理像素xhdpiValue的长宽最好计算为8的倍数,这样在设计其他像素密度的图片时,就可以根据xhdpiValue*3(4或6)/8来计算真实物理图片的像素。
然后,把这些图像文件置于相应的文件目录下面,如下:
01
02
03
04
05
06
07
08
09
10
|
MyProject/ res/ drawable-xhdpi/ awesomeimage.png drawable-hdpi/ awesomeimage.png drawable-mdpi/ awesomeimage.png drawable-ldpi/ awesomeimage.png |
然后,当你引用到@drawable/awesomeimage,系统会基于设备的dpi选择合适的图像文件