dementrock
dementrock

Reputation: 937

Asymmetric RelativeLayout behavior in Android

The following two layout files produce different results:

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

Result: enter image description here

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

Result: enter image description here

Both layouts are trying to describe the same constraints. Namely, the red rectangle should touch the left edge of the parent, the blue rectangle should touch the right edge of the parent, and they should appear next to each other horizontally. The only difference is whether you specify the "next to" constraint on the red rectangle or the blue rectangle. I figured out the reason which has to do with the measure resolution order generated by forming a dependency graph of the constraints, but I only figured it out through reading RelativeLayout's source code, and I couldn't find any documentation / notes regarding this behavior. Since RelativeLayout must be a commonly used layout component, is there a more intuitive explanation for this behavior, or is there some part of documentation that I am missing?

Upvotes: 2

Views: 381

Answers (2)

Nikolay Nikiforchuk
Nikolay Nikiforchuk

Reputation: 2048

All this parameters defined in: 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;
        }
    }
}

The view left and right edges ( childParams.mLeft, childParams.mRight) calculations based on anchor view parameters (anchorParams). From this code childParams.mRight edge of the view defined by LEFT_OF (android:layout_toLeftOf) can be recalculated by ALIGN_RIGHT (android:layout_alignRight) or ALIGN_PARENT_RIGHT (android:layout_alignParentRight). Here is explanation why 0-width red view become more than 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"/>

Right edge of this view defined by LEFT_OF:

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

In this case anchor view is:

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

the left edge of this view 60dp from the right side of the screen margings not defined => childParams.mRight = screen_width - 60dp

Left edge of this view defined by ALIGN_PARENT_LEFT:

childParams.mLeft = mPaddingLeft + childParams.leftMargin;

the left edge of this view left edge of anchor view is 0 because android:layout_alignParentLeft="true" and margins not defined => childParams.mLeft = 0

the same calculation can be done for the second example: childParams.mRight = screen_width childParams.mLeft = 0

Upvotes: 1

ekcr1
ekcr1

Reputation: 4514

Although both seem to describe the same constraints, they actually don't. The difference is that one says, red must sit next to blue, while the other says blue must sit next to red. One means that where ever red goes blue must follow, the other says, where ever blue goes red must follow, and they both want to go to different places.

In the first instance, red box depends on the blue box, so the blue box gets constructed first. The blue box has a width of 60dp, so a 60dp blue box is constructed first and aligned right. Then comes the red box, which has a constraint to sit next to the blue box. Width 0 is ignore because it needs to sit next to 60dp blue and align left.

In the second instance, blue box depends on the red box, so the red box gets constructed first. The red box says it wants 0dp and align left, so it can't be seen. Then comes the blue box which needs to sit next to invisible red and align right, thus occupying the entire space, its width ignored.

Hope this makes sense :)

Upvotes: 4

Related Questions