自定义ViewGroup中的几个方法记载

以下记载为在自定义ViewGroup,并向其中放入控件时的方法的理解,后期在能力提升上来后,会重新修改记录;

一、onMeasure

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
1.1

翻译View中父类对该方法的注释是:

  • 测量视图及其内容,以确定测量的宽度和测量的高度,这个方法应该由子类覆盖,以提供对其内容的精确和高效的度量;
  • 重写此方法时,必须使用setMeasuredDimension(int,int)来存储此视图的测量宽度和高度,如果不这样做,将触发lllegalStateException异常。也可以直接调用父类的onMeasure方法;
  • 测量的基类默认为背景大小,出入度量允许较大的大小,子类应该覆盖以提供更好的内容度量;
  • 如果这个方法被重写,那么子类的责任就是确保被测量的高度和宽度至少是视图的最小高度和宽度;

上面说这么多,我理解就是:自定义时,子类可复写此方法,并测量好控件的宽高,最终调用setMeasuredDimension(int width,int height)方法给定控件的宽高
这个方法最终的目的就是测量控件的宽高;

1.2

在解释这两个参数前,需要先说一个类MeasureSpec:封装了从父级传递到子级的布局需求,每一项测量都表示对宽度和高度的要求,MeasureSpec由一个尺寸和一个模式组成;
它有三种模式:

MeasureSpec的三种模式 解释
MeasureSpec.UNSPECIFLED 他的父控件没有给他任何约束
他可以是他想要的任意大小尺寸空间
MeasureSpec.EXACTLY 他的父控件已经确定了约束尺寸
不管该控件想要多大,都会赋予该确定的尺寸
布局属性(MATCH_PARENT/固定值
MeasureSpec.AT_MOST 子元素的大小可以达到指定的大小
布局常用(wrap_content)

获取模式:getMode(int)
获取尺寸:getSize(int)

1.3

经过1.2的解释,现在我们再来看传递进来的两个参数:

  • 参数解释

int widthMeasureSpec : 父控件给当前控件的水平宽度和约束条件
int heightMeasureSpec: 父控件给当前控件的垂直高度和约束条件
根据参数我们可以分别得到宽高的模式和尺寸:

//宽度模式
 int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
//宽度尺寸
 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//高度模式
 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//高度尺寸
 int heightSize = MeasureSpec.getSize(heightMeasureSpec);

为什么传入的参数是int类型?其实是MeasureSpec是View的一个内部类,其中有一个makeMeasureSpec(int size,int mode )方法将size和mode打包成一个32位的int值,这样可以减少内存的分配。所以我们可以使用getMode()和getSize()获取到父控件给与的约束和尺寸;

1.4

现在我们已经知道父控件给与本控件的模式和尺寸,现在看看,如何测量该ViewGroup中子控件的宽高;
在ViewGroup中,提供了三个关于测量子控件的方法:

/**
* 访问所有的子控件并测量他们,包含他们的约束条件和padding,并且跳过了状态
*为Gone的子控件
* @param widthMeasureSpec 父控件给与本ViewGroup的宽度约束
* @param heightMeasureSpec 父控件给与本ViewGroup的高度约束
*/
protected void measureChildren(int widhtMeasureSpec,int heightMeasureSpec);
/**
*访问这个ViewGroup中的单个子控件,并测量他,包含了这个视图的约束条件和padding
* @param child  要测量的子控件
* @param parentWidthMeasureSpec 父控件的宽度约束要求
* @param parentHeightMeasureSpec 父控件的高度约束要求
*/
protected void measureChild(View child,
                        int parentWidthMeasureSpec,int parentHeightMeasureSpec);
/**
* 测量这个子控件,包含这个视图的约束要求以及他的填充和边缘,
* 这个子控件必须有 MarginLayoutParams获得是在getChildMeasureSpec中完成滴
*/
protect void measureChildWithMargins(View child,
                        int parentWidthMeasureSpec,int widthUsed,
                        int parentHeightMeasureSpec ,int heightused);

我现在只使用了第一种方法 measureChildren(),其他两种没研究也不会用,就不说了;
当使用measureChildren() 方法后,我们就可以获取子控件的测量宽高了

view.getMeasuredWidth();
view.getMeasuredHeight();

下面是我在学习时用到的onMeasure中的代码:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.i(TAG, "onMeasure: widhtSize = " + widthSize + ",  heigthSize = " + heightSize);
        measureChildren(widthMeasureSpec,heightMeasureSpec);

        int width = getMaxWidth(widthMode,widthSize);
        int height = getMaxHeight(heightMode,heightSize);
        
        setMeasuredDimension(width,height );        //确定控件的宽高大小并设置
    }
 public int getMaxWidth(int widthMode,int widthSize) {
        int childNum = getChildCount();
        int maxWidth = 0;
        Log.i(TAG, "getMaxWidth: 宽度模式");
        switch (widthMode) {
            case MeasureSpec.AT_MOST:
                Log.i(TAG, "getMaxWidth: MeasureSpec.AT_MOST 设置了最大范围(一般是WRAP_CONTENT)");
                for (int i = 0; i < childNum; i++) {
                    View childView = getChildAt(i);
                    int childMeasuredWidth = childView.getMeasuredWidth();
                    if (i == 0){
                        maxWidth += childMeasuredWidth ;
                        continue;
                    }
                    if (maxWidth + childMeasuredWidth + mHorizontalSpace > widthSize){
                        maxWidth = widthSize;
                        break;
                    }else{
                        maxWidth += (childMeasuredWidth + mHorizontalSpace);
                    }
                }
                break;
            case MeasureSpec.EXACTLY:
                Log.i(TAG, "getMaxWidth: MeasureSpec.EXACTLY  精确(一般是MATCH_PARENT和固定值)");
                maxWidth = widthSize ;
                break;
            case MeasureSpec.UNSPECIFIED:        //没用过,所以没有写值
                Log.i(TAG, "getMaxWidth: MeasureSpec.UNSPECIFIED ");
                break;
            default:
                break;
        }
        return maxWidth;
    }
    public int getMaxHeight(int heightMode,int heightSize){
        int height = 0 ;
        Log.i(TAG, "getMaxHeight: 高度模式");
        switch (heightMode){
            case MeasureSpec.EXACTLY:
                Log.i(TAG, "getMaxHeight: MeasureSpec.EXACTLY");
                height = heightSize;
                break;
                case MeasureSpec.AT_MOST:
                    Log.i(TAG, "getMaxHeight: MeasureSpec.AT_MOST");
                    //自己计算想要的高度值
                 
                    break;
                case MeasureSpec.UNSPECIFIED:        //没用过
                    Log.i(TAG, "getMaxHeight: MeasureSpec.UNSPECIFIED");
                    break;
        }
        return height;
    }

二、 onLayout()

为View和其子View分配一个大小和位置
自定义ViewGroup则需要实现从ViewGroup中的抽象方法:

ViewGroup.class:

  @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

而这个方法使用@Override标注,是其是重写View类中的onLayout

View.class:

   /**
     * 当这个View和其子View被分配一个大小和位置时访问layout。
     * 带有子类的派生类应该重写此方法,并在每个子类上调用布局
     * @param changed 当前View的大小和位置被改变了
     * @param left 左边位置(相对于父视图)
     * @param top 顶部位置(相对于父视图)
     * @param right 右边位置(相对于父视图)
     * @param bottom 底部位置(相对于父视图)
     */
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

由上面可见,继承ViewGroup时,实现的onLayout方法的参数是该控件相对于父控件的位置,如图理解该位置:

上面是我自己画的一个草图,应该看到还是很清楚的
我这里既然说的是ViewGroup,那么肯定还需要确定其子控件的位置,这就需要我们自己给出子控件的相对于父控件的left,top,right,bottom的值,然后调用:child.layout(left,top,right,bottom);
例:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.i(TAG, "onLayout: ################################");
        Log.i(TAG, "onLayout: l="+l+",t="+t+",r="+r+",b="+b);
        int height = b - t;        //父控件本身的高
        Log.i(TAG, "onLayout: 控件的高 = " + height );
        int left = getPaddingLeft();
        int top = getPaddingTop();
        int childNum = getChildCount();
        for (int i = 0; i < childNum; i++) {
            View child = getChildAt(i);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            Log.i(TAG, "onLayout: left=" + left + ",top="+ (height/2 - childHeight/2) + ",right="+(left+childWidth) + ",bottom="+ ( height/2 + childHeight/2) );
//            child.layout(left, top, left + childWidth, b);
            child.layout(left, height/2 - childHeight/2, left + childWidth, height/2 + childHeight/2);
            left += (childWidth + mHorizontalSpace);
        }
    }

本文中的代码,仅做简单的思路学习参考,还是需要以实际需求为主;

你可能感兴趣的:(自定义ViewGroup中的几个方法记载)