Android适配那点事儿

欢迎转载,转载请标明出处:
http://blog.csdn.net/johnny901114/article/details/54880754
本文出自:【余志强的博客】

屏幕适配的基本概念

屏幕尺寸(Screen size)

屏幕的尺寸是手机屏幕对角线的长度。根据勾股定理,斜边等于宽平方加高的平方再开平方,如下图所示:

这里写图片描述

为简便起见,Android 将所有实际屏幕尺寸分组为四种通用尺寸:小、 正常、大和超大

屏幕密度(Screen density)

屏幕物理区域中的像素量;通常称为 DPI(Dots Per Inch每英寸 点数)。例如, 与“正常”或“高”密度屏幕相比,“低”密度屏幕在给定物理区域的像素较少。
为简便起见,Android 将所有屏幕密度分组为六种通用密度: 低、中、高、超高、超超高和超超超高

方向(Orientation)

从用户视角看屏幕的方向,即横屏还是竖屏,分别表示屏幕的纵横比是宽还是高。
请注意, 不仅不同的设备默认以不同的方向操作,而且 方向在运行时可随着用户旋转设备而改变。

分辨率(Resolution)

屏幕上物理像素的总数。添加对多种屏幕的支持时,应用不会直接使用分辨率;而只应关注通用尺寸和密度组指定的屏幕 尺寸及密度。

密度无关像素 (Density-independent pixel (dp))

密度无关像素等于 160 dpi 屏幕上的一个物理像素,这是 系统为“中”密度屏幕假设的基线密度。

在运行时,系统 根据使用中屏幕的实际密度按需要以透明方式处理 dp 单位的任何缩放 。dp 单位转换为屏幕像素很简单: px = dp * (dpi / 160)。

例如,在 240 dpi 屏幕上(密度为1.5),1 dp 等于 1.5 物理像素。在开发应用的时候应始终使用dp单位 ,以确保在不同密度的屏幕上正常显示 UI。

屏幕尺寸和密度的划分

Android 将实际屏幕尺寸和密度的范围 分为:

四种通用尺寸:

小、正常、 大 和超大

超大屏幕尺寸至少为 960dp x 720dp
大屏幕尺寸至少为 640dp x 480dp
正常屏幕尺寸至少为 470dp x 320dp
小屏幕尺寸至少为 426dp x 320dp

注:从 Android 3.2(API Level 13)开始,这些尺寸组 已弃用,而采用根据可用屏幕宽度管理屏幕尺寸的 新技术,后面会介绍。

六种通用的密度:

  • ldpi(低)~120dpi
  • mdpi(中)~160dpi
  • hdpi(高)~240dpi
  • xhdpi(超高)~320dpi
  • xxhdpi(超超高)~480dpi
  • xxxhdpi(超超超高)~640dpi

他们之间的比例关系如下:0.7 : 1 : 1.5 : 2 : 3 : 4

现在drawable文件夹改成mipmap,如drawable-ldpi改成mipmap-ldpi,如下图:

这里写图片描述

下图粗略的说明不同的尺寸和密度如何归类:

这里写图片描述

资源的预缩放(例如位图可绘制对象)

例如,针对 mdpi 屏幕以 50x50 像素 设计的位图在 hdpi 屏幕上将扩展至 75x75 像素(如果没有 用于 hdpi 的备用资源)。

缩放遵循上面说的比例关系 ldpi:mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi = 0.7 : 1 : 1.5 : 2 : 3 : 4

如果你不需要预缩放功能(Pre-scaling),最简单的方法是将资源放在有nodpi配置限定符的资源目录中。例如:
res/drawable-nodpi/icon.png当系统使用此文件夹中的 icon.png 位图时,不会根据当前设备密度缩放。

屏幕适配常用的几个手段

利用资源的预缩放功能

Android建议针对不同的密度的设置设计不同质量大小的图片,例如设计在设计图标的时候,假设是针对1280 x 720分辨率来设计的(xhdpi),那么某图标设计为72 x 72像素,那么应该在xxhdpi限定符下放置96 x 96像素的图标,说白就是设计多套图标。如果所有的限定符都设计一套图标,那么APK大小会非常大。

我们可以利用资源的预缩放功能,只设计一套高分辨率的图标,Android会自动缩放。如果是小图标放大成大图标肯定会失真严重,大图标缩小也会缩小失真,但是比小的放大效果要好点,这就是一种取舍。最好的当然是针对不同的限定符设置一套图标。

利用Android限定符

我们在开发App的时候适配loading页的时候,就可以利用限定符功能。

大家都知道APP的loading也的适配图都是比较大的,如果所有的适配都适配完那是不现实的,APK会增大很多,为了这一个功能让整个APK的体积增大很多是不值得的。

所以一般我们适配主流的屏幕就可以了。下面是友盟2016年12月份的Android设备主流分辨率分布图:

这里写图片描述

从上面的数据可以看出1280 x 7201920 x 1080两个分辨率占了60%,其次是854 x 480占了9.6%,如果你要适配这个三个分辨率,就可以使用限定符功能,1920 x 1080一般属于xxhdpi密度的设备,1280 x 720属于xhdpi,854 x 480属于hdpi,所以我们把三个尺寸的loading图分别放到hdpi、xhdpi、xxhdpi,针对不同密度的设置Android会自动从不同的限定符去加载资源。

上面我们适配了友盟统计的前三个分辨率的loading图,但是从上面的友盟数据可以看到排在第四的960x540也是属于hdpi,但是在这一个hdpi只能放一个分辨率的loading图。这就是通过限定符来实现loading图适配的不足。但是不足也是相对的。

这时候就自然而想到了根据分辨率来动态选择加载那个loading。例如,假设当前的分辨率是出1280 x 720我们去加载出1280 x 720的loading,其他分辨率的同理。适配前四个分辨率是没有问题的,只要通过代码来判断就可以了。这样可以精确的控制。但是这样做也有个不足,因为Android分辨率很多,如果某个分辨率我们没有判断,假设分辨率为1290 x 730,它与1280 x 720相近,同样属于xhdpi,按道理应该从离得最近的分辨率的去加载,但是我们并没有判断这样的分辨率。但是如果通过限定符来做的话就没有问题,因为限定符自带该功能。

针对loading界面的适配的建议:

要想适配好app启动的loading图,最好不要尺寸精确适配(就是loading图和屏幕的宽高一样)。把背景做成纯色背景,或者把背景的内容弱化哪怕背景被系统裁剪也没关系。然后呢,背景上的内容通过小图片或控件来布局,也就是loading界面分为两个部分,一个是背景,一个是内容。而不是背景和内容都用一张图来表现。这样就很好的解决了这样的问题了。

下面列举一些不同限定符常见的分辨率:

res/drawable-ldpi/  (240x320 and nearer resolution)
res/drawable-mdpi/  (320x480 and nearer resolution)
res/drawable-hdpi/  (480x800, 540x960 and nearer resolution)
res/drawable-xhdpi/  (720x1280 - Samsung S3, Micromax Canvas HD etc)
res/drawable-xxhdpi/ (1080x1920 - Samsung S4, HTC one, Nexus 5, etc)

wrap_content、match_parent、weight属性

为确保布局能够灵活地适应不同的屏幕尺寸,我们应该为某些视图组件的宽度和高度使用 “wrap_content” 和 “match_parent”。 如果您使用 “wrap_content”,视图的宽度或高度将设置为使内容适应该视图所需的最小尺寸,而 “match_parent” 会使组件通过扩展来匹配其父视图的尺寸。例如:

<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>

下面是针对上面布局横屏和竖屏的效果图(来自Google官方):

这里写图片描述

使用RelativeLayout

您可以使用 LinearLayout 的嵌套实例和 “wrap_content” 尺寸与 “match_parent” 尺寸的组合来构建相当复杂的布局。不过,LinearLayout 不允许您精确控制子视图的空间关系;LinearLayout 中的视图只是排成一行,要么横向要么纵向。

RelativeLayout可以根据组件之间的空间关系指定布局并且可以有效的减少布局层级。

例如,您可以在屏幕左侧布置一个子视图,在屏幕右侧布置另一个子视图。

<?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 屏幕上的显示效果:

这里写图片描述

在较大屏幕上的显示的效果:

这里写图片描述

nine patch(.9)文件

通常我们需要某个icon只缩放某个区域,像普通的icon都是系统让其等比缩放的。

这个时候就可以使用.9文件,典型的使用它场景就是聊天的背景,如(一般聊天的内容背景的尖角不需要缩放的。):

这里写图片描述

.9这种特殊格式的 PNG文件会指示哪些区域可以拉伸,哪些区域不可以拉伸。

下面是一个使用各种尺寸 button.9.png 图像的按钮:

这里写图片描述

如果对.9文件的使用不是很明白,可以在网上找到很多文档,在这里就不赘述了。

适配不同尺寸的屏幕

上面的介绍的一些适配的方法在一定程度上都是针对一个布局的,有时候一个布局在不同屏幕尺寸展示还是那么不尽人意。

所以这个时候就需要针对不同的屏幕配置提供多种备选布局。

使用尺寸限定符

我们可以利用配置限定符来实现此目的,它允许运行组件根据当前设备配置(如针对不同屏幕尺寸的不同布局设计)自动选择合适的资源。

我们上面介绍了,屏幕的尺寸可以分为小、正常、大、超大(small, normal, large, and extra-large)

如果我们要适配大屏幕怎么办?我们还是以Google提供的例子(NewsReader),应用针对大屏幕实现了“双窗格”模式(应用可以在一个窗格中显示项目列表,在另一个窗格中显示项目内容)。 平板电脑和 TV 足够大,可在一个屏幕上同时容纳两个窗格,但手机屏幕只能独立显示它们。 因此,如需实现这些布局,您可以建立以下文件(布局的具体内容就不贴出来了):

  • res/layout/main.xml,单窗格(默认)布局
  • res/layout-large/main.xml,双窗格布局

请注意第二个布局目录名称中的large 限定符。在屏幕归类为大屏幕的设备(例如,7 英寸及更大尺寸的平板电脑)上,将选择此布局。 对于小型设备,将选择另一个布局(无限定符)

使用最小宽度限定符

使用通用化的尺寸组时,为 7 英寸平板电脑设计很棘手的原因在于, 7 英寸平板电脑在技术上与 5 英寸手机属于同一个组(大组large)

虽然 这两种设备在尺寸上似乎很接近,但用于 应用 UI 的空间量明显不同,用户交互的方式也明显不同。因此,7 英寸和 5 英寸 屏幕不一定使用相同的布局。为便于您为这两种屏幕提供不同的 布局。所以Android3.2推出了使用最小宽度限定符功能。

使用和large限定符类似,如:

  • res/layout-sw600dp/main.xml,双窗格布局

限定符中的sw全拼为smallestWidth

例如,在设计要用于平板电脑样式设备的布局之后,发现该布局在屏幕宽度小于 600dp 时不适用。此阈值 于是变成平板电脑布局需要的最小尺寸。因此,您现在可以指定应仅当至少有 600dp 宽度供应用的 UI 使用时才使用这些布局资源。

使用布局别名

最小宽度限定符仅在 Android 3.2 及更高版本上提供。因此,您仍应使用兼容早期版本的抽象尺寸容器(小、正常、大和超大)。 例如,如果您想让自己设计的 UI 在手机上显示单窗格 UI,但在 7 英寸平板电脑、TV 及其他大屏设备上显示多窗格 UI,则需要提供下列文件:

-res/layout/main.xml: 单窗格布局
-res/layout-large: 多窗格布局
-res/layout-sw600dp: 多窗格布局

后两个文件完全相同,Android为了避免产生多个重复相同的文件,Android为我们提供布局别名功能。

我们先提供两不同布局形式:

-res/layout/main.xml,单窗格布局
-res/layout/main_twopanes.xml,双窗格布局

然后添加以下两个文件:

res/values-large/layouts.xml:

<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

res/values-sw600dp/layouts.xml:

<resources>
    <item name="main" type="layout">@layout/main_twopanes</item>
</resources>

低于 3.2 版本的平板电脑和电视匹配 large,高于 3.2 版本者将匹配 sw600dp

使用屏幕方向限定符

这里写图片描述

例如:

res/layout/my_layout.xml              // layout for normal screen size ("default")
res/layout-large/my_layout.xml        // layout for large screen size
res/layout-xlarge/my_layout.xml       // layout for extra-large screen size
res/layout-xlarge-land/my_layout.xml  // layout for extra-large in landscape orientation

然后可以利用布局别名技巧:

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>

百分比布局

针对如下图我们如何实现底部的布局:

这里写图片描述

一般来说我们先把底部分为两个部分添加评论三个按钮,假设通过度量发现添加评论宽度为xx dp,然后剩余的宽度全部给三个按钮,可能在当前设计的分辨率展示是没有问题的,但是如果放到小点的或者大点的分辨率的手机上展示就不尽人意了。

这个时候就可以通过百分比布局来实现了。依然把界面的底部分为两个部分,先量出添加评论的宽度占屏幕宽度的百分比是多少,然后三个按钮这部分的宽度占比也就出来了,然后三个按钮里的按钮平分它的宽度就可以了。

现在gradle.build文件中加入百分比布局依赖:

compile 'com.android.support:percent:24.2.0'

完成上面效果的完整布局:

<android.support.percent.PercentRelativeLayout
    android:id="@+id/ll_bottom"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_alignParentBottom="true"
    android:background="#ffffff"
    android:orientation="horizontal">

    <View
        android:layout_width="match_parent"
        android:layout_height="0.3dp"
        android:background="#cfcfcf"/>

    <FrameLayout
        android:id="@+id/fl_add_comment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:background="@drawable/selector_article_add_comment"
        app:layout_widthPercent="40%">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:drawableLeft="@mipmap/ic_comment_add2"
            android:drawablePadding="12dp"
            android:gravity="center"
            android:text="添加评论"
            android:textSize="16sp"/>
    </FrameLayout>


    <View
        android:id="@+id/view_divider"
        android:layout_width="0.5dp"
        android:layout_height="match_parent"
        android:layout_toRightOf="@id/fl_add_comment"
        android:background="#cfcfcf"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toRightOf="@id/view_divider"
        android:gravity="center">

        <RelativeLayout
            android:id="@+id/rl_comment_count"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">

            <ImageView
                android:id="@+id/iv_to_comment"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:contentDescription="@null"
                android:src="@mipmap/ic_comment"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignTop="@id/iv_to_comment"
                android:layout_marginLeft="-5dp"
                android:layout_marginTop="-6dp"
                android:layout_toRightOf="@id/iv_to_comment"
                android:background="@drawable/shape_count_number"
                android:maxLength="4"
                android:paddingLeft="3dp"
                android:paddingRight="3dp"
                android:text="@{String.valueOf(article.commentCount),default=100}"
                android:textColor="#ffffff"
                android:textSize="10sp"/>
        </RelativeLayout>


        <RelativeLayout
            android:id="@+id/rl_bottom_like"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">

            <ImageView
                android:id="@+id/iv_to_like"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:contentDescription="@null"
                android:src="@mipmap/ic_hot_like2"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignTop="@id/iv_to_like"
                android:layout_marginLeft="-4dp"
                android:layout_marginTop="-6dp"
                android:layout_toRightOf="@id/iv_to_like"
                android:background="@drawable/shape_count_number"
                android:maxLength="4"
                android:paddingLeft="3dp"
                android:paddingRight="3dp"
                android:text="@{String.valueOf(article.likeCount),default=100}"
                android:textColor="#ffffff"
                android:textSize="10sp"/>
        </RelativeLayout>

        <ImageView
            android:id="@+id/iv_bottom_share"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:contentDescription="@null"
            android:src="@mipmap/icon_share_new"/>
    </LinearLayout>
</android.support.percent.PercentRelativeLayout>

ImageView的ScaleType属性

在Android上面我们通过ImageView 来展示一张图片,但是后台返回的图片的尺寸比例和我们布局设定的宽高比不一致,在这种情况下使用scaleType=centerCrop。这样不管后台返回的图片宽高比和我们设置的是否一样ImageView显示的图片不会变形(但是可能被裁剪)。

动态设置宽高

比如我们图片的高度是根据屏幕宽度来决定的,如下图:

这里写图片描述

假设界面里的图的宽高比是3:2,因为设备的宽度在不同的手机上可能不一样,为了保持宽高比一直,需要获取屏幕的宽度,然后动态设置ImageView的高度。

在其他Activity中重复使用Fragment

在开发中重用Fragment比较典型的案例就是 列表页搜索结果页(搜索的结果是列表)

比如在输入框搜索国画结果如下:

这里写图片描述

分类列表界面 如下所示:

这里写图片描述

然后点击分类,结果页搜索结果页是一样的,这个结果页面还可能被其他页面引用,如果TabLayout、ViewPager,所以可以把该界面使用Fragment来做是最合适的。

重用的Fragment时候注意一下几点:

1)Fragment不要和Activity强耦合,通过接口来通信。例如在Fragment通知Activity不要硬编码某个Activity而应该让Activity实现某个接口,然后在Fragment通过接口的方式来通信。

2)Fragment应该只专注内容部分,像Title部分不要包含。因为很多界面虽然内容相同但是Title部分是不一样的。

参考文档:

实现自适应UI流

支持不同屏幕尺寸

本文主要参考Google官方文档和自己对屏幕适配的一些实践,如果有什么错误或者更好的适配方案,欢迎留言讨论。

猜你喜欢

转载自blog.csdn.net/johnny901114/article/details/54880754