问题
In order to understand Double Taxation on Android, I wrote the following code which is pretty simple. There is a RelativeLayout
with three TextView
s.
<?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 callonMeasure
on each child twice beforeonMeasure
ofMyRelativeLayout
is called? WithMyLinearLayout
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 ofLinearLayout
?
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