Double Taxation when using a RelativeLayout on Android

送分小仙女□ 提交于 2020-08-06 05:39:08

问题


In order to understand Double Taxation on Android, I wrote the following code which is pretty simple. There is a RelativeLayout with three TextViews.

<?xml version="1.0" encoding="utf-8"?>
<ru.maksim.sample_app.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="ru.maksim.sample_app.MainActivity">

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fca"
        android:tag="text1"
        android:text="Text 1" />

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text1"
        android:background="#acf"
        android:tag="text2"
        android:text="Text 2" />

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text1"
        android:layout_toRightOf="@id/text2"
        android:background="#fac"
        android:tag="text3"
        android:text="text 3" />

</ru.maksim.sample_app.MyRelativeLayout>

MyTextView

public class MyTextView extends android.support.v7.widget.AppCompatTextView {

    private static final String TAG = "MyTextView";

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context,
                      @Nullable AttributeSet attrs
    ) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
        Log.d(TAG,
              "onMeasure, "
                      + getTag()
                      + " widthMeasureSpec=" + MeasureSpecMap.getName(widthMode)
                      + " heightMeasureSpec=" + MeasureSpecMap.getName(heightMode)
        );
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG, getTag() + " onLayout");
    }
}

MyRelativeLayout

public class MyRelativeLayout extends RelativeLayout {

    public static final String TAG = "MyRelativeLayout";

    public MyRelativeLayout(Context context) {
        super(context);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
        Log.d(TAG,
              "onMeasure, "
                      + getTag()
                      + " widthMeasureSpec=" + MeasureSpecMap.getName(widthMode)
                      + " heightMeasureSpec=" + MeasureSpecMap.getName(heightMode)
        );
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG, getTag() + " onLayout");
    }
}

Logcat:

09-11 19:25:40.077 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text2 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text3 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text3 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text2 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.078 7732-7732/ru.maksim.sample_app D/MyRelativeLayout: onMeasure, null widthMeasureSpec=EXACTLY heightMeasureSpec=EXACTLY

                                                                      [ 09-11 19:25:40.098  7732: 7748 D/         ]
                                                                      HostConnection::get() New Host Connection established 0xa0a8fbc0, tid 7748
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text2 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text3 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text3 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: onMeasure, text2 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyRelativeLayout: onMeasure, null widthMeasureSpec=EXACTLY heightMeasureSpec=EXACTLY
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: text1 onLayout
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: text2 onLayout
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyTextView: text3 onLayout
09-11 19:25:40.132 7732-7732/ru.maksim.sample_app D/MyRelativeLayout: null onLayout

Now let's replace MyRelativeLayout with a child of LinearLayoiut called MyLinearLayout:

<?xml version="1.0" encoding="utf-8"?>
<ru.maksim.sample_app.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="ru.maksim.sample_app.MainActivity">

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fca"
        android:tag="text1"
        android:text="Text 1" />

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#acf"
        android:tag="text2"
        android:text="Text 2" />

    <ru.maksim.sample_app.MyTextView
        android:id="@+id/text3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#fac"
        android:tag="text3"
        android:text="text 3" />

</ru.maksim.sample_app.MyLinearLayout>

MyLinearLayout

public class MyLinearLayout extends LinearLayout {

    public static final String TAG = "MyLinearLayout";

    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
        Log.d(TAG,
              "onMeasure, "
                      + getTag()
                      + " widthMeasureSpec=" + MeasureSpecMap.getName(widthMode)
                      + " heightMeasureSpec=" + MeasureSpecMap.getName(heightMode)
        );
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG, getTag() + " onLayout");
    }
}

Here is what I see in logcat now:

09-11 19:50:57.974 2781-2781/? D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:50:57.974 2781-2781/? D/MyTextView: onMeasure, text2 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:50:57.974 2781-2781/? D/MyTextView: onMeasure, text3 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:50:57.974 2781-2781/? D/MyLinearLayout: onMeasure, null widthMeasureSpec=EXACTLY heightMeasureSpec=EXACTLY

                                                 [ 09-11 19:50:58.004  2781: 2817 D/         ]
                                                 HostConnection::get() New Host Connection established 0xa5ec1940, tid 2817
09-11 19:50:58.017 2781-2781/? D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:50:58.017 2781-2781/? D/MyTextView: onMeasure, text2 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
09-11 19:50:58.017 2781-2781/? D/MyTextView: onMeasure, text3 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
09-11 19:50:58.017 2781-2781/? D/MyLinearLayout: onMeasure, null widthMeasureSpec=EXACTLY heightMeasureSpec=EXACTLY
09-11 19:50:58.017 2781-2781/? D/MyTextView: text1 onLayout
09-11 19:50:58.017 2781-2781/? D/MyTextView: text2 onLayout
09-11 19:50:58.017 2781-2781/? D/MyTextView: text3 onLayout
09-11 19:50:58.017 2781-2781/? D/MyLinearLayout: null onLayout

MeasureSpecMap used in both examples above just contains the following Map:

public class MeasureSpecMap {

    private static final Map<Integer, String> MAP = new HashMap<>();

    static {
        MAP.put(View.MeasureSpec.AT_MOST, "AT_MOST");
        MAP.put(View.MeasureSpec.EXACTLY, "EXACTLY");
        MAP.put(View.MeasureSpec.UNSPECIFIED, "UNSPECIFIED");
    }

    private MeasureSpecMap() {

    }

    public static String getName(int mode) {
        return MAP.get(mode);
    }

}

Question 1.

When using MyRelativeLayout, why does the system need to call onMeasure on each child twice before onMeasure of MyRelativeLayout is called? With MyLinearLayout each child in my example is measured once as you can see in the log output above.

Question 2.

Here is something else from the Double taxation section that I don't understand:

when you use the RelativeLayout container, which allows you to position View objects with respect to the positions of other View objects, the framework performs the following actions:

Executes a layout-and-measure pass, during which the framework calculates each child object’s position and size, based on each child’s request. Uses this data, also taking object weights into account, to figure out the proper position of correlated views.

Uses this data, also taking object weights into account, to figure out the proper position of correlated views.

But wait... Aren't they talking about android:layout_weight which is a feature of LinearLayout?


The code from above is also available on GitHub:

The approach with MyLinearLayout

The approach with MyRelativeLayout


回答1:


Question 1.

When using MyRelativeLayout, why does the system need to call onMeasure on each child twice before onMeasure of MyRelativeLayout is called? With MyLinearLayout each child in my example is measured once as you can see in the log output above.

In (overly) simple terms, it comes down to the fact that the size and position of one view can affect the size and position of another view.

Consider text3: its width depends not only on the length of the text it is holding, but also on the width of text2; if text2 consumes 80% of the screen, then text3 will only get (at most) 20% of the screen.

So the system does a first measure pass to figure out the "constraints" that views are going to place on each other, and then does a second measure pass to figure out the final values to use.

Take a look at your log output (some text omitted for brevity):

D/MyTextView: onMeasure, text2 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
D/MyTextView: onMeasure, text3 widthMeasureSpec=AT_MOST heightMeasureSpec=AT_MOST
D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST

D/MyTextView: onMeasure, text1 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
D/MyTextView: onMeasure, text3 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST
D/MyTextView: onMeasure, text2 widthMeasureSpec=EXACTLY heightMeasureSpec=AT_MOST

See how the first time text2 is measured, its widthMeasureSpec is of mode AT_MOST while the second time it is of mode EXACTLY? The first pass is giving text2 the opportunity to consume up to 100% of the screen width, but the second pass is limiting it to its actual necessary size.

Whereas for a vertical LinearLayout, the system can get everything it needs to know in a single measurement pass. text1 is allowed up to the full window height, text2 is allowed up to the full window height minus text1's height, and so on.

Question 2.

...

But wait... Aren't they talking about android:layout_weight which is a feature of LinearLayout?

I don't understand this part either. I'm inclined to believe CommonsWare's speculation that it's just a documentation bug.



来源:https://stackoverflow.com/questions/46164222/double-taxation-when-using-a-relativelayout-on-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!