进阶之自定义View之自绘控件

自定义View按类型来划分的话,自定义View的实现方式大概可以分为三种:

  1. 组合控件
  2. 继承控件
  3. 自绘控件

上一篇我们讲过了组合控件,接下来我们来讲讲自绘控件。
每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()onLayout()onDraw()

onMeasure():测量的意思,顾名思义就是用于测量视图的大小的。**measure()**方法接收两个参数widthMeasureSpec和heightMeasureSpec。

widthMeasureSpec:用于确定视图的宽度的规格和大小。
heightMeasureSpec:用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
specMode一共有三种类型  EXACTLY、AT_MOST、UNSPECIFIED

EXACTLY:即子视图的大小是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小。

AT_MOST:即子视图最大只能是specSize中指定的大小,并且不会超过specSize。系统默认会按照这个规则来设置子视图的大小。

UNSPECIFIED:即开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。

onLayout():这个方法是用于给视图进行布局的,也就是确定视图的位置。

layout()方法接收四个参数,分别代表着左、上、右、下的座标,当然这个座标是相对于当前视图的父视图而言的。

public void layout(int l, int t, int r, int b) {}

onDraw():measure和layout的之后,就进入到draw的过程。

下面我们就来自定义一个自己的View

public class CircularProgressBar extends View {

    /**
     * 动画时长
     */
    private int mDuration;
    /**
     * 接口
     */
    private OnFinishListener mListener;
    /**
     * 动画
     */
    private ValueAnimator mAnimator;

    /**
     * 逆向进度条最大值
     */
    private int mCurrentProgress;

    /**
     * 进度条最大值
     */
    private int maxValue = 100;

    /**
     * 当前进度值
     */
    private int mCurrentValue;

    /**
     * 每次扫过的角度,用来设置进度条圆弧所对应的圆心角,alphaAngle=(currentValue/maxValue)*360
     */
    private float mAlphaAngle;

    /**
     * 底部圆弧的颜色,默认 ResUtil.getColor(R.color.colorAccent)
     */
    private int mDefaultColor;

    /**
     * 进度条圆弧块的颜色, 默认 ResUtil.getColor(R.color.colorWhith)
     */
    private int mRunColor;

    /**
     * 中间文字颜色(默认蓝色)
     */
    private int cTextColor = Color.BLUE;

    /**
     * 中间文字的字体大小(默认30dp)
     */
    private int cTextSize;

    /**
     * 圆环的宽度
     */
    private int mCircleWidth;

    /**
     * 画圆弧的画笔
     */
    private Paint mCirclePaint;

    /**
     * 画文字的画笔
     */
    private Paint mTextPaint;

    /**
     * 进度条方向
     */
    private boolean mDirectionof;

    public void setListener(OnFinishListener mListener) {
        this.mListener = mListener;
    }

    public CircularProgressBar(Context context) {
        this(context, null);
    }

    public CircularProgressBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    //初始化
    @SuppressLint("ResourceAsColor")
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {

        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CirculaProgressBar,
                defStyleAttr, 0);
        // 默认底色白色透明度百分之20
        mDefaultColor = ta.getColor(R.styleable.CirculaProgressBar_circular_defaultColor, R.color.colorGray);
        // 默认进度条颜色为白色
        mRunColor = ta.getColor(R.styleable.CirculaProgressBar_circular_runColor, R.color.colorWhith);
        // 默认中间文字颜色为蓝色
        cTextColor = ta.getColor(R.styleable.CirculaProgressBar_circular_cTextColor, Color.BLUE);
        // 默认中间文字字体大小为30dp
        cTextSize = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_cTextSize, DpDonversionPxUtils.px2dip(30));
        // 默认圆弧宽度为6dp
        mCircleWidth = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_circleWidth, DpDonversionPxUtils.px2dip(6));
        // 默认顺时针方向
        mDirectionof = ta.getBoolean(R.styleable.CirculaProgressBar_circular_directionof, false);

        ta.recycle();

        //画圆画笔
        mCirclePaint = new Paint();
        // 抗锯齿
        mCirclePaint.setAntiAlias(true);
        // 防抖动
        mCirclePaint.setDither(true);
        //画笔宽度
        mCirclePaint.setStrokeWidth(mCircleWidth);


        //画文字画笔
        mTextPaint = new Paint();
        // 抗锯齿
        mTextPaint.setAntiAlias(true);
        // 防抖动
        mTextPaint.setDither(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取屏幕宽
        int widthPixels = this.getResources().getDisplayMetrics().widthPixels;
        //获取屏幕高
        int heightPixels = this.getResources().getDisplayMetrics().heightPixels;

        int rwidth = MeasureSpec.getSize(widthMeasureSpec);
        int rhedight = MeasureSpec.getSize(heightMeasureSpec);

        int minWidth = Math.min(widthPixels, rwidth);
        int minHedight = Math.min(heightPixels, rhedight);

        setMeasuredDimension(Math.min(minWidth, minHedight), Math.min(minWidth, minHedight));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int center = this.getWidth() / 2;
        int radius = center - mCircleWidth / 2;
        // 绘制进度圆弧
        drawCircle(canvas, center, radius);
        drawText(canvas, center);
    }

    /**
     * 绘制进度圆弧
     *
     * @param canvas 画布对象
     * @param center 圆心的x和y座标
     * @param radius 圆的半径
     */
    private void drawCircle(Canvas canvas, int center, int radius) {
        if (mCirclePaint == null) {
            return;
        }
        // 清除上一次的shader
        mCirclePaint.setShader(null);
        // 设置底部圆环的颜色,这里使用第一种颜色
        mCirclePaint.setColor(mDefaultColor);
        // 设置绘制的圆为空心
        mCirclePaint.setStyle(Paint.Style.STROKE);
        // 画底部的空心圆
        canvas.drawCircle(center, center, radius, mCirclePaint);
        // 圆的外接正方形
        RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);


        mCirclePaint.setShadowLayer(10, 10, 10, Color.BLUE);
        // 设置圆弧的颜色
        mCirclePaint.setColor(mRunColor);
        // 把每段圆弧改成圆角的
        mCirclePaint.setStrokeCap(Paint.Cap.ROUND);
        // 计算每次画圆弧时扫过的角度,这里计算要注意分母要转为float类型,否则alphaAngle永远为0
        if (mDirectionof) {
            canvas.drawArc(oval, -90, mCurrentProgress - 360, false, mCirclePaint);
        } else {
            mAlphaAngle = mCurrentValue * 360.0f / maxValue * 1.0f;
            canvas.drawArc(oval, -90, mAlphaAngle, false, mCirclePaint);
        }
    }

    /**
     * 绘制文字
     *
     * @param canvas 画布对象
     * @param center 圆心的x和y座标
     */
    private void drawText(Canvas canvas, int center) {
        // 计算进度
        int result = ((maxValue - mCurrentValue) * (mDuration / 1000) / maxValue);
        String percent;
        if (maxValue == mCurrentValue) {
            percent = "完成";
            // 设置要绘制的文字大小
            mTextPaint.setTextSize(cTextSize);
        } else {
            percent = result + "";
            // 设置要绘制的文字大小
            mTextPaint.setTextSize(cTextSize + cTextSize / 3);
        }
        // 设置文字居中,文字的x座标要注意
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        // 设置文字颜色
        mTextPaint.setColor(cTextColor);
        // 注意此处一定要重新设置宽度为0,否则绘制的文字会重叠
        mTextPaint.setStrokeWidth(0);
        // 文字边框
        Rect bounds = new Rect();
        // 获得绘制文字的边界矩形
        mTextPaint.getTextBounds(percent, 0, percent.length(), bounds);
        // 获取绘制Text时的四条线
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        // 计算文字的基线,方法见http://blog.csdn.net/harvic880925/article/details/50423762
        int baseline = center + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        // 绘制表示进度的文字
        canvas.drawText(percent, center, baseline, mTextPaint);

    }


    /**
     * 设置圆环的宽度
     *
     * @param width
     */
    public void setCircleWidth(int width) {
        this.mCircleWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, getResources()
                .getDisplayMetrics());
        mCirclePaint.setStrokeWidth(mCircleWidth);
        //一般只是希望在View发生改变时对UI进行重绘。invalidate()方法系统会自动调用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 设置圆环的底色,默认为亮灰色LTGRAY
     *
     * @param color
     */
    public void setDefaultColor(int color) {
        this.mDefaultColor = color;
        mCirclePaint.setColor(mDefaultColor);
        //一般只是希望在View发生改变时对UI进行重绘。invalidate()方法系统会自动调用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 设置进度条的颜色,默认为白色<br>
     *
     * @param color
     */
    public void setRunColor(int color) {
        this.mRunColor = color;
        mCirclePaint.setColor(mRunColor);
        //一般只是希望在View发生改变时对UI进行重绘。invalidate()方法系统会自动调用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 设置进度条运行方向
     *
     * @param directionof
     */
    public void setRunDirectionof(boolean directionof) {
        this.mDirectionof = directionof;
        //一般只是希望在View发生改变时对UI进行重绘。invalidate()方法系统会自动调用 View的onDraw()方法。
        invalidate();
    }


    /**
     * 按进度显示百分比,可选择是否启用数字动画
     *
     * @param duration 动画时长
     */
    public void setDuration(int duration, OnFinishListener listener) {
        this.mListener = listener;
        this.mDuration = duration + 1000;
        if (mAnimator != null) {
            mAnimator.cancel();
        } else {
            mAnimator = ValueAnimator.ofInt(0, maxValue);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentValue = (int) animation.getAnimatedValue();
                    Log.e(" onAnimationUpdate ", " onAnimationUpdate  =   " + mCurrentValue);
                    mCurrentProgress = (int) (360 * (mCurrentValue / 100f));
                    Log.e(" onAnimationUpdate ", " onAnimationUpdate  mCurrentProgress  =   " + mCurrentProgress);
                    //一般只是希望在View发生改变时对UI进行重绘。invalidate()方法系统会自动调用 View的onDraw()方法。
                    invalidate();
                    if (maxValue == mCurrentValue && CircularProgressBar.this.mListener != null) {
                        CircularProgressBar.this.mListener.onFinish();
                    }
                }
            });
            mAnimator.setInterpolator(new LinearInterpolator());
        }
        mAnimator.setDuration(duration);
        mAnimator.start();
    }

    /**
     * 停止动画
     */
    public void stopAnimator() {
        if (mAnimator != null) {
            mAnimator.cancel();
        }
        mCurrentValue = 0;
        mCurrentProgress = 0;
        invalidate();
    }

}

效果图
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章