美文网首页
自定义View

自定义View

作者: chenmingzhi | 来源:发表于2016-11-03 22:03 被阅读0次

    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

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="CircleView">
            <attr name="circle_color" format="color"/>
        </declare-styleable>
    </resources>
    

    在布局文件添加

    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

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