Android 屏幕适配(Supporting Different Screen Sizes)

    本章交从以下几方面来讲解如何使 UI 适配到不同的 screen sizes 。
* 确保 layout 可以正确的 resized,以适应不同的屏幕
* 根据不同的屏幕 configuration,提供合适的 layout
* 确保特定的 layout 应用到特定的 screen
* 为不同的屏幕提供不同大小的 bitmaps

1. 正确使用 wrap_content 和 match_parent 的使用
    为了保证 layout 能适配到各种 screen sizes,在定义组件的 width 和 height 的时候,应该多使用 wrap_content 和 match_parent 。如果使用了 wrap_content,那么 view 的高宽就会被设置成刚好能够显示这个 view 的最小宽度和高度 。而 match_parent 会把组件的高宽设置成和其 parent view 一样的大小 。如
注意上面这段布局代码如何使用 wrap_content 和 match_parent,它使得这个 layout 可以正确适配到不同大小的屏幕及横屏和竖屏,如下图所示
Android 屏幕适配(Supporting Different Screen Sizes)_第1张图片
2. 使用 RelativeLayout
    使用重重嵌套的 LinearLayout 也可以做出很复杂的界面,但是 LinearLayout 很难控制各子 view 之间的关系,而且 LinearLayout 中的子 view 是顺序的,而且要么是 vertical,要么是 horizontal 。而 RelativeLayout 就可以定义各子 view 之间的位置关系,示例如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns: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>
    上面这段布局代码在竖屏和横屏情况下的显示效果如下
Android 屏幕适配(Supporting Different Screen Sizes)_第2张图片
Android 屏幕适配(Supporting Different Screen Sizes)_第3张图片

3. 使用 size qualifiers(尺寸标识符)
    为了更好的适配,仅靠前面说的那一点点是不够的,虽然它们能在不同的屏幕大小下自动调整各 view 的大小 ,但是那样会直接对各 view 进行放大或缩小,这样可能会导致 view 变形 。所以除了在定义 layout 的时候要注意以外,还要为一些不同大小的屏幕提供不同的 layouts 。这时候 configuration qualifiers 就派上用场了,系统会自动根据设备的 configuration 来决定采用哪一套 layout 。
    如有些 app 为大屏设备提供了 two pane layout(即在一个屏幕上同时显示两个版面),而在小屏设备上,一个屏幕只显示一个 pane 。实现案例如下
  • res/layout/main.xml, single-pane (default) layout:
    <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, two-pane layout:
    <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 所存放的目录是不同的,为大屏设备准备的 layout 放在 layout-large 目录下 。

4. 使用 Smallest-width 标识符
    在 Android 3.2 之前,系统并不能很好的区分 large screen,如某些设备本应该是 large screen 的,但是它加载 layout 的时候,还是会到错误的目录下加载,也就是说它没意识到自己是 large screen device 。为了解决这个问题,Android 3.2 开始引进 Smallest-width 这个概念 。
    通过 Smallest-width 标识符,你就可以为 layout 指定最小的宽度 。例如,如果你希望当设备的 width 大于等 600dp 时,就在同一个屏幕上显示两个 pane,那么你就可以如下定义你的 layout
  • res/layout/main.xml, single-pane (default) layout:
    <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, two-pane layout:
    <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>
    注意了没有,two-pane layout 的文件夹名称是 layout-sw600dp 。当屏幕大小大于等于 600dp 的时候,系统加载的是 layout-sw600dp 下的布局文件,而小于 600dp 的时候,就加载 layout 下面的 。
    但是这种设置在 Android 3.2 以前是无效的,因为 Android 3.2 以前的系统不认识 sw600dp 标识符,所以你还得为 Android 3.2 以前的系统提供另外一个目录  res/layout-large/main.xml 。

5. 使用 Layout Aliases(Layout 别名)
    从上面第 4 点的讨论可以看到,为发兼容 Android 3.2 之前的版本,我们不得不多加了一个文件夹,存放相同的布局文件 。如下
  • res/layout/main.xml: single-pane layout
  • res/layout-large: multi-pane layout
  • res/layout-sw600dp: multi-pane layout
这样会使布局文件增多,不利于维护 。为了解决这个问题,可以使用别名,即为布局文件另外取个名字,并直接放在 layout 目录下,然后在不同的目录下引用它们就行,如下
首先为 single-pane 和 two-pane 定义两个布局文件
  • res/layout/main.xml, single-pane layout
  • res/layout/main_twopanes.xml, two-pane layout
然后在需要的地方引用它们
  • 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.xml 分别拷贝到 layout-large 目录和 layout-sw600dp 目录下了 。

6. 使用 Orientation Qualifiers
    有些 layouts 可以即适配到 landscape,又适配到 portrait,如在前面的 News Reader 例子中,在不同的屏幕尺寸及横竖屏幕下,其表现如下
  • small screen, portrait: single pane, with logo
  • small screen, landscape: single pane, with logo
  • 7" tablet, portrait: single pane, with action bar
  • 7" tablet, landscape: dual pane, wide, with action bar
  • 10" tablet, portrait: dual pane, narrow, with action bar
  • 10" tablet, landscape: dual pane, wide, with action bar
  • TV, landscape: dual pane, wide, with action bar
    根据第 5 点,现在我们所有的 layouts 文件都是放在  res/layout/ 目录下的,为了适配好所有的 screen configurations,我们就需要用到 orientation qualifiers 了,如下先定义各布局文件

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:

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   
android:layout_width="fill_parent"
   
android:layout_height="fill_parent"
   
android:orientation="horizontal">
   
<fragmentandroid:id="@+id/headlines"
             
android:layout_height="fill_parent"
             
android:name="com.example.android.newsreader.HeadlinesFragment"
             
android:layout_width="200dp"
             
android:layout_marginRight="10dp"/>
   
<fragmentandroid:id="@+id/article"
             
android:layout_height="fill_parent"
             
android:name="com.example.android.newsreader.ArticleFragment"
             
android:layout_width="fill_parent"/>
</LinearLayout>
    然后在根据各种 screen configuration 的需要引用它们,如下

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>

7. 使用 9-patch bitmaps

Supporting different screen sizes usually means that your image resources must also be capable of adapting to different sizes. For example, a button background must fit whichever button shape it is applied to.

If you use simple images on components that can change size, you will quickly notice that the results are somewhat less than impressive, since the runtime will stretch or shrink your images uniformly. The solution is using nine-patch bitmaps, which are specially formatted PNG files that indicate which areas can and cannot be stretched.

Therefore, when designing bitmaps that will be used on components with variable size, always use nine-patches. To convert a bitmap into a nine-patch, you can start with a regular image (figure 4, shown with in 4x zoom for clarity).

Android 屏幕适配(Supporting Different Screen Sizes)_第4张图片

Figure 4. button.png

And then run it through the draw9patch utility of the SDK (which is located in the tools/ directory), in which you can mark the areas that should be stretched by drawing pixels along the left and top borders. You can also mark the area that should hold the content by drawing pixels along the right and bottom borders, resulting in figure 5.

Android 屏幕适配(Supporting Different Screen Sizes)_第5张图片

Figure 5. button.9.png

Notice the black pixels along the borders. The ones on the top and left borders indicate the places where the image can be stretched, and the ones on the right and bottom borders indicate where the content should be placed.

Also, notice the .9.png extension. You must use this extension, since this is how the framework detects that this is a nine-patch image, as opposed to a regular PNG image.

When you apply this background to a component (by setting android:background="@drawable/button"), the framework stretches the image correctly to accommodate the size of the button, as shown in various sizes in figure 6.

Android 屏幕适配(Supporting Different Screen Sizes)_第6张图片
Figure 6.  A button using the  button.9.png  nine-patch in various sizes.


你可能感兴趣的:(android,bitmap,layout,适配,9-patch)