你的自定义View是否真的支持Margin

简书 编程之乐
转载请注明原创出处!

复习自定义View过程中我发现几乎 很多人 都犯了一个细节上的错误,就是ViewGroup中的子View 不支持margin。

注: 关于自定义View的基础教程 请参阅其他博客

先总结两点

  1. 自定义View在onDraw里面需要处理padding的影响,widthMeasureSpec和heightMeasureSpec是包含padding大小的。
  2. 子View的margin属性是由ViewGroup处理的,ViewGroup在onMeasure和onLayout时一定要考虑 ViewGroup自己的padding和子View的margin的影响。

你可能遇到过下面这样的错误。

java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams

下面我们分析为什么会遇到这种错误以及解决方法。

你可能见过很多人在自定义ViewGroup的
onMeasure()中使用
measureChildren(widthMeasureSpec, heightMeasureSpec); 来测量所有子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等都没有用过这个measureChildren呢,几乎全部都重写了,我们的自定义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) {
        // ******************* 注意这里 ********************
        measureChildWithMargins(child, widthMeasureSpec, heightMeasureSpec);
    }
}

你应该看到了区别,measureChild和measureChildWithMargins区别就是
测量child尺寸时,保证child的 最大可用尺寸,感觉这个with前缀起的不太好。

  1. measureChild减去了 ViewGroup的padding 保证child最大可用空间
  2. measureChildWithMargins减去了ViewGroup的padding子View的margin 保证child最大可用空间

至于 measureChild和measureChildWithMargins中是如何**生成child的MeasureSpec,并最终调用child.measure() -- > child.onMeasure()的,这里就不贴源码了。

总结 : ViewGroup中测量child一定要用measureChildWithMargins而不是measureChild

使用measureChildWithMargins后却产生异常
终于改成measureChildWithMargins了,却突然产生了异常,这是为什么?
找到异常产生的位置,追踪到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();获取不到,则默认生成一个
generateDefaultLayoutParams();

protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

这个默认生成的肯定不能强制转换为MarginLayoutParams了。

再来看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);
        ................
        ................
}

里面还有检测这个child的LayoutParams 是不是为空的,干脆全部重写得了。

在你的自定义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()时中千万别忘记根据 ViewGroup的padding和子View的margin 灵活给子View布局。

关于自定义View和自定义ViewGroup的其他细节就参阅其他文章吧。最好参考Android系统自带控件的 源码,毕竟这是最准确无误的,阅读他们文章加上自己的见解和怀疑,大牛也会有犯错的时候。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容