Android 屏幕适配解决方案

屏幕适配问题的本质

  • 使得“布局”、“布局组件”、“图片资源”、“用户界面流程”匹配不同的屏幕尺寸
  • 使得“图片资源”匹配不同的屏幕密度

这里写图片描述

布局匹配

本质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文件夹里就好,这样大大减少的设计师的工作量!

其他

  1. ImageView的ScaleType属性
    设置不同的ScaleType会得到不同的显示效果,一般情况下,设置 为centerCrop能获得较好的适配效果

  2. 有些情况下,我们需要动态的设置控件大小或者是位置,比如说popwindow的显示位置和偏移量等。这时我们可以动态获取当前的屏幕属性,然后设置合适的数值

屏幕密度匹配方案

这里写图片描述

  • 本质:使得布局组件在不同屏幕密度上显示相同的像素效果
  • 做法1:使用密度无关像素 dp

百分比适配方法

  1. 以某一分辨率为基准,生成所有分辨率对应像素数列表
  2. 将生成像素数列表存放在res目录下对应的values文件下
  3. 根据UI设计师给出设计图上的尺寸,找到对应像素数的单位,然后设置给控件即可

猜你喜欢

转载自blog.csdn.net/qq_26057629/article/details/81363030