系列文章:
前言
从 Android 1.6(API 4)开始,Android支持多种屏幕尺寸和密度,因此Android屏幕适配需要做到两个方面,尺寸适配和密度适配,确保应用正常渲染,在每个屏幕上提供最佳的用户体验。
在前文Android屏幕适配(一),相关概念中,主要给出了一些概念的定义,本文具体说明屏幕适配的解决办法。
屏幕尺寸适配方案
布局适配
1.使布局自适应屏幕尺寸
使用相对布局,不要使用绝对布局(AbsoluteLayout)
与其他布局不同,AbsoluteLayout
会强制使用固定位置放置其子视图,很容易导致在不同显示屏上显示效果不好的用户界面。因此,AbsoluteLayout
在 Android 1.5(API 级别 3)上便已弃用。
使用RelativeLayout
则会使用相对位置来布局子控件,即使屏幕大小改变,视图之间的位置也不会改变。
LinearLayout
的嵌套实例和"wrap_content"
尺寸与 "match_parent"
尺寸的组合可以构建相当复杂的布局。不过LinearLayout
不允许精确控制子视图的空间关系;LinearLayout
中的视图只是排成一行。
例如:
<?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>
该布局在QVGA(240x320,ldpi)屏幕上的外观显示:
2.根据屏幕配置加载相应布局
使用尺寸限定符
我们的应用不仅应该实现灵活布局,还应针对不同的屏幕配置提供多种备选布局,可以通过配置限定符实现,它允许组件根据当前设备配置(如针对不同屏幕尺寸的不同布局设计)自动选择合适的资源。
例如:许多应用都针对大屏幕实现了“双窗”模式(应用可以在一个窗中显示项目列表,在另一个窗中显示项目内容)。 平板电脑和 TV 足够大,可在一个屏幕上同时容纳两个窗,但手机屏幕只能独立显示它们。因此,如需实现这些布局,我们可以编写以下布局文件:
-
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>
- 说明:第二个布局的目录名为
layout-large
,包含了尺寸限定符large
但这种方式只适合Android3.2之前
使用最小宽度限定符(Smallest-width)
在Android3.2以下版本设备中,5寸和7寸设备都被视为“大屏”设备,但是许多应用可能需要精确的根据屏幕配置加载布局文件,所以Android3.2中引入了最小宽度
限定符。
最小宽度限定符通过指定某个特定最小宽度(单位:dp)来加载不同的UI资源。
例如,典型的7英寸平板电脑最小宽度为600dp,因此,如果希望UI在这些屏幕上显示双窗(但在较小屏幕上显示单个列表),我们使用sw600dp
为最小宽度是600dp的屏幕指定双窗布局:
-
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
,双窗布局:
<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
(单窗)布局。
使用布局别名
最小宽度限定符只在Android3.2及以上版本有效,若要兼容早期版本,例如在手机上显示单窗,在大屏设备上显示双窗,则需要编写以下布局文件:
-
res/layout/main.xml
: 单窗布局 -
res/layout-large
: 多窗布局 -
res/layout-sw600dp
: 多窗布局
后两个文件内容完全一致,只是文件名不一样,分别支持不同版本的Android设备。为了避免这种重复内容的文件(避免后期维护中的问题),我们可以使用文件别名,定义以下布局:
-
res/layout/main.xml
,单窗布局 -
res/layout/main_twopanes.xml
,双窗布局
并添加两个文件:
-
res/values-large/layout.xml
:
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
-
res/values-sw600dp/layout.xml
:
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
后两个文件内容完全相同,实际上并未定义布局,而只是将 main
设置为 main_twopanes
的别名。由于这些文件具有 large
和 sw600dp
选择器,因此它们适用于任何 Android 版本的平板电脑和电视(低于 3.2 版本的平板电脑和电视匹配 large
,高于 3.2 版本者将匹配 sw600dp
)。
使用屏幕方向限定符
假设某一应用布局在各种屏幕尺寸和屏幕方向下的行为如下:
- 小屏幕,纵向:单窗,带徽标
- 小屏幕,横向:单窗,带徽标
- 7 英寸平板电脑,纵向:单窗,带操作栏
- 7 英寸平板电脑,横向:双窗,宽,带操作栏
- 10 英寸平板电脑,纵向:双窗,窄,带操作栏
- 10 英寸平板电脑,横向:双窗,宽,带操作栏
- TV,横向:双窗,宽,带操作栏
因此,将每一种布局定义在res/layout/
目录下的某个XML文件,使用应用别名将对应布局分配给各种屏幕配置。共定义四种布局:(具体代码见Android开发者官网)
res/layout/onepane.xml
res/layout/onepane_with_bar.xml
res/layout/twopanes.xml
res/layout/twopanes_narrow.xml
再使用布局别名:(具体代码见Android开发者官网)
res/values/layouts.xml
res/values-sw600dp-land/layouts.xml
res/values-sw600dp-port/layouts.xml
res/values-large-land/layouts.xml
res/values-large-port/layouts.xml
匹配的过程和最小限定符类似,不再赘述。
视图组件适配
对布局尺寸使用 wrap_content、match_parent
为了使布局能灵活适应不同屏幕尺寸,某些视图组件的宽度和高度应该使用"wrap_content"
和 "match_parent"
。
为 XML 布局文件中的视图定义android:layout_width
和 android:layout_height
时,使用 "wrap_content"
、 "match_parent"
可确保在当前设备屏幕上为视图提供适当的尺寸。
图片资源适配
使用九图(Nine-Patch)
支持不同屏幕尺寸通常意味着图片资源也必须能够适应不同的尺寸。例如,一个Button
的背景图片必须能够适应其大小变化。
如果我们对该Button
使用普通的位图,会发现效果很差,因为Button
会均匀地拉伸或缩小图像。
解决方案是使用九宫格位图,这种特殊格式的 PNG (后缀名是:.9.png)文件会指示哪些区域可以拉伸,哪些区域不可以拉伸。
9图的制作教程这里暂时不说,网上教程也比较多。(参考Android开发者官网)
屏幕密度适配方案
视图组件适配
使用密度无关像素
设计布局需要避免用绝对像素来定义距离或者尺寸,因为不同屏幕具有不同像素密度,同样像素数量在不同设备上可能对应不同物理尺寸。因此,设计布局文件时务必使用dp
或sp
单位(具体定义参考Android屏幕适配(一),相关概念 )。
layout_width="100dp"
的视图在中密度屏幕上测出宽度为100像素,在高密度屏幕上系统会将其扩展至150像素宽, 因此视图在屏幕上占用的物理空间大约相同。例如:指定两个视图间距:
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clickme"
android:layout_marginTop="20dp" />
类似地,选择sp
来定义文本大小。sp
缩放系数取决于用户设置,系统会像处理dp
一样缩放大小,但切勿为布局尺寸使用该单位。例如:
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
在Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px。
图片资源匹配
Android的设备具有多种屏幕密度,我们需要提供能够根据各种通用密度级别(ldpi,mdpi,hdpi,xhdpi)进行定制的图片资源。
如需生成这些图像,我们以原始资源为基础,按以下尺寸缩放比例生成每种屏幕密度对应的图像:
- xhdpi:2.0
- hdpi:1.5
- mdpi:1.0(基准)
- ldpi:0.75
如果为 xhdpi 设备生成了一幅 200x200 的图像,则应分别按 150x150、100x100 和 75x75 图像密度为 hdpi 设备、mdpi 设备和 ldpi 设备生成同一资源。
然后,将生成的图片文件置于 res/
下的相应子目录中,系统将自动根据设备的屏幕密度选取正确的文件:
MyProject/
res/
drawable-xhdpi/
awesomeimage.png
drawable-hdpi/
awesomeimage.png
drawable-mdpi/
awesomeimage.png
drawable-ldpi/
awesomeimage.png
百分比布局
前端设计中,网页提供了百分比计算,从而可以根据百分比来设计布局。控件的宽度可以去参考父控件的宽度去设置百分比,最外层控件的宽度参考屏幕尺寸设置百分比。
谷歌曾开源了一个支持百分比布局的项目android-percent-support-lib-sample,但现在该项目已经被废弃,取而代之推荐我们使用ConstraintLayout 布局方式,具体可以参考郭霖大神的Android新特性介绍,ConstraintLayout完全解析这篇文章。
总结
由上述的各种适配解决方案,我们可以总结如下:通过提供各种布局文件,各种图片资源,合理使用九图,布局限定符等,以dp为核心对不同的屏幕适配。正确的理解核心概念是正确适配的关键。