美文网首页Android技术知识Android开发Android自定义View
Android自定义ViewGroup:如何理解和利用onMea

Android自定义ViewGroup:如何理解和利用onMea

作者: 瑞神Collection | 来源:发表于2018-08-06 17:28 被阅读11次

    2018-08-06
    在Android自定义开发ViewGroup时 总是避免不了对onMeasure方法的重写
    那对这个方法应该如何理解?如何重写?有什么作用?等疑问接踵而来 这篇文章就来简洁地说明下这两个方法的使用

    onMeasure此方法主要有两个使用目的
    1.为本ViewGroup中所有的子view调用"它们自己的测量方法":View.measure(int,int)
    2.在所有的子view的测量方法调用完成后 理论上所有子view都可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度 这时由此来确定ViewGroup自己的宽度和高度

    现在我们通过阐述这两个目的来间接学习onMeasure的使用

    1.调用"它们自己的测量方法":View.measure(int,int)
    我们要先从子view测量方法开始说起
    这个测量方式要情况而定 一般最基本也最通用的做法是使用Android已经封装好了的测量方法:

    measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec)
    measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed)

    这两个方法都可以用于单个view的测量:

        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (view.getVisibility() != View.GONE) {
                measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
            }
        }
    

    在这段for循环完成后view便有了自己的宽高 可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度了

    注1:
    这两个方法有一定的区别 只有measureChildWithMargins()在测量时会将子view的margin属性考虑进去 在margin会影响子view宽高时(如 子view设置宽度match_parent 并设置了marginLeft为10dp 那么最后获取到的view的测量宽度应该比父控件宽度小10dp)会影响其测量宽高 而measureChild不会 所以有时会造成测量不准的问题

    注2:measureChildWithMargins()中那两个被赋值为0的两个参数 在android说明中说是为了在平行 or 垂直布局中设置固定间隔而用的 在代码中其实是直接加在了margin和padding值里 其实算是当作另一个margin来用 详细请查看源码

    measureChildren(int widthMeasureSpec, int heightMeasureSpec)

    方法源码:

    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);
            }
        }
    }
    

    没错 这个方法其实就是我们之前写的for循环子view进行测量的那部分代码 android早就给了封装方法 在实际开发中 直接使用这个方法基本就可以解决所有的测量问题
    只是它用的是measureChild进行的测量 所以在子view使用了margin的情况下 有可能会影响实际测量值(一般只在子view设置为match_parent时有这种情况 所以也不是很大的问题) 必须在onLayout中考虑到这点

    在调用了以上方法后 经过一系列计算 最终view.measure(int width,int height)会被调用 传入的就是view的测量高度和宽度 具体内容可看源码(实际和ViewGroup的onMeasure类似甚至更简单些 明白了onMeasure的用法 就很容易理解了)


    2.确定ViewGroup自己的宽度和高度
    这个问题要从ViewGroup的测量方法onMeasure(int widthMeasureSpec, int heightMeasureSpec)的参数意义开始说起
    这个方法中的两个形参是两个int类型 但是它们不是单纯的数字 没有单纯的算术意义 而是记录了"测量模式"和测量高/宽度的数字(因为int类型共32位 前2位用来表示模式 后30位用来表示宽度或高度 具体可百度了解)
    android提供了MeasureSpec工具类 方便我们提取测量模式和宽高:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);//获取宽度
        int height = MeasureSpec.getSize(heightMeasureSpec);//获取高度
    
        int modeW = MeasureSpec.getMode(widthMeasureSpec);//获取宽度测量模式
        int modeH = MeasureSpec.getMode(heightMeasureSpec);//获取高度测量模式
    
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (view.getVisibility() != View.GONE) {
                measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
            }
        }
    
        switch (modeW) {//判断宽度模式以演示
            case MeasureSpec.AT_MOST://当本控件在xml中宽度上写入 wrap_content时为此模式 这时返回的width一般为0
            case MeasureSpec.EXACTLY://当本控件在xml中宽度上写入实际值时为此模式 如20dp match_parent(与父布局一样的数值) 这里返回的width也就是xml中写明的数值
            case MeasureSpec.UNSPECIFIED://只有在ListView或类似的控件中会出现 表示不关心大小 这时返回的width一般为0
                break;
        }}
    

    关键点在于如何去理解不同的测量模式给我们对确定ViewGroup宽高的影响
    首先说明 确定了ViewGroup的宽高后 应该使用
    setMeasuredDimension(int width, int height);
    进行赋值 传入的两个Int是确定的数值

    如何使用测量模式:
    如 模式为 MeasureSpec.EXACTLY:
    此时情况最简单 因为此模式下表示开发者使用XML传入了一个固定的值 我们直接设置为ViewGroup的宽高就可以了
    setMeasuredDimension(width, height);

    如模式为 MeasureSpec.AT_MOST:
    此时情况为 XML要求宽高为wrap_content 也就是包裹内容 因为我们已经通过for测量了所有子view的宽高
    所以我们可以这样:

        int maxHeight = 0;
        int maxWidth = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            maxHeight = view.getMeasuredHeight() > maxHeight ? view.getMeasuredHeight() : maxHeight;
            maxWidth = view.getMeasuredWidth() > maxWidth ? view.getMeasuredWidth() : maxWidth;
        }
        setMeasuredDimension(maxWidth,maxHeight);
    

    也可以这样:

        int maxHeight = 0;
        int maxWdith = 500;
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            maxHeight += view.getMeasuredHeight();
        }
        setMeasuredDimension(maxWidth,maxHeight);
    

    上述前者部分是view没有排布规则(如没有任何其它设置的FrameLayout) 我们取所以子view里最宽和最高的值作为我们布局的宽高 很符合"包裹内容"的定义

    后者部分是view有垂直排布规则(如LinearLayout) 先假设宽度一定 因为子view垂直排布 所以 我们viewGroup的高度应该是所以子view的高度的和

    至于MeasureSpec.UNSPECIFIED模式
    一般会出现这个模式的情况是作为listView的item 这时应该开发者根据实际情况处理(一般直接按包裹内容方式处理)

    到这里应该就可以看出 测量方法的重要性 不但是设置子view宽高的方法(子view在调用了测量方法后才会有测量宽高 之前会一直为0 没有经过这个方法也就无法完成view的绘制和排布) 同时也是为了确保控件在"自定义排布功能"的情况下确定自身宽高的方法

    相关文章

      网友评论

        本文标题:Android自定义ViewGroup:如何理解和利用onMea

        本文链接:https://www.haomeiwen.com/subject/vemwvftx.html