屏幕适配
基础知识
屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
常见的屏幕尺寸:3.5,3.7,4.2,5.0,5.15
屏幕分辨率
屏幕分辨率指在横纵向上的像素点数,单位是px,1px = 1个像素点,一般以纵x横 eg:1920 x 1080
屏幕像素密度
指每英寸上的像素点数单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
安卓中的单位
- px
像素点 - dip 与 dp :
Density Independent Pixels的缩写,即密度无关像素,上面我们说过,dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi,那么在这种情况下,dp和px如何换算呢?在Android中,规定以160dpi为基准,1dip=1px,如果密度是320dpi,则1dip=2px,以此类推。 - sp :
scale-independent pixels,与dp类似,可以根据文字大小首选项进行缩放,若果文字设置成dp,则不会进行缩放
以dpi分类屏幕
mdpi、hdpi、xhdpi、xxhdpi用来修饰drawable文件夹以及values文件夹,用来区分不同像素密度下的图片和dimen值。
名称 | 像素密度范围 |
---|---|
mdpi | 120dpi ~ 160dpi |
hdpi | 160dpi ~ 240dpi |
xhdpi | 240dpi ~ 320dpi |
xxhdpi | 320dpi ~ 480dpi |
xxxhdpi | 480dpi ~ 640dpi |
在设计图标时,对于mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi应该按照2:3:4:6:8比例进行缩放
图标对应尺寸如下:
屏幕密度 | 图标尺寸 |
---|---|
mdpi | 48x48px |
hpi | 72x72px |
xhdpi | 96x96px |
xxhdpi | 144x144px |
xxxhdpi | 192x192px |
尺寸限定词
尺寸限定词主要指在编写布局文件时,将布局文件放在加上蕾丝large,sw600dp等这样的限定词文件夹中,以此来告诉屏幕选择对应的布局文件。
标识符 | 意思 |
---|---|
sw< N >dp | 最短的可用的屏幕区域(smallestWidth) |
w < N >dp | 最小的可用屏幕宽度(Available width) |
h < N >dp | 最小的可用屏幕高度(Available Height) |
port | 在纵向的情况下选择 |
land | 横向的情况下选择 |
可以使用‘-’来指定多个分离。
EG:
在布局文件中加入sw600dp布局,「layout-sw600dp」表示宽度在600dp或以上的设备
「layout-h1024dp-port」 表示高度大于或等于1024dp,纵向显示的情况
更多限定相关内容请点击下面链接
屏幕适配
使用“wrap_content” 和 “match_parent”
通过使用“wrap_content”和“match_parent”代替硬编码的尺寸,EG:
<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>
使用LinearLayout的weight属性做对应比重分配
weight是Linearlayout特有的属性,可以通过给LinearLayout的子控件设置weight值,来分配比重
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:layout_weight="1"
/>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:layout_weight="1"
/>
</LinearLayout>
使用相对布局(RelativeLayout)
使用Relative可以明确指定子View的位置
<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>
使用尺寸限定词
开发时,同时为大屏幕实现“两个窗格模式”,EG:平板和电视可能在一个屏幕上适应两个窗格
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>
使用最小宽度限定词
在安卓3.2开始使用最小宽度限定词,最小宽度限定词允许你根据设备的最小宽度(dp单位)来指定不同布局。
EG:传统的7寸平板最小宽度为600dp,如果你希望你的UI能够在这样的屏幕上显示两个窗格(不是一个窗格显示在小屏幕上),你可以使用上节中提到的使用同样的两个布局文件。不同的是,使用sw600来指定两个方框的布局使用在最小宽度为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>
使用布局别名
最小宽度限定词只能在android3.2或者更高的版本上使用。因此,你还是需要使用抽象尺寸(small,normal,large,xlarge)来兼容以前的版本。比如,你想要将你的UI设计为在手机上只显示一个方框的布局,而在7寸平板或电视,或者其他大屏幕设备上显示多个方框的布局,你可能得提供这些文件:
- res/layout/main.xml:单个窗格布局
- res/layout-large:多个窗格布局
- res/layout-sw600dp:多个窗格布局
最后两个文件都是一样的,因为其中一个将会适配Android3.2的设备,而另外一个则会适配其他Android低版本的平板或者电视。 为了避免这些重复的文件(维护让人感觉头痛就是因为这个),你可以使用别名文件。比如,你可以定义如下布局:
- res/layout/main.xml,单个方框布局
- res/layout/main_twopans.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_twopanes设置成为了别名main,它们分别处在large和sw600dp选择器中,所以它们能适配Android任何版本的平板和电视(在3.2之前平板和电视可以直接匹配large,而3.2或者以上的则匹配sw600dp)。
使用方向限定词
有一些布局不管是在横向还是纵向的屏幕配置中都能显示的非常好,但是更多的时候,适当的调整一下会更好。在News Reader应用例子中,以下是布局在不同屏幕尺寸和方向的行为:
- 小屏幕,纵向:一个窗格加logo
- 小屏幕,横向:一个窗格加logo
- 7寸平板,纵向:一个窗格加action bar
- 7寸平板,横向:两个宽窗格加action bar
- 10寸平板,纵向:两个窄窗格加action bar
- 10寸平板,横向:两个宽窗格加action bar
- 电视,横向:两个宽窗格加action bar
这些每个布局都会在res/layout目录下定义一个xml文件,如此,应用就能根据屏幕配置的变化根据别名匹配到对应的布局来适应屏幕。
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>
现在所有可能的布局我们都已经定义了,唯一剩下的问题是使用方向限定词来匹配对应的布局给屏幕。这时候,你就可以使用布局别名的功能了:
res/values/layouts.xml:
<resources>
<item name="main_layout" type="layout">@layout/onepane_with_bar</item>
<bool name="has_two_panes">false</bool>
</resources>
res/values-sw600dp-land/layouts.xml:
<resources>
<item name="main_layout" type="layout">@layout/twopanes</item>
<bool name="has_two_panes">true</bool>
</resources>
res/values-sw600dp-port/layouts.xml:
<resources>
<item name="main_layout" type="layout">@layout/onepane</item>
<bool name="has_two_panes">false</bool>
</resources>
res/values-large-land/layouts.xml:
<resources>
<item name="main_layout" type="layout">@layout/twopanes</item>
<bool name="has_two_panes">true</bool>
</resources>
res/values-large-port/layouts.xml:
<resources>
<item name="main_layout" type="layout">@layout/twopanes_narrow</item>
<bool name="has_two_panes">true</bool>
</resources>
使用.9.png图片
支持不同的屏幕尺寸同时也意味着你的图片资源也必须能兼容不同的屏幕尺寸。如果你在使用组件时可以改变图片的大小,你很快就会发现这是一个不明确的选择。因为运行的时候,图片会被拉伸或者压缩(这样容易造成图片失真)。避免这种情况的解决方案就是使用点9图片,这是一种能够指定哪些区域能够或者不能够拉伸的特殊png文件。
使用密度独立像素(dp)
设计布局时,要避免使用绝对像素(absolutepixels)定义距离和尺寸。使用像素单位来定义布局大小是有问题的。因为,不同的屏幕有不同的像素密度,所以,同样单位的像素在不同的设备上会有不同的物理尺寸。因此,在指定单位的时候,通常使用dp或者sp。一个dp代表一个密度独立像素,也就相当于在160 dpi的一个像素的物理尺寸,sp也是一个基本的单位,不过它主要是用在文本尺寸上(它也是一种尺寸规格独立的像素),所以,你在定义文本尺寸的时候应该使用这种规格单位(不要使用在布尺寸上)。
EG:当你定义两个View之间的空间时,应该使用dp而不是px:
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clickme"
android:layout_marginTop="20dp" />
当指定文本尺寸时,始终应该使用sp:
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
提供可供选择的图片
因为Android能运行在很多不同屏幕密度的设备上,所以,你应该针对不同的设备密度提供不同的bitmap资源:小屏幕(low),medium(中),high(高)以及超高(extra-high)密度。这将能帮助你在所有的屏幕密度中得到非常好的图形质量和性能。
为了提供更好的用户体验,你应该使用以下几种规格来缩放图片大小,为不同的屏幕密度提供相应的位图资源:
xhdpi:2.0
hdpi:1.5
mdpi:1.0(标准线)
ldpi:0.75
这也就意味着如果在xhdpi设备上你需要一个200x200的图片,那么你则需要一张150x150的图片用于hdpi,100x100的用于mdpi以及75x75的用户ldpi设备。
然后将这些图片资源放到res/对应的目录下面,系统会自动根据当前设备屏幕密度自动去选择合适的资源进行加载:
MyProject/
res/
drawable-xhdpi/
awesomeimage.png
drawable-hdpi/
awesomeimage.png
drawable-mdpi/
awesomeimage.png
drawable-ldpi/
awesomeimage.png
这样放置图片资源后,不论你什么时候使用@drawable/awesomeimage,系统都会给予屏幕的dp来选择合适的图片。