Android 中的非对称 RelativeLayout 行为

Asymmetric RelativeLayout behavior in Android

以下两个布局文件产生不同的结果:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:gravity="center">
  <RelativeLayout
    android:layout_width="match_parent" 
    android:layout_height="wrap_content"
    android:gravity="center">
    <View
      android:id="@+id/box"
      android:background="#ff0000"
      android:layout_width="0dp"
      android:layout_height="30dp"
      android:layout_alignParentLeft="true"
      android:layout_toLeftOf="@+id/next_box" />
    <View
      android:id="@+id/next_box"
      android:background="#0000ff"
      android:layout_width="60dp"
      android:layout_alignParentRight="true"
      android:layout_height="30dp"
      />
  </RelativeLayout>
</LinearLayout>

结果:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:gravity="center">
  <RelativeLayout
    android:layout_width="match_parent" 
    android:layout_height="wrap_content"
    android:gravity="center">
    <View
      android:id="@+id/box"
      android:background="#ff0000"
      android:layout_width="0dp"
      android:layout_height="30dp"
      android:layout_alignParentLeft="true"
       />
    <View
      android:id="@+id/next_box"
      android:background="#0000ff"
      android:layout_width="60dp"
      android:layout_alignParentRight="true"
      android:layout_height="30dp"
      android:layout_toRightOf="@+id/box"
      />
  </RelativeLayout>
</LinearLayout>

结果:

两种布局都试图描述相同的约束。也就是说,红色矩形应该接触父级的左边缘,蓝色矩形应该接触父级的右边缘,并且它们应该水平相邻出现。唯一的区别是您是在红色矩形还是蓝色矩形上指定 "next to" 约束。我弄清楚了与通过形成约束的依赖关系图生成的测量解析顺序有关的原因,但我只是通过阅读 RelativeLayout 的源代码才弄清楚,我找不到任何关于此行为的文档/注释。由于 RelativeLayout 必须是一个常用的布局组件,对于这种行为是否有更直观的解释,或者我缺少某些文档?

虽然两者似乎描述了相同的约束,但实际上并非如此。不同的是,一个说红色必须坐在蓝色旁边,而另一个说蓝色必须坐在红色旁边。一个是红到哪里就蓝到哪里去,一个是蓝到哪里红到哪里就到哪里,两个都想去不同的地方。

在第一个例子中,红框依赖于蓝框,所以先构建蓝框。蓝框的宽度为60dp,所以先构造一个60dp的蓝框,右对齐。然后是红色框,它有一个位于蓝色框旁边的约束。宽度 0 被忽略,因为它需要坐在 60dp 蓝色旁边并左对齐。

第二种情况,蓝框依赖于红框,所以红框先构建。红框说要0dp,左对齐,所以看不到。然后是蓝色框,它需要坐在不可见的红色旁边并右对齐,从而占据整个 space,其宽度被忽略。

希望这是有道理的:)

所有这些参数定义在: android.widget.RelativeLayout

private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
    RelativeLayout.LayoutParams anchorParams;

    // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
    // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
    // wants to the right
    // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
    // wants to the left
    // left=10, right=20 means the left and right ends are both fixed
    childParams.mLeft = VALUE_NOT_SET;
    childParams.mRight = VALUE_NOT_SET;

    anchorParams = getRelatedViewParams(rules, LEFT_OF);
    if (anchorParams != null) {
        childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
                childParams.rightMargin);
    } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
        if (myWidth >= 0) {
            childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
        }
    }

    anchorParams = getRelatedViewParams(rules, RIGHT_OF);
    if (anchorParams != null) {
        childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
                childParams.leftMargin);
    } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
        childParams.mLeft = mPaddingLeft + childParams.leftMargin;
    }

    anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
    if (anchorParams != null) {
        childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
    } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
        childParams.mLeft = mPaddingLeft + childParams.leftMargin;
    }

    anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
    if (anchorParams != null) {
        childParams.mRight = anchorParams.mRight - childParams.rightMargin;
    } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
        if (myWidth >= 0) {
            childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
        }
    }

    if (0 != rules[ALIGN_PARENT_LEFT]) {
        childParams.mLeft = mPaddingLeft + childParams.leftMargin;
    }

    if (0 != rules[ALIGN_PARENT_RIGHT]) {
        if (myWidth >= 0) {
            childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
        }
    }
}

视图左右边缘(childParams.mLeft、childParams.mRight)的计算基于锚点视图参数(anchorParams)。从这段代码 childParams.mRight LEFT_OF (android:layout_toLeftOf) 定义的视图的边缘可以通过 ALIGN_RIGHT (android:layout_alignRight) 或 ALIGN_PARENT_RIGHT (android:layout_alignParentRight)。这是为什么 0 宽度的红色视图变得大于 0 的解释。

    <View
        android:id="@+id/box"
        android:background="#ff0000"
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:layout_alignParentLeft="true"
        android:layout_toLeftOf="@+id/next_box"/>

此视图的右边缘由 LEFT_OF 定义:

childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
                    childParams.rightMargin);

在这种情况下锚视图是:

    <View
        android:id="@+id/next_box"
        android:background="#0000ff"
        android:layout_width="60dp"
        android:layout_alignParentRight="true"
        android:layout_height="30dp"
        />

此视图的左边缘距屏幕右侧 60dp 未定义边距 => childParams.mRight = screen_width - 60dp

此视图的左边缘由 ALIGN_PARENT_LEFT:

定义
childParams.mLeft = mPaddingLeft + childParams.leftMargin;

此视图的左边缘锚视图的左边缘为 0,因为 android:layout_alignParentLeft="true" 且边距未定义 => childParams.mLeft = 0

第二个例子也可以做同样的计算: childParams.mRight = screen_width childParams.mLeft = 0