自定义View没那么可怕~进度圈

本着无图言x的原则,所以先上效果:

这里写图片描述

很简单!也很常见!

所以这样子比较适合入门,因为这个是自定义单个控件,所以只会用到ondraw()、onmeasure()两个方法,所以面试的时候不要傻傻的说自定义view要重写onlayout(),要分情况的!

下面我们一步一步来:

  • 继承view,重写构造方法,至于重写那个构造方法,要看情况咯,CircleView(Context context) 一个参数构造 一般是用于Java代码对象view的创建,CircleView(Context context, AttributeSet attrs) 两个参数构造用于布局声明,但也有1~3构造全部写的,我觉得没必要

  • 自定义view一般都会用到画笔,毕竟图是我们自己实现的,随意我们需要在构造里初始化画笔,当然还有画笔的还需要修饰,比如:颜色、大小、样式等等

  • 接着就是要在画布上画出我们想要的东西了,画布在我们的ondraw()里面,至于里面的逻辑都不是很难,简单的数据计算

  • 当然这时候效果已经出来了,但还不尽人意,比如:你会发现你的布局文件设置宽高为wrap_content和match_parent没有区别,这些问题,我们接下来就会在码代码的时候讲


我先贴下代码,没有onmeasure()哦!

public class CircleView extends View {
    private float sweepAngle;//圆弧经过的角度

    private Paint rPaint;//矩形的画笔
    private Paint progressPaint;//圆弧的画笔
    private Paint textPaint;//进度画笔
    private int precent = 0;//更新百分比
    CircleAnim anim;//内部动画

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
        anim = new CircleAnim();
    }

    private void init(Context context, AttributeSet attrs) {
        rPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        rPaint.setStyle(Paint.Style.STROKE);//不填充
        rPaint.setColor(Color.GRAY);
        rPaint.setStrokeWidth(15);

        progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        progressPaint.setStyle(Paint.Style.STROKE);//不填充
        progressPaint.setColor(Color.RED);
        progressPaint.setStrokeWidth(15);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setStyle(Paint.Style.STROKE);
        textPaint.setColor(Color.BLACK);
        textPaint.setFakeBoldText(true);//设置粗体
        textPaint.setTextSize(40);
        textPaint.setTextAlign(Paint.Align.CENTER);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int min = Math.min(getWidth(), getHeight());
        int centre = min / 2; // 获取圆心的x坐标
        int radius = centre - 15 / 2;// 半径
        RectF rectF = new RectF(centre - radius, centre - radius, centre + radius, centre + radius);
        canvas.drawArc(rectF, 0, 360, false, rPaint);
        canvas.drawArc(rectF, 0, sweepAngle, false, progressPaint);//这里角度0对应的是三点钟方向,顺时针方向递增
        canvas.drawText(precent + "%", centre, centre, textPaint);
    }



    public class CircleAnim extends Animation {

        public CircleAnim() {
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            sweepAngle = interpolatedTime * 360;
            precent = (int) (interpolatedTime * 100);
            invalidate();
        }
    }


    //设置动画时间
    public void setProgressNum(int time) {
        anim.setDuration(time);
        this.startAnimation(anim);
    }

}

ondraw()方法:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int min = Math.min(getWidth(), getHeight());
        int centre = min / 2; // 获取圆心的x坐标
        int radius = centre - 15 / 2;// 半径
        RectF rectF = new RectF(centre - radius, centre - radius, centre + radius, centre + radius);
        canvas.drawArc(rectF, 0, 360, false, rPaint);
        canvas.drawArc(rectF, 0, sweepAngle, false, progressPaint);//这里角度0对应的是三点钟方向,顺时针方向递增
        canvas.drawText(precent + "%", centre, centre, textPaint);
    }
  • 获取view的宽高,进而获取圆弧的的中心坐标,为什么会取宽高的最小值呢?如:w:150 h:100 看下效果图:

这里写图片描述

下面一部分怎么少了呢? 我们的圆弧半径是取值于宽高的,如果宽度大于高度,而又用宽度来做半径的取值标准,就会导致这样效果,因为宽度是150,也就是半径是75,但高度只有100,那肯定会有一部分绘制不出来的;但如果我们按最小的算就不一样了,怎么算都够,高度100作为取值标准,半径就是50,而宽度是150,所以肯定够你绘制,这就是我们取宽、高最小值的原因。

  • 接着就是画出矩形、圆弧、文本了。

动画

@Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            sweepAngle = interpolatedTime * 360;
            precent = (int) (interpolatedTime * 100);
            invalidate();
        }
  • 主要是这个方法第一个参数的值是0.0~1.0 ;不敢乱翻译。所以你们可以看下源码解释:
/**
     * Helper for getTransformation. Subclasses should implement this to apply
     * their transforms given an interpolation value.  Implementations of this
     * method should always replace the specified Transformation or document
     * they are doing otherwise.
     *
     * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
     *        after it has been run through the interpolation function.
     * @param t The Transformation object to fill in with the current
     *        transforms.
     */
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

主要是通过invalidate(); 来刷新界面(注意:invalidate()和postInvalidate()的区别,后者可以在子线程刷新ui),你启动这个动画后,applyTransformation()方法会不断的执行,直到interpolatedTime 变为1.0,同时,invalidate() 会不停的调用ondraw(),这就实现了连贯的更新动画


这样基本没什么问题了,但。。。真的没问题了吗?

我们把代码改成这样:

 <com.example.administrator.kotlinapp.CircleView
       android:id="@+id/circleview_100"
       android:layout_marginTop="50dp"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       />

效果图:

这里写图片描述

纳尼???wrap_content和match_parent效果一样有没有。
所以我们就需要自己测量咯

onmeasure():

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measurewidth = 400;
        int measureheight = 400;
        measurewidth = resolveSize(measurewidth, widthMeasureSpec);
        measureheight = resolveSize(measureheight, heightMeasureSpec);
        setMeasuredDimension(measurewidth, measureheight);

    }
  • resolveSize()是不是很少见?但你一定经常见这样代码段:
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }

没错这就是resolveSize()的源码,以前我们都是自己写这样的逻辑

  1. MeasureSpec.UNSPECIFIED: 不限制,一般我们会给一个默认值
  2. MeasureSpec.EXACTLY:有上限,不能超过限制
  3. MeasureSpec.AT_MOST:限制固定尺寸

ok,各种模式已经适配完了,这样就支持wrap_content、match_parent、固定值了。

最后附上 demo:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值