android屏幕适配

来源:互联网 发布:淘宝网变形金刚玩具 编辑:程序博客网 时间:2024/05/16 16:12

使用“wrap_content”和“match_parent”

为确保您的布局能够灵活地适应不同的屏幕尺寸,您应该为某些视图组件的宽度和高度使用 "wrap_content" 和 "match_parent"。 如果您使用 "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>

请注意示例应用如何为组件尺寸使用 "wrap_content" 和 "match_parent",而非使用具体尺寸。 这可使布局正确适应不同的屏幕尺寸和屏幕方向。

例如,以下便是此布局在纵向模式和横向模式下的外观。 请注意,组件尺寸会自动适应宽度和高度:

图 1. 纵向模式(左侧)和横向模式(右侧)下的 News Reader 示例应用。

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

图 2 展示了此布局在 QVGA 屏幕上的显示外观。

图 2. QVGA 屏幕(小屏幕)上的屏幕截图。

图 3 展示了它在较大屏幕上的显示外观。

图 3. WSVGA 屏幕(大屏幕)上的屏幕截图。

请注意,尽管组件的尺寸发生了变化,但其空间关系如 RelativeLayout.LayoutParams 所指定得以保留。

使用尺寸限定符

您可以从前文介绍的那类灵活布局或相对布局中获得的好处是有限的。 尽管这些布局可以通过拉伸组件内部和周围的空间来适应不同的屏幕,却可能无法为每一种屏幕尺寸提供最佳用户体验。 因此,您的应用不仅应实现灵活布局,还应针对不同的屏幕配置提供多种备选布局。 您可以利用配置限定符来实现此目的,它允许运行组件根据当前设备配置(如针对不同屏幕尺寸的不同布局设计)自动选择合适的资源。

例如,许多应用都针对大屏幕实现了“双窗格”模式(应用可以在一个窗格中显示项目列表,在另一个窗格中显示项目内容)。 平板电脑和 TV 足够大,可在一个屏幕上同时容纳两个窗格,但手机屏幕只能独立显示它们。 因此,如需实现这些布局,您可以建立以下文件:

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

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

使用最小宽度限定符

开发者在 3.2 以下版本 Android 设备上遇到的其中一个困难是“庞大”的屏幕尺寸容器,其中囊括了 Dell Streak、原版 Galaxy Tab 以及常规 7 英寸平板电脑。 不过,尽管这些设备都被视为“大”屏设备,许多应用可能仍需要为此类别中的不同设备(如为 5 英寸和 7 英寸设备)显示不同的布局。 正因如此,Android 在 Android 3.2 中引入了“最小宽度”限定符。

最小宽度限定符允许您将目标锁定在具有特定最小宽度(单位:dp)的屏幕。 例如,典型的 7 英寸平板电脑最小宽度为 600dp,因此,如果您希望您的 UI 在这些屏幕上显示两个窗格(但在较小屏幕上显示单个列表),您同样可以为单窗格布局和双窗格布局使用前文中的两种布局,但不使用 large 尺寸限定符,而是使用 sw600dp 为最小宽度是 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>

这意味着,最小宽度大于或等于 600dp 的设备将选择 layout-sw600dp/main.xml(双窗格)布局,而屏幕较小的设备将选择 layout/main.xml(单窗格)布局。

不过,这种方法在低于 3.2 版本的设备上不太奏效,因为它们无法将 sw600dp 识别为尺寸限定符,所以您仍需使用 large 限定符。因此,您应该建立一个与 res/layout-sw600dp/main.xml 完全相同的、名为 res/layout-large/main.xml 的文件。下文介绍的技巧可让您避免因此而产生重复的布局文件。

使用布局别名

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

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

后两个文件完全相同,因为其中一个将由 Android 3.2 设备匹配,另一个是为了照顾使用早期版本 Android 的平板电脑和 TV 的需要。

为避免为平板电脑和 TV 产生相同的重复文件(以及由此带来的维护难题),您可以使用别名文件。 例如,您可以定义下列布局:

  • res/layout/main.xml,单窗格布局
  • res/layout/main_twopanes.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 设置为 main_twopanes 的别名。由于这些文件具有 large 和 sw600dp 选择器,因此它们适用于任何 Android 版本的平板电脑和电视(低于 3.2 版本的平板电脑和电视匹配 large,高于 3.2 版本者将匹配 sw600dp)。

使用屏幕方向限定符

某些布局在横向和纵向屏幕方向下都表现不错,但其中大多数布局均可通过调整做进一步优化。 在 News Reader 示例应用中,以下是布局在各种屏幕尺寸和屏幕方向下的行为:

  • 小屏幕,纵向:单窗格,带徽标
  • 小屏幕,横向:单窗格,带徽标
  • 7 英寸平板电脑,纵向:单窗格,带操作栏
  • 7 英寸平板电脑,横向:双窗格,宽,带操作栏
  • 10 英寸平板电脑,纵向:双窗格,窄,带操作栏
  • 10 英寸平板电脑,横向:双窗格,宽,带操作栏
  • TV,横向:双窗格,宽,带操作栏

因此,以上每一种布局都在 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>

使用九宫格位图

支持不同屏幕尺寸通常意味着您的图像资源也必须能够适应不同的尺寸。 例如,按钮背景必须能够适应其所应用到的任何一种按钮形状。

如果您在可能改变尺寸的组件上使用简单图像,您很快会发现效果有些差强人意,因为运行组件会均匀地拉伸或缩小您的图像。 解决方案是使用九宫格位图,这种特殊格式的 PNG 文件会指示哪些区域可以拉伸,哪些区域不可以拉伸。

因此,在设计将用于尺寸可变组件的位图时,请一律使用九宫格位图。 如需将位图转换为九宫格位图,您可以先从普通图像着手(图 4,为了清晰起见,放大 4 倍显示)。

图 4.button.png

然后通过 SDK 的 draw9patch 实用程序(位于 tools/ 目录中)运行它,在该实用程序中,您可以通过沿左侧和顶部边框绘制像素来标记应拉伸的区域。 您还可以通过沿右侧和底部边框绘制像素来标记应容纳内容的区域,结果会得到图 5。

图 5.button.9.png

请注意边框沿线的黑色像素。顶部和左侧边框上的黑色像素指示可以拉伸图像的位置,右侧和底部边框上的黑色像素则指示应该放置内容的位置。

还请注意 .9.png 扩展。您必须使用此扩展,因为框架就是通过它来检测这是一幅九宫格图像,而不是普通的 PNG 图像。

当您对组件应用此背景时(通过设置 android:background="@drawable/button"),框架会正确拉伸图像来适应按钮的尺寸,如图 6 中的各种尺寸所示。

图 6. 一个使用各种尺寸 button.9.png 九宫格图像的按钮。

使用密度无关像素


您在设计布局时必须避免的一个常见陷阱是,使用绝对像素来定义距离或尺寸。 使用像素来定义布局尺寸会带来问题,因为不同的屏幕具有不同的像素密度,因此同样数量的像素在不同设备上可能对应于不同的物理尺寸。 因此,在指定尺寸时,请务必使用 dp 或 sp单位。dp 是一种密度无关像素,对应于 160 dpi 下像素的物理尺寸。 sp 是相同的基本单位,但它会按用户首选的文本尺寸进行缩放(属于缩放无关像素),因此您在定义文本尺寸时应使用此计量单位(但切勿为布局尺寸使用该单位)。


例如,当您指定两个视图的间距时,请使用 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 的设备具有多种屏幕密度,您应始终提供能够根据各种通用密度级别(低密度、中密度、高密度和超高密度)进行定制的位图资源。这有助于您在所有屏幕密度上获得良好的图形质量和性能。

如需生成这些图像,您应以矢量格式的原始资源为基础,按以下尺寸缩放比例生成每种屏幕密度对应的图像:

  • xhdpi:2.0
  • hdpi:1.5
  • mdpi:1.0(基准)
  • ldpi:0.75

这意味着,如果您为 xhdpi 设备生成了一幅 200x200 的图像,则应分别按 150x150、100x100 和 75x75 图像密度为 hdpi 设备、mdpi 设备和 ldpi 设备生成同一资源。

然后,将生成的图片文件置于 res/ 下的相应子目录中,系统将自动根据运行您的应用的设备的屏幕密度选取正确的文件:

MyProject/  res/    drawable-xhdpi/        awesomeimage.png    drawable-hdpi/        awesomeimage.png    drawable-mdpi/        awesomeimage.png    drawable-ldpi/        awesomeimage.png

之后,每当您引用 @drawable/awesomeimage 时,系统便会根据屏幕 dpi 选择相应的位图。

将您的启动器图标置于 mipmap/ 文件夹中。

res/...    mipmap-ldpi/...        finished_launcher_asset.png    mipmap-mdpi/...        finished_launcher_asset.png    mipmap-hdpi/...        finished_launcher_asset.png    mipmap-xhdpi/...        finished_launcher_asset.png    mipmap-xxhdpi/...        finished_launcher_asset.png    mipmap-xxxhdpi/...        finished_launcher_asset.png

确定当前布局


由于您对每个布局的实现都略有差异,您需要优先完成的一项工作可能是确定用户目前查看的布局。 例如,您可能想了解用户是处于“单窗格”模式还是“双窗格”模式。 您可以通过查询给定视图是否存在并且是否可见来实现此目的:

public class NewsReaderActivity extends FragmentActivity {    boolean mIsDualPane;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main_layout);        View articleView = findViewById(R.id.article);        mIsDualPane = articleView != null &&                        articleView.getVisibility() == View.VISIBLE;    }}

请注意,此代码查询“article”窗格是否可用,这种方法要比针对特定布局对查询进行硬编码灵活得多。

还有一个示例可以说明如何适应不同组件的存在,其内容是检查组件是否存在,然后再对它们执行操作。 例如,在 News Reader 示例应用中,有一个用于打开菜单的按钮,但该按钮只在运行的版本低于 Android 3.0 时才存在(因为从 API 级别 11 开始,该功能已被 ActionBar 取代)。因此,如需为此按钮添加事件侦听器,您可以这样做:

Button catButton = (Button) findViewById(R.id.categorybutton);OnClickListener listener = /* create your listener here */;if (catButton != null) {    catButton.setOnClickListener(listener);}

根据当前布局作出反应


某些操作可能视当前布局而有不同的结果。例如,在 News Reader 示例应用中,如果 UI 处于双窗格模式,则点击标题列表中的某个标题会在右侧窗格中打开该文章,但如果 UI 处于单窗格模式,则会启动不同的 Activity:

@Overridepublic void onHeadlineSelected(int index) {    mArtIndex = index;    if (mIsDualPane) {        /* display article on the right pane */        mArticleFragment.displayArticle(mCurrentCat.getArticle(index));    } else {        /* start a separate activity */        Intent intent = new Intent(this, ArticleActivity.class);        intent.putExtra("catIndex", mCatIndex);        intent.putExtra("artIndex", index);        startActivity(intent);    }}

同样,如果应用处于双窗格模式,其设置的操作栏应包含用于导航的标签;而如果应用处于单窗格模式,则应设置具有微调框小部件的导航。 因此您的代码还应检查哪一种情况适当:

final String CATEGORIES[] = { "Top Stories", "Politics", "Economy", "Technology" };public void onCreate(Bundle savedInstanceState) {    ....    if (mIsDualPane) {        /* use tabs for navigation */        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);        int i;        for (i = 0; i < CATEGORIES.length; i++) {            actionBar.addTab(actionBar.newTab().setText(                CATEGORIES[i]).setTabListener(handler));        }        actionBar.setSelectedNavigationItem(selTab);    }    else {        /* use list navigation (spinner) */        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);        SpinnerAdapter adap = new ArrayAdapter(this,                R.layout.headline_item, CATEGORIES);        actionBar.setListNavigationCallbacks(adap, handler);    }}

在其他 Activity 中重复使用 Fragment


在面向多种屏幕的设计中采用的一种固定模式是,让界面的某一部分在一些屏幕配置下以窗格形式实现,在其他配置下则以一个单独 Activity 的形式实现。 例如,在 News Reader 示例应用中,新闻文章文字在较大屏幕上显示在右侧窗格中,但在较小屏幕上则显示在一个单独的 Activity 内。

在这类情况下,您通常可以通过在几个 Activity 中重复使用同一 Fragment 子类来避免代码重复。例如,双窗格布局中使用了 ArticleFragment

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

并在适用于较小屏幕的 Activity 布局 (ArticleActivity) 中重复使用(无布局):

ArticleFragment frag = new ArticleFragment();getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

当然,这与在 XML 布局中声明 Fragment 的效果相同,但在此情况下,XML 布局是无用功,因为文章 Fragment 是该 Activity 的唯一组件。

设计 Fragment 时需要牢记的一个要点是不要创建与特定 Activity 的强耦合。 为此,您通常可以定义一个界面,将 Fragment 与其宿主 Activity 进行交互时需要使用的所有方式抽象化,然后宿主 Activity 会实现该界面:

例如,News Reader 应用的 HeadlinesFragment 发挥的就是这个作用:

public class HeadlinesFragment extends ListFragment {    ...    OnHeadlineSelectedListener mHeadlineSelectedListener = null;    /* Must be implemented by host activity */    public interface OnHeadlineSelectedListener {        public void onHeadlineSelected(int index);    }    ...    public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {        mHeadlineSelectedListener = listener;    }}

然后,当用户选择某个标题时,Fragment 便会通知宿主 Activity 指定的侦听器(而不是通知特定硬编码 Activity):

public class HeadlinesFragment extends ListFragment {    ...    @Override    public void onItemClick(AdapterView<?> parent,                            View view, int position, long id) {        if (null != mHeadlineSelectedListener) {            mHeadlineSelectedListener.onHeadlineSelected(position);        }    }    ...}

支持平板电脑和手机指南中将进一步阐述此技巧。

处理屏幕配置变更


如果您要使用不同的 Activity 来实现界面的不同部分,您需要牢记的是,可能需要对某些配置变更(如旋转变化)作出反应,以便保持界面的一致性。

例如,在一台运行 Android 3.0 或更高版本的典型 7 英寸平板电脑上,当平板电脑在纵向模式下运行时,News Reader 示例应用使用单独的 Activity 来显示新闻文章,但在横向模式下则使用双窗格布局。

这意味着当用户处于纵向模式,并且用于查看文章的 Activity 位于屏幕上时,您需要检测屏幕方向已变为横向模式的情况并作出相应的反应:结束该 Activity 并返回主 Activity,以便内容可以显示在双窗格布局中:

public class ArticleActivity extends FragmentActivity {    int mCatIndex, mArtIndex;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mCatIndex = getIntent().getExtras().getInt("catIndex", 0);        mArtIndex = getIntent().getExtras().getInt("artIndex", 0);        // If should be in two-pane mode, finish to return to main activity        if (getResources().getBoolean(R.bool.has_two_panes)) {            finish();            return;        }        ...}

使用ConstraintLayout构建响应式UI


ConstraintLayout允许您创建具有平面视图层次结构的大型复杂布局(不包含嵌套视图组)。类似于RelativeLayout所有视图都是根据兄弟视图和父级布局之间的关系来布局的,但是RelativeLayout与Android Studio的布局编辑器相比它更加灵活,更易于使用。

ConstraintLayout由于布局编辑器的可视化工具直接提供了所有的功能,因为布局API和布局编辑器是专门为对方构建的。因此,您可以ConstraintLayout完全通过拖放而不是编辑XML 来构建布局


ConstraintLayout在与Android 2.3(API级别9)及更高版本兼容的API库中可用。本页面提供了ConstraintLayout在Android Studio 3.0或更高版本中构建布局的指南如果您想了解更多关于布局编辑器的信息,请参阅Android Studio指南以使用布局编辑器构建UI

要查看可以创建的各种布局ConstraintLayout,请查看GitHub上的“ 约束布局示例”项目

约束概述


要定义视图的位置ConstraintLayout,您必须为视图添加至少一个水平和垂直的约束。每个约束表示与另一个视图的连接或对齐,父布局或不可见的准则。每个约束定义沿垂直轴或水平轴的视图位置; 所以每个视图必须对每个轴至少有一个约束,但通常更多是必要的。

将视图拖放到布局编辑器中时,即使没有约束,它也会保留在离开的位置。但是,这只是为了使编辑更容易; 如果视图在设备上运行布局时没有约束,则会在[0,0]位置(左上角)绘制视图。

在图1中,编辑器中的布局看起来不错,但是在视图C上没有垂直约束。当这个布局在设备上绘图时,视图C水平地与视图A的左边和右边对齐,但出现在视图C的顶部因为它没有垂直限制。

图1.编辑器在A下显示视图C,但没有垂直约束

图2.视图C现在被垂直约束在视图A之下

虽然缺少的约束不会导致编译错误,但布局编辑器会将缺少的约束作为工具栏中的错误进行指示。要查看错误和其他警告,请单击显示警告和错误 为了帮助您避免缺少约束,布局编辑器可以使用自动连接为您自动添加约束,并推断约束特征。

将ConstraintLayout添加到您的项目


ConstraintLayout在您的项目中使用,请按照下列步骤操作:

  1. 确保您maven.google.com的模块级build.gradle文件中声明存储库 
    存储库{     maven {         url'https ://maven.google.com' } }    
  2. 在相同的build.gradle文件中添加库作为依赖项
    依赖{     编译'com.android.support.constraint:约束的布局:1.0.2' }
  3. 在工具栏或同步通知中,单击使用Gradle文件同步项目

现在,您已经准备好用您的布局来构建布局ConstraintLayout

转换布局

图3.将布局转换为的菜单ConstraintLayout

要将现有布局转换为约束布局,请按照下列步骤操作:

  1. 在Android Studio中打开布局,然后单击编辑器窗口底部的“ 设计”选项卡。
  2. Component Tree窗口中,右键单击布局,然后单击Convert layout to ConstraintLayout

创建一个新的布局

要启动新的约束布局文件,请按照下列步骤操作:

  1. 在“ 项目”窗口中,单击模块文件夹,然后选择 文件>新建> XML>布局XML
  2. 输入布局文件的名称,然后为根标记输入“android.support.constraint.ConstraintLayout” 
  3. 点击完成

添加一个约束


通过拖动从一个视图启动调色板窗户进入编辑器。在a中添加视图时ConstraintLayout,会在每个角上显示一个包围方框,并在每个角上显示方形大小调整手柄,并在每个边上显示圆形约束手柄。

视频1.视图的左侧限制在父视图的左侧

点击视图来选择它。然后单击并按住其中一个约束句柄,并将该行拖到可用的锚点(另一个视图的边缘,布局的边缘或指南)。当你释放的时候,约束被创建,用两个视图分开的默认边距

创建约束时,请记住以下规则:

  • 每个视图必须至少有两个约束:一个水平和一个垂直。
  • 只能在约束句柄和共享同一平面的锚点之间创建约束。因此,一个视图的垂直平面(左侧和右侧)可以仅限于另一个垂直平面; 基线只能限制到其他基线。
  • 每个约束句柄只能用于一个约束,但是您可以创建多个约束(从不同的视图)到同一个锚点。

视频2.添加一个反对现有的约束

要删除约束,请选择该视图,然后单击约束句柄。或者通过选择视图然后单击删除约束 删除所有约束

如果在视图上添加相反的约束条件,那么约束线会像弹簧一样变得扭曲,以指示相反的力,如视频2所示。当视图大小设置为“固定”或“包装内容”时,效果最明显在这种情况下,视图集中在约束之间。如果您希望视图伸展其大小以满足约束,请将大小切换为“匹配约束” ; 或者如果要保持当前的大小,但移动视图以使其不居中,请调整约束偏差

您可以使用约束来实现不同类型的布局行为,如以下各节所述。

父母的位置

将视图的一侧限制到布局的相应边缘。

在图4中,视图的左侧连接到父布局的左边缘。你可以定义从边缘到边距的距离。

图4.父级的水平约束

订单位置

定义两个视图的外观顺序,垂直或水平。

在图5中,B被约束在A的右边,而C被约束在A的下面。但是,这些约束并不意味着对齐,因此B仍然可以上下移动。

图5.水平和垂直约束

对准

将视图的边缘与另一个视图的同一边缘对齐。

在图6中,B的左侧与A的左侧对齐。如果要对齐视图中心,请在两侧创建一个约束。

您可以通过从约束中向内拖动视图来抵消对齐。例如,图7显示了具有24dp偏移对齐的B. 偏移量由约束视图的边距来定义。

您也可以选择所有要对齐的意见,然后单击对齐  工具栏中选择对齐类型。

图6.一个水平对齐约束

图7.偏移的水平对齐约束

基线对齐

将视图的文本基线与另一个视图的文本基线对齐。

在图8中,B的第一行与A中的文字一致

要创建基线约束,请选择要约束的文本视图,然后单击 显示在视图下方的“ 编辑基线  ”。然后单击文本基线,然后将该行拖到另一个基线。

图8.一个基线对齐约束

约束指南

您可以添加可以限制视图的垂直或水平指南,并且指南对应用程序用户不可见。您可以根据相对于版面的边缘的dp单位或百分比来在布局中定位指南。

要创建指南,请单击 工具栏中的指南 ,然后单击添加垂直指南 或添加水平指南

拖动虚线重新定位它,然后单击指南边缘的圆来切换测量模式。

图9.限制在指南中的视图

制约一个障碍

与指南类似,屏障是您可以限制视图的无形线。除了障碍没有界定自己的位置,相反,屏障位置基于其中包含的视图的位置而移动。当你想将视图约束到一组视图而不是一个特定的视图时,这是非常有用的。

例如,图10显示视图C被限制在屏障的右侧。屏障被设置为视图A和视图B两者的“结束”(或者以从左到右布局的右侧)。因此,屏障根据视图A或视图B的右侧是否是最远对。

要创建屏障,请按照下列步骤操作:

  1. 单击工具栏上的指导 ,然后单击 添加垂直屏障添加水平屏障
  2. 在“ 组件树”窗口中,在屏障内选择所需的视图,并将其拖入屏障组件。
  3. 组件树中选择屏障,打开 属性 窗口,然后设置barrierDirection

现在,您可以从另一个视图创建一个约束到障碍。

你也可以限制屏障视图这样,即使您不知道哪个视图最长或最高,也可以确保屏障中的所有视图始终相互对齐。

您也可以在屏障中包含一个指引,以确保屏障的“最小”位置。

图9.视图C被限制在屏障上,屏障根据视图A和视图B的位置/大小进行移动

调整约束偏差


当您向视图的两侧添加约束(并且同一维的视图大小是“固定”或“包装内容”)时,视图将变为居中于两个约束之间,默认情况下偏差为50%。您可以通过拖动“ 属性”窗口中的偏移滑块或拖动视图来调整偏差,如视频5所示。

如果您希望视图伸展其大小以满足约束,请将大小切换为“匹配约束”

视频5.调整约束偏差

调整视图大小


图10.属性窗口包括用于控制 1分尺寸比, 2删除约束, 3高度/宽度模式中, 4页边距和5个约束偏压。

您可以使用拐角手柄来调整视图大小,但是这会对大小进行硬编码,因此视图将不会针对不同的内容或屏幕大小调整大小。要选择不同的尺寸模式,请单击视图并打开 编辑器右侧的“ 属性” 窗口。

在“ 属性”窗口的顶部附近是视图检查器,其中包含多个布局属性的控件,如图10所示(仅适用于约束布局中的视图)。

您可以通过单击图10中的标注3所示的符号来更改高度和宽度的计算方式。这些符号代表大小模式,如下所示(单击符号以在这些设置之间切换):

  •  修正:在下面的文本框中指定一个特定的维度,或者在编辑器中调整视图的大小。
  •  包装内容:该视图只根据需要进行扩展以适应其内容。
  •  匹配约束:该视图尽可能扩展以满足各方的约束(在考虑视图边际之后)。但是,可以使用以下属性和值修改该行为(只有在将视图宽度设置为与约束匹配时,这些属性才会生效):
    • layout_constraintWidth_default
      • 传播:尽可能扩大观点,以适应双方的制约。这是默认行为。
      • wrap:仅根据需要扩展视图以适应其内容,但仍然允许视图比如果约束要求的视图更小。因此,与使用Wrap Content(上面)不同的是,将宽度设置为 Wrap Content将使宽度始终与内容宽度完全匹配; 而将match约束与 layout_constraintWidth_default设置为换行也允许视图小于内容宽度。
    • layout_constraintWidth_min

      这需要dp维度为视图的最小宽度。

    • layout_constraintWidth_max

      这需要dp视图的最大宽度维度。

    但是,如果给定的维度只有一个约束,则视图将扩展以适合其内容。在高度或宽度上使用此模式也可以 设置大小比例

注意:您不能match_parent 在任何视图中使用ConstraintLayout而是使用“匹配约束”(0dp)。

图11.视图被设置为16:9宽高比。

设置大小作为一个比例

如果至少有一个视图尺寸设置为“匹配约束”(0dp,则可以将视图尺寸设置为16:9等比例为了使比,单击切换宽高比约束(标注1在图10中),然后进入 宽度高度在出现的输入比。

如果宽度和高度都设置为与约束匹配,则可以单击“ 切换长宽比约束”以选择基于另一维度的比例的维度。视图检查器通过用实线连接相应的边缘来指示哪个设置为比率。

例如,如果将两侧设置为“匹配约束”,请单击“ 切换宽高比约束”两次以将宽度设置为高度的比率。现在,整个大小由视图的高度决定(可以用任何方式定义),如图11所示。

调整视图边距


为了确保所有的意见都间隔均匀,单击边距 工具栏中的选择要添加到布局每个视图的默认保证金。您对默认边距所做的任何更改仅适用于您从此之后添加的视图。

您可以通过单击表示每个约束的线上的数字(在图10中,标注4显示底部边距设置为28dp)来控制“ 属性”窗口中每个视图的边距。

图12.工具栏的Margin按钮

该工具提供的所有边距均为8dp,以帮助您将视图与Material Design的8dp方形网格建议对齐 

用链控制线性组


一个链是一组相互连接的视图,具有双向的位置约束。例如,图13显示了两个视图,这两个视图都相互之间有一个约束,从而创建一个水平链。

一个链可以让你用下列样式水平或垂直分配一组视图(如图14所示):

  1. 点差:意见均匀分布(在利润空间计算之后)。这是默认的。
  2. 内部传播:第一个和最后一个视图固定在链条两端的约束上,其余均匀分布。
  3. 加权:当链条被设置为扩散扩散时,可以通过设置一个或多个视图来填充剩余空间以“匹配约束”(0dp)。默认情况下,空间在被设置为“匹配约束”的每个视图之间均匀分布,但是您可以使用layout_constraintHorizontal_weightlayout_constraintVertical_weight 属性为每个视图分配一个重要权重 如果你熟悉layout_weight的 线性布局,这种工作方式相同。所以权值最高的视图获得最多的空间; 具有相同重量的视图获得相同的空间量。
  4. 包装:意见是包装在一起(利润余额后)。然后,您可以通过更改链条的头部视图偏差来调整整个链条的偏差(左/右或上/下)。

链的“头”视图(水平链中最左边的视图和垂直链中最顶端的视图)用XML定义链的风格。但是,您可以通过选择链中的任何视图,然后单击视图下方显示的链式按钮展开展开打包之间进行切换

要快速创建的视图的链,其全部选中,右键单击一个视图,然后选择水平居中垂直居中,分别创建水平或垂直产业链,(见视频4)。

使用链条时需要考虑的其他一些事情:

  • 视图可以是横向和纵向链条的一部分,使得构建灵活的网格布局变得容易。
  • 只有链条的每一端都被约束到同一轴上的另一个对象时,链才能正常工作,如图13所示。
  • 虽然链的方向是垂直或水平的,但是使用一个方向不能使视图在该方向上对齐。因此,请确保包含其他约束条件以实现链中每个视图的正确位置,例如对齐约束

图13.只有两个视图的链

图14.每个链式的例子

视频4.从操作菜单创建链

自动创建约束


将布局中的每个视图添加到每个视图时,您可以将每个视图移动到您想要的位置,然后单击“继续约束” 以自动创建约束。

推断约束扫描布局以确定所有视图的最有效的一组约束。它尽最大努力将观点限制在当前的位置,同时允许灵活性。您可能需要进行一些调整,以确保布局能够响应不同的屏幕尺寸和方向。

Autoconnect是一个独立的功能,可以打开或关闭。打开时,它将自动为每个视图创建两个或更多的约束,因为您将它们添加到布局,但仅在适当时将视图限制为父布局。Autoconnect不会为布局中的其他视图创建约束。