簡(jiǎn)書(shū) 編程之樂(lè)
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處!
復(fù)習(xí)自定義View過(guò)程中我發(fā)現(xiàn)幾乎 很多人 都犯了一個(gè)細(xì)節(jié)上的錯(cuò)誤,就是ViewGroup中的子View 不支持margin。
注: 關(guān)于自定義View的基礎(chǔ)教程 請(qǐng)參閱其他博客
先總結(jié)兩點(diǎn)
- 自定義View在onDraw里面需要處理padding的影響,widthMeasureSpec和heightMeasureSpec是包含padding大小的。
- 子View的margin屬性是由ViewGroup處理的,ViewGroup在onMeasure和onLayout時(shí)一定要考慮 ViewGroup自己的padding和子View的margin的影響。
你可能遇到過(guò)下面這樣的錯(cuò)誤。
java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
下面我們分析為什么會(huì)遇到這種錯(cuò)誤以及解決方法。
你可能見(jiàn)過(guò)很多人在自定義ViewGroup的
onMeasure()中使用
measureChildren(widthMeasureSpec, heightMeasureSpec); 來(lái)測(cè)量所有子View的尺寸。
ViewGroup.measureChildren的源碼如下:
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);
}
}
measureChild是不是不太合適呢,查閱了FrameLayout和LinearLayout等都沒(méi)有用過(guò)這個(gè)measureChildren呢,幾乎全部都重寫(xiě)了,我們的自定義ViewGroup的measureChildren是不是應(yīng)該是改成下面這樣才對(duì)。
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) {
// ******************* 注意這里 ********************
measureChildWithMargins(child, widthMeasureSpec, heightMeasureSpec);
}
}
你應(yīng)該看到了區(qū)別,measureChild和measureChildWithMargins區(qū)別就是
測(cè)量child尺寸時(shí),保證child的 最大可用尺寸,感覺(jué)這個(gè)with前綴起的不太好。
- measureChild減去了 ViewGroup的padding 保證child最大可用空間
- measureChildWithMargins減去了ViewGroup的padding和子View的margin 保證child最大可用空間
至于 measureChild和measureChildWithMargins中是如何**生成child的MeasureSpec,并最終調(diào)用child.measure() -- > child.onMeasure()的,這里就不貼源碼了。
總結(jié) : ViewGroup中測(cè)量child一定要用measureChildWithMargins而不是measureChild
使用measureChildWithMargins后卻產(chǎn)生異常
終于改成measureChildWithMargins了,卻突然產(chǎn)生了異常,這是為什么?
找到異常產(chǎn)生的位置,追蹤到ViewGroup.addView()方法,源碼如下:
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
// **************** 注意這里 ****************
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
異常信息是 ClassCastException
cannot be cast to android.view.ViewGroup$MarginLayoutParams
而addView中,如果child.getLayoutParams();獲取不到,則默認(rèn)生成一個(gè)
generateDefaultLayoutParams();
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
這個(gè)默認(rèn)生成的肯定不能強(qiáng)制轉(zhuǎn)換為MarginLayoutParams了。
再來(lái)看addView中的其他方法
private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) {
if (!checkLayoutParams(params)) {
// **************** 注意這里 ****************
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
................
................
}
里面還有檢測(cè)這個(gè)child的LayoutParams 是不是為空的,干脆全部重寫(xiě)得了。
在你的自定義ViewGroup中加入如下代碼即可令 子View 的margin生效。
public class MyViewGroup extends ViewGroup {
// ..................... 其他代碼省略 .....................
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyLayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
return new MyLayoutParams(lp);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
public static class MyLayoutParams extends MarginLayoutParams {
public MyLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
public MyLayoutParams(LayoutParams lp) {
super(lp);
}
}
}
另外在ViewGroup.onLayout()時(shí)中千萬(wàn)別忘記根據(jù) ViewGroup的padding和子View的margin 靈活給子View布局。
關(guān)于自定義View和自定義ViewGroup的其他細(xì)節(jié)就參閱其他文章吧。最好參考Android系統(tǒng)自帶控件的 源碼,畢竟這是最準(zhǔn)確無(wú)誤的,閱讀他們文章加上自己的見(jiàn)解和懷疑,大牛也會(huì)有犯錯(cuò)的時(shí)候。