Android自定义View的基础知识

一、自定义View的套路
1. 自定义属性
2. 测量(onMeasure)
3. 摆放(onLayout)
4. 绘制(onDraw)
5. 用户交互,触摸事件(onTouchEvent)
二、自定义属性的步骤
1. 编写attrs.xml文件(如下)
     
        
        
        
     
2. 在布局中引用
3. 在自定义View中获取
    private void init(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
        mText = array.getString(R.styleable.MyTextView_mText);
        mTextColor = array.getColor(R.styleable.MyTextView_mTextColor, mTextColor);
        mTextSize = array.getDimension(R.styleable.MyTextView_mTextSize, dp2px(mTextSize));
        array.recycle();
    }
三、自定义View的测量模式
onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值。其中最高2位是测量模式,剩下的30位是值。
------------------------------------------------------------------
1. UNSPECIFIED(00) :表示父类没有对子施加任何约束,子可以是任意大小。比如ScrollView就是这个模式。 
2. EXACTLY(01) : 表示父控件已经确切的指定了子View的大小。比如100dp 、match_parent
3. AT_MOST(10):表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。比如wrap_content。

** 实际上关于上面的东西了解即可,在实际运用之中只需要记住有三种模式,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可。
** 如果对View的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec);
要调用 setMeasuredDimension( widthsize, heightsize); 这个函数。
** 对于ViewGrop一般可以调用measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec)这个方法去测量子View的宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        // 测量子控件的宽高
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

测量模式是由自己和父布局决定的:
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

小结:

  1. 如果子View的大小是固定的值,它的测量模式就是EXACTLY(精确的)。
  2. 如果子View的大小是match_parent,它的测量模式和父布局一样。
  3. 如果子View的大小是wrap_content,它的测量模式:如果父布局是match_parent和wrap_content的话,它的测量模式是:AT_MOST;如果父布局是UNSPECIFIE,它就是UNSPECIFIED。
四、绘制文本的计算方法
文本的绘制:drawText(String text, float x, float y,  Paint paint)
    1. text:绘制的文本内容
    2. x:开始的位置
    3. y:基线(baseLine)
    4. paint:画笔
    5. baseLine的计算:
          Paint.FontMetrics metrics = mPaint.getFontMetrics();
          float dy = (metrics.top - metrices.bottom) / 2  - metrics.bottom;
          float baseLine = getHeight()/2+dy;
  注意:metrices.top是负数,metrices.bottom是正数。
Android自定义View的基础知识_第1张图片
事例图.jpg

参考学习文章:
安卓自定义View教程目录

你可能感兴趣的:(Android自定义View的基础知识)