自定义View

View 的工作原理主要包含 View 的三大流程Measure、Layout和Draw,即测量、布局、绘制。

measure过程

View:

通过measure方法就完成了其测量。measure()是final的方法,子类无法重写此方法,measure()中会调用View的onMeasure(widthMeasureSpec, heightMeasureSpec)方法并传递MeasureSpec给他,MeasureSpec在上一篇博文已经讲过。

onMeasure()会执行
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));方法设置View宽/高的测量值

public static int getDefaultSize(int size, int measureSpec) {  
    int result = size;  
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  
    switch (specMode) {  
    case MeasureSpec.UNSPECIFIED:  
        result = size;  
        break;  
    case MeasureSpec.AT_MOST:  
    case MeasureSpec.EXACTLY:  
        result = specSize;  
        break;  
    }  
    return result;  
}

getDefaultSize()作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使用提供的大小,否则在允许范围内可任意指定大小,第一个参数size为提供的默认大小,第二个参数为测量的大小。

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身大小,否则在布局中使用就相当于使用match_parent。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);//都为最大模式时(wrap_content)
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }

上面的代码中给View指定了一个默认的内部宽/高,mWidth和mHeight,在wrap_content时设置默认的大小,非wrap_content时沿用系统的测量值。

ViewGroup:

与View的过程不同,除了完成自己的测量过程,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,没有重写View的onMeasure,不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同。

layout过程

这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程。layout方法确定View本身位置,而onLayout方法会确定所有子元素的位置。

layout完成后可以通过getWidth和getHeight获取最终宽高。

draw过程

draw的作用是将View绘制到屏幕上,其过程如下几步:

  1. drawBackground(canvas);绘制背景
  2. onDraw(canvas);绘制自己本身
  3. dispathDraw(canvas);绘制children
  4. onDrawScrollBars(canvas);绘制装饰
    dispathDraw会遍历调用所有子元素的draw方法,如此draw事件就一层一层传递下去。

自定义View

自定义View分类:

  1. 继承View重写onDraw方法(需自己支持wrap_content、padding)
  2. 继承特定的View(比如TextView等,不需要自己支持wrap_content、padding)
  3. 继承特定的ViewGroup(如LinearLayout)
  4. 继承ViewGroup派生特殊的Layout(比较复杂)

继承View重写onDraw
在布局文件使用自定义属性:首先在values新建attrs.xml



    
        
    

在布局文件添加

xmlns:app="http://schemas.android.com/apk/res-auto"

绘制一个圆的过程:

public class CircleView extends View {

    private int mColor = Color.BLACK;//圆的颜色
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//定义画笔

    public CircleView(Context context) {
        super(context);
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);//加载自定义属性
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.BLACK);//默认值为蓝色
        a.recycle();//释放资源
        init();
    }


    private void init() {
        mPaint.setColor(mColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);//都为最大模式时(wrap_content)
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
    }


}

Canvas的用法1
Canvas的用法2

你可能感兴趣的:(自定义View)