一切的原因是因为自己终于想自定义View干些正事了。想自己自定义一个banner遇到了的问题
generateDefaultLayoutParams()
想自己定义一个Banner,然后方便使用
在布局里面使用了
mBannerPager = new ViewPager(getContext());
addView(mBannerPager);
然后发现出现了下面错误
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();
导致了取的时候直接强转MarginLayoutParams
,
自定义View直接添加的View的时候,没有添加LayoutParams的,就会走默认的
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
或者它的实现,就可以正常使用了。
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源码发现
//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);
这里可以看到调用了generateLayoutParams(attrs)
这样就明白了,重写一个generateLayoutParams(attrs)
这个方法就可以在LayoutInflate中得到自定义View的LayoutParams
这就可以正常的使用measureChildWithMargins
,同样的,这样自定义ViewGroup中的子类就具有了Layout_margin的这个属性,否则设置上去也是无效的,不会在LayoutInflate的时候解析到,xml中定义就无效了。
得出结论
在自定义ViewGroup的时候可以实现下面方法
@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,创建一个新的LayoutParams,如果自己实现LayoutParams的话就建议重写一下,可以转化为自己实现的LayoutParams,方便后续如果是自己ViewGroup的子类照成没有对应的布局参数的错误,在Layout过程中获取LayoutParams得到不想要的结果;
补:这个后面我在使用addView的时候,添加ViewGroup.LayoutParams()的时候,然后会通过重写的这个方法进行转换成当前自定义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中默认没有传LayoutParams的时候调用。
以上个人理解,如果错误请指出,非常感谢。