屏幕适配问题的本质
- 使得“布局”、“布局组件”、“图片资源”、“用户界面流程”匹配不同的屏幕尺寸
- 使得“图片资源”匹配不同的屏幕密度
布局匹配
本质1:使得布局元素自适应屏幕尺寸
- 布局的子控件之间使用相对位置的方式排列,因为RelativeLayout讲究的是相对位置,即使屏幕的大小改变,视图之前的相对位置都不会变化,与屏幕大小无关,灵活性很强
- LinearLayout无法准确地控制子视图之间的位置关系,只能简单的一个挨着一个地排列
所以,对于屏幕适配来说,使用相对布局(RelativeLayout)将会是更好的解决方案
本质2:根据屏幕的配置来加载相应的UI布局
应用场景:需要为不同屏幕尺寸的设备设计不同的布局
- 做法:使用限定符
- 作用:通过配置限定符使得程序在运行时根据当前设备的配置(屏幕尺寸)自动加载合适的布局资源
限定符类型
- 尺寸(size)限定符
- 最小宽度(Smallest-width)限定符
- 布局别名
- 屏幕方向(Orientation)限定符
尺寸(size)限定符
使用场景:当一款应用显示的内容较多
1.在平板电脑和电视的屏幕(>7英寸)上:实施“双面板”模式以同时显示更多内容
2.在手机较小的屏幕上:使用单面板分别显示内容
因此,我们可以使用尺寸限定符(layout-large)通过创建一个文件来完成上述设定:
res/layout-large/main.xml
这种方式只适合Android 3.2版本之前。因为限定符large 没有一个定量的指标,这便意味着可能没办法准确地根据当前设备的配置(屏幕尺寸)自动加载合适的布局资源(3.2后不建议使用)
最小宽度(Smallest-width)限定符
在Android 3.2及之后版本,引入了最小宽度(Smallest-width)限定符
- 定义:通过指定某个最小宽度(以 dp 为单位)来精确定位屏幕,从而加载不同的UI资源
- 使用场景:
你需要为标准 7 英寸平板电脑匹配双面板布局(其最小宽度为 600 dp),在手机(较小的屏幕上)匹配单面板布局
- layout-sw xxxdp 即small width的缩写,其不区分方向,即无论是宽度还是高度,只要大于 xxxdp,就采用次此布局
解决方案:您可以使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕。
例子:
适配手机的单面板(默认)布局: 适用于最小宽度为 600 dp 的屏幕。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>
适用于宽度 ≥ 600 dp 的(双面板布局)设备布局: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>
使用布局别名
设想这么一个场景
,当你需要同时为Android 3.2版本前和Android 3.2版本后的手机进行屏幕尺寸适配的时候,由于尺寸限定符仅用于Android 3.2版本前,最小宽度限定符仅用于Android 3.2版本后,所以这会带来一个问题,为了很好地进行屏幕尺寸的适配,你需要同时维护layout-sw600dp和layout-large的两套main.xml平板布局,如下
- 适配手机的单面板(默认)布局:res/layout/main.xml
- 适配尺寸>7寸平板的双面板布局(Android 3.2前):res/layout-large/main.xml
- 适配尺寸>7寸平板的双面板布局(Android 3.2后)res/layout-sw600dp/main.xml
最后的两个文件的xml内容是完全相同的,这会带来:文件名的重复从而带来一些列后期维护的问题,于是为了要解决这种重复问题,我们引入了布局别名
还是上面的例子,你可以定义以下布局:
- 适配手机的单面板(默认)布局:res/layout/main.xml
- 适配尺寸>7寸平板的双面板布局:res/layout/main_twopanes.xml
然后加入以下两个文件,以便进行Android 3.2前和Android 3.2后的版本双面板布局适配:
1.res/values-large/layout.xml(Android 3.2之前的双面板布局)
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
2.res/values-sw600dp/layout.xml(Android 3.2及之后的双面板布局)
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
这样两个layout.xml都只是引用了@layout/main_twopanes,就避免了重复定义布局文件的情况
屏幕方向(Orientation)限定符
使用场景:根据屏幕方向进行布局的调整
取以下为例子:
- 小屏幕, 竖屏: 单面板
- 小屏幕, 横屏: 单面板
- 7 英寸平板电脑,纵向:单面板,带操作栏
- 7 英寸平板电脑,横向:双面板,宽,带操作栏
- 10 英寸平板电脑,纵向:双面板,窄,带操作栏
- 10 英寸平板电脑,横向:双面板,宽,带操作栏
- 电视,横向:双面板,宽,带操作栏
方法是:
- 先定义类别:单/双面板、是否带操作栏、宽/窄
定义在 res/layout/ 目录下的某个 XML 文件中
- 再进行相应的匹配:屏幕尺寸(小屏、7寸、10寸)、方向(横、纵)
使用布局别名进行匹配
1.在 res/layout/ 目录下的 文件中定义所需要的布局类别(单/双面板、是否带操作栏、宽/窄)
res/layout/onepane.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/onepane_with_bar.xml:(单面板带操作栏)
<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>
res/layout/twopanes.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>
res/layout/twopanes_narrow.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="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>
2.使用布局别名进行相应的匹配(屏幕尺寸(小屏、7寸、10寸)、方向(横、纵))
res/values/layouts.xml:(默认布局)
<resources>
<item name="main_layout" type="layout">@layout/onepane_with_bar</item>
<bool name="has_two_panes">false</bool>
</resources>
可为resources设置bool,通过获取其值来动态判断目前已处在哪个适配布局
res/values-sw600dp-land/layouts.xml(大屏、横向、双面板、宽-Andorid 3.2版本后)
<resources>
<item name="main_layout" type="layout">@layout/twopanes</item>
<bool name="has_two_panes">true</bool>
</resources>
res/values-sw600dp-port/layouts.xml(大屏、纵向、单面板带操作栏-Andorid 3.2版本后)
<resources>
<item name="main_layout" type="layout">@layout/onepane</item>
<bool name="has_two_panes">false</bool>
</resources>
res/values-large-land/layouts.xml(大屏、横向、双面板、宽-Andorid 3.2版本前)
<resources>
<item name="main_layout" type="layout">@layout/twopanes</item>
<bool name="has_two_panes">true</bool>
</resources>
res/values-large-port/layouts.xml(大屏、纵向、单面板带操作栏-Andorid 3.2版本前)
<resources>
<item name="main_layout" type="layout">@layout/onepane</item>
<bool name="has_two_panes">false</bool>
</resources>
布局组件匹配
本质:使得布局组件自适应屏幕尺寸
- 做法 使用”wrap_content”、”match_parent”和”weight“来控制视图组件的宽度和高度
图片资源匹配
- 本质:使得图片资源在不同屏幕密度上显示相同的像素效果
- 提供备用位图(符合屏幕尺寸的图片资源)
密度类型 | 缩写 | 代表的分辨率 | 屏幕像素密度 | 尺寸比例 |
---|---|---|---|---|
低密度 | LDPI | 240x320 | 120 | 0.75 |
中密度 | MDPI | 320x480 | 160 | 1 |
高密度 | HDPI | 480x800 | 240 | 1.5 |
超高密度 | XHDPI | 720x1280 | 320 | 2 |
超超高密度 | XXHDPI | 1080x1920 | 480 | 3 |
超超超高密度 | XXXHDPI | 640 | 4 |
- 步骤1:根据以下尺寸范围针对各密度生成相应的图片。
比如说,如果我们为 xhdpi 设备生成了 200x200 px尺寸的图片,就应该按照相应比例地为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75
- 步骤2:将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片
那么,有没有一种方法能更好地解决图片资源适配问题:
- 保证屏幕密度适配
- 可以最小占用设计资源
- 使得apk包不变大(只使用一套分辨率的图片资源)
1. 先来理解下Android 加载资源过程
Android SDK会根据屏幕密度自动选择对应的资源文件进行渲染加载(自动渲染)
比如说,SDK检测到你手机的分辨率是320x480(dpi=160),会优先到drawable-mdpi文件夹下找对应的图片资源;但假设你只在xhpdi文件夹下有对应的图片资源文件(mdpi文件夹是空的),那么SDK会去xhpdi文件夹找到相应的图片资源文件,然后将原有大像素的图片自动缩放成小像素的图片,于是大像素的图片照样可以在小像素分辨率的手机上正常显示。
所以需要提供一套你需要支持的最大dpi分辨率规格的图片资源,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。那么这一套最大dpi分辨率规格应该是哪种呢?是现在市面手机分辨率最大可达到1080X1920的分辨率(dpi=xxdpi=480)吗?
2. xhdpi应该是首选
- xhdpi分辨率以内的手机需求量最旺盛
- 节省设计资源&工作量
在现在的App开发中(iOS和Android版本),有些设计师为了保持App不同版本的体验交互一致,可能会以iPhone手机为基础进行设计,包括后期的切图之类的。
设计师们一般都会用最新的iPhone6和iPhone5s(5s和5的尺寸以及分辨率都一样)来做原型设计,所有参数请看下图
机型 | 分辨率(px) | 屏幕尺寸(inch) | 系统密度(dpi) |
---|---|---|---|
iPhone 5s | 640X1164 | 4 | 332 |
iPhone 6 | 1334x750 | 4.7 | 326 |
iPhone 6 Plus | 1080x1920 | 5 | 400 |
iPhone主流的屏幕dpi约等于320, 刚好属于xhdpi,所以选择xhdpi作为唯一一套dpi图片资源,可以让设计师不用专门为Android端切图,直接把iPhone的那一套切好的图片资源放入drawable-xhdpi文件夹里就好,这样大大减少的设计师的工作量!
其他
ImageView的ScaleType属性
设置不同的ScaleType会得到不同的显示效果,一般情况下,设置 为centerCrop能获得较好的适配效果有些情况下,我们需要动态的设置控件大小或者是位置,比如说popwindow的显示位置和偏移量等。这时我们可以动态获取当前的屏幕属性,然后设置合适的数值
屏幕密度匹配方案
- 本质:使得布局组件在不同屏幕密度上显示相同的像素效果
- 做法1:使用密度无关像素 dp
百分比适配方法
- 以某一分辨率为基准,生成所有分辨率对应像素数列表
- 将生成像素数列表存放在res目录下对应的values文件下
- 根据UI设计师给出设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可