??樓主最近在復(fù)習(xí)自定義View戈二,在復(fù)習(xí)到自定義ViewGroup這個(gè)知識(shí)點(diǎn)時(shí),發(fā)現(xiàn)了一個(gè)問(wèn)題--就是我們之前的定義ViewGroup在考慮Margin屬性可能有問(wèn)題喳资。本文在解決該問(wèn)題給出建議性的意見(jiàn)觉吭,但是不一定是正確的,如果有錯(cuò)誤或者不當(dāng)?shù)牡胤狡偷耍M刚?br>
??本文參考文章:
??1.Android 手把手教您自定義ViewGroup(一)
??2.你的自定義View是否真的支持Margin
1.提出問(wèn)題
??這里我舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)鲜滩,假設(shè)我們需要定義一個(gè)ViewGroup放置一個(gè)子View,同時(shí)這個(gè)子View支持Padding和Margin屬性节值。
??這里我先貼出一個(gè)常規(guī)的寫法:
public class CustomViewGroup02 extends ViewGroup {
public CustomViewGroup02(Context context) {
super(context);
}
public CustomViewGroup02(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomViewGroup02(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//這里假設(shè)只有一個(gè)子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
View view = getChildAt(0);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int width = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getPaddingLeft() + getPaddingRight();
int height = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + getPaddingTop() + getPaddingBottom();
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize:width, (heightMode == MeasureSpec.EXACTLY) ? heightSize:height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View view = getChildAt(0);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int left = getPaddingLeft() + lp.leftMargin;
int top = getPaddingTop() + lp.topMargin;
view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
??在代碼中徙硅,我們考慮到了padding屬性和Margin屬性,同時(shí)我們可以在xml代碼測(cè)試一下效果
??xml中這樣寫:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.apple.android_demo08.CustomViewGroup02
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:background="#FFDAB9" />
</com.example.apple.android_demo08.CustomViewGroup02>
</android.support.constraint.ConstraintLayout>
??模擬器上展示的效果圖:
??看上去似乎是沒(méi)有問(wèn)題的搞疗,我們給TextView設(shè)置了marginLeft為20dp嗓蘑,在手機(jī)上也能正常顯示出來(lái)margin屬性。但是匿乃,如果TextView的layout_width設(shè)置為match_parent會(huì)怎么樣呢桩皿?
??xml代碼:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.apple.android_demo08.CustomViewGroup02
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:background="#FFDAB9" />
</com.example.apple.android_demo08.CustomViewGroup02>
</android.support.constraint.ConstraintLayout>
??此時(shí)我們?cè)贏ndroid studio右側(cè)的預(yù)覽界面來(lái)看看此時(shí)效果:
??我們發(fā)現(xiàn)雖然TextVeiw向左移動(dòng)了20dp,但是我們發(fā)現(xiàn)了一個(gè)問(wèn)題幢炸,就是TextView右側(cè)超出了屏幕泄隔,也就是說(shuō),TextView的layout_marginLeft 屬性根本沒(méi)有影響到它的width宛徊,只是單純將TextView向右移動(dòng)了20dp梅尤。這個(gè)是有問(wèn)題的柜思,我們?nèi)タ纯聪到y(tǒng)的LinearLayout布局,margin屬性會(huì)影響View的寬和高的巷燥。從而得知赡盘,我們這里支持的Margin屬性是假的!那怎么才能真正的支持Margin屬性呢缰揪?
2.解決問(wèn)題
??要想解決問(wèn)題陨享,必須先知道問(wèn)題出現(xiàn)在哪里。這個(gè)問(wèn)題就出現(xiàn)在onMeasure方法中measureChildren方法钝腺。
??我們先來(lái)看看measureChildren方法的源碼:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
??這個(gè)方法表達(dá)的意思非常簡(jiǎn)單抛姑,就是循環(huán)測(cè)量每個(gè)子View。然后我們?cè)賮?lái)看看measureChild方法:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
??在measureChild方法里面艳狐,先利用父布局的XXXXMeasureSpec定硝、padding值和子View向父布局申請(qǐng)的大小來(lái)生成子View的寬和高。這里我們就看出問(wèn)題了毫目,我們發(fā)現(xiàn)系統(tǒng)在測(cè)量子View的width和height時(shí)蔬啡,只是考慮了padding的影響,沒(méi)有考慮Margin對(duì)View的width和height的影響镀虐。
??看到這里箱蟆,我們明白了,為什么之前我們給TextView設(shè)置了marginLeft刮便,同時(shí)設(shè)置TextView的layout_width為match_parent時(shí)空猜,TextView只是單純的向右移動(dòng)了,而沒(méi)有調(diào)整TextView的大小恨旱。因?yàn)槲覀兺ㄟ^(guò)measureChild方法來(lái)測(cè)量每個(gè)子View是不會(huì)考慮Margin屬性對(duì)View的大小的影響辈毯。
??知道的問(wèn)題所在,解決問(wèn)題就非常的容易搜贤。解決的問(wèn)題的辦法就是重寫measureChildren方法谆沃,在測(cè)量每個(gè)View時(shí),考慮到margin的影響入客。其實(shí)在ViewGroup還有一個(gè)方法那就是measureChildWidthMargins方法管毙,這個(gè)方法測(cè)量每個(gè)View時(shí),考慮到了每個(gè)View的margin屬性的影響桌硫。我們來(lái)看看measureChildWidthMargins方法的源代碼:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
??我們發(fā)現(xiàn)在這個(gè)方法里面夭咬,將Margin屬性的影響也考慮到的。那么我們就來(lái)重寫measureChildren方法:
@Override
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
if (view != null && view.getVisibility() != GONE){
measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
}
??在這個(gè)重寫的代碼中铆隘,我們需要主要兩點(diǎn):
??1.在原來(lái)的measureChildren方法的if判斷條件是:(child.mViewFlags & VISIBILITY_MASK) != GONE卓舵,而我們這里是:view != null && view.getVisibility() != GONE。我們這里的依據(jù)是LinearLayout膀钠,系統(tǒng)的LinearLayout也重寫了measureChildren方法的掏湾,它的判斷條件就是:view != null && view.getVisibility() != GONE裹虫。
??2.measureChildrenWithMargins方法多出兩個(gè)參數(shù),分別是:widthUsed融击,heightUsed筑公,這里傳入的是兩個(gè)0,這里的依據(jù)還是LinearLayout尊浪,LinearLayout調(diào)用measureChildrenWithMargins傳入就是兩個(gè)0匣屡。
??重寫之后,我們來(lái)看看之前的match_parent的情況(記得Rebuild一下工程):
??這下就變得正常得多了拇涤!