一切的原因是因為自己終于想自定義View干些正事了徒欣。想自己自定義一個banner遇到了的問題
generateDefaultLayoutParams()
想自己定義一個Banner逐样,然后方便使用
在布局里面使用了
mBannerPager = new ViewPager(getContext());
addView(mBannerPager);
然后發(fā)現(xiàn)出現(xiàn)了下面錯誤
java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6942)
at com.zhousaito.bannerdemo.banner.BannerView.onMeasure(BannerView.java:65)
是因為我在測量子View的時候
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
measureChildWithMargins(view, widthMeasureSpec, getPaddingLeft() + getPaddingRight(), heightMeasureSpec, getPaddingTop() + getPaddingBottom());
// measureChildren(widthMeasureSpec, heightMeasureSpec);
}
這個measureChildWithMargins
中源碼如下
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);
}
其中有一行代碼final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
導(dǎo)致了取的時候直接強(qiáng)轉(zhuǎn)MarginLayoutParams
,
自定義View直接添加的View的時候打肝,沒有添加LayoutParams的脂新,就會走默認(rèn)的
public void addView(View child) {
addView(child, -1);
}
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);
}
LayoutParams params = child.getLayoutParams();
如果沒添加LayoutParams就為null,就會從generateDefaultLayoutParams()
中去取值粗梭,所以争便,自定義View的時候,如果要通過measureChildWithMargins
去測量子View断医,就需要重寫generateDefaultLayoutParams()
方法滞乙,然后返回MarginLayoutParams
或者它的實現(xiàn),就可以正常使用了鉴嗤。
generateLayoutParams(AttributeSet attrs)
如果在布局里面這么寫
<com.zhousaito.bannerdemo.banner.BannerView
android:id="@+id/bannerView"
android:layout_width="match_parent"
android:layout_height="200dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.zhousaito.bannerdemo.banner.BannerView>
然后又報同樣的錯了
java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6942)
at com.zhousaito.bannerdemo.banner.BannerView.onMeasure(BannerView.java:65)
這里就想到了斩启,這個布局是在activity中的PhoneWindow對象setContentView把布局渲染上去的,而最終是用了LayoutInflater去解析得到一個一個對象醉锅,然后添加到DecroView中的一個R.id.content的LinearLayout中兔簇。
這么說來,看LayoutInflater源碼發(fā)現(xiàn)
//LayoutInflater源碼rInflate方法中
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
這里可以看到調(diào)用了generateLayoutParams(attrs)
這樣就明白了,重寫一個generateLayoutParams(attrs)
這個方法就可以在LayoutInflate中得到自定義View的LayoutParams
這就可以正常的使用measureChildWithMargins
男韧,同樣的朴摊,這樣自定義ViewGroup中的子類就具有了Layout_margin的這個屬性,否則設(shè)置上去也是無效的此虑,不會在LayoutInflate的時候解析到甚纲,xml中定義就無效了。
得出結(jié)論
在自定義ViewGroup的時候可以實現(xiàn)下面方法
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParamsWrapper(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
if (p instanceof MarginLayoutParams) {
return new LayoutParamsWrapper(((MarginLayoutParams) p));
}
return new LayoutParamsWrapper(p);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParamsWrapper(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
generateLayoutParams(AttributeSet attrs)
:在布局中需要朦前,如果要自己的LayoutParams的時候介杆,建議加上
generateLayoutParams(LayoutParams p)
:一種copy,創(chuàng)建一個新的LayoutParams韭寸,如果自己實現(xiàn)LayoutParams的話就建議重寫一下春哨,可以轉(zhuǎn)化為自己實現(xiàn)的LayoutParams,方便后續(xù)如果是自己ViewGroup的子類照成沒有對應(yīng)的布局參數(shù)的錯誤恩伺,在Layout過程中獲取LayoutParams得到不想要的結(jié)果赴背;
補(bǔ):這個后面我在使用addView的時候,添加ViewGroup.LayoutParams()的時候晶渠,然后會通過重寫的這個方法進(jìn)行轉(zhuǎn)換成當(dāng)前自定義View的LayoutParams()
public void addView(View child, int index, LayoutParams params) {
//...省略
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
//..省略代碼
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
//..省略代碼
}
generateDefaultLayoutParams()
: addView中默認(rèn)沒有傳LayoutParams的時候調(diào)用凰荚。
以上個人理解,如果錯誤請指出褒脯,非常感謝便瑟。