Android 自定义View实例之进度圆环

来源:互联网 发布:linux echo $ 编辑:程序博客网 时间:2024/05/18 00:29

自定义View的相关文章:

  • Android 实现一个简单的自定义View
  • Android 自定义View步骤
  • Android Paint详解
  • Android 自定义View之Canvas相关方法说明
  • Android 自定义View实例之 “京东跑”
  • Android 自定义View实例之进度圆环
  • Android 源码分析(TextView)
  • Android 自定义View分发流程
  • Android 自定义View 需要注意的事项

看下效果:
这里写图片描述

实际效果呢要比这个gif好,大家运行看看吧。

我们可以把这个View分为几个部分:

  1. 底部灰色的圆环
  2. 渐变、动态的圆弧,
  3. 圆弧上的箭头
  4. 中间的文字

我们就一个一个的来实现,我本来想直接把最终的代码给大家的,可是我做的过程中遇到了好多细节问题,所以我详细的把我实现的步骤也分享给大家。

底部灰色的圆环

底部灰色的圆环宽度和颜色可以通过属性设置,所以在res/values下新建attrs.xml:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="RoundView">        <!--圆环半径-->        <attr name="radius" format="dimension"/>        <!--圆环宽度-->        <attr name="ring_width" format="dimension"/>        <!--圆环颜色-->        <attr name="ring_color" format="color"/>    </declare-styleable></resources>

后面还会有其他属性,我们直接添加这里,不在具体说明。

RoundView(此自定义View)的代码:

/** * 自定义进度圆环 */public class RoundView extends View{    /**     * 圆环宽度,默认半径的1/5     */    private float mRingWidth = 0;    /**     * 圆环颜色,默认 #CBCBCB     */    private int mRingColor = 0;    /**     * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2)     */    private float mRadius = 0;    /**     * 底环画笔     */    private Paint mRingPaint;    public RoundView(Context context) {        this(context, null);    }    public RoundView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView);        mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0);        mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB"));        mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0);        init();    }    /**     * 初始化     */    private void init() {        mRingPaint = new Paint();        mRingPaint.setAntiAlias(true);// 抗锯齿效果        mRingPaint.setStyle(Paint.Style.STROKE);        mRingPaint.setColor(mRingColor);// 背景    }    @Override    public void onDraw(Canvas canvas) {        // 圆心坐标,当前View的中心        float x = getWidth() / 2;        float y = getHeight() / 2;        //如果未设置半径,则半径的值为view的宽、高一半的较小值        mRadius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius;        //圆环的宽度默认为半径的1/5        mRingWidth = mRingWidth == 0 ? mRadius / 5 : mRingWidth;        // 底环        canvas.drawCircle(x, y, mRadius, mRingPaint);    }

效果:
这里写图片描述
我们发现这个环不全啊,因为圆的宽度是在当前半径向2边展开的。
这里写图片描述
红线就是我们的半径,一部分超出的view的边界了。解决的办法就是将我们的半径减去圆环宽度的一半:

这里写图片描述
加入图中方框内部分。在运行就会出现我们想要的效果了,不过这里还有一个问题,就是onDraw方法会一直重绘,所以这段代码(就是我们解决上面问题的代码)

 //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面        mRadius = mRadius - mRingWidth / 2;

会越来越小,所以修改为:

@Override    public void onDraw(Canvas canvas) {        // 圆心坐标,当前View的中心        float x = getWidth() / 2;        float y = getHeight() / 2;        //如果未设置半径,则半径的值为view的宽、高一半的较小值        float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius;        //圆环的宽度默认为半径的1/5        float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth;        //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面        radius = radius - ringWidth / 2;        mRingPaint.setStrokeWidth(ringWidth);        // 底环        canvas.drawCircle(x, y, radius, mRingPaint);        }

没有设置任何属性的效果:
这里写图片描述

设置属性:

<qiwei.com.frameworknote.ui.RoundView        android:layout_width="300dp"        android:layout_height="300dp"        app:ring_width="10dp"        app:ring_color="#ff0000"        android:radius="100"/>

效果:
这里写图片描述

渐变、动态的圆弧

我们先在圆环上画一个静态进度圆环,属性有宽度,由于进度圆环有渐变效果,所以设置属性开始颜色、结束颜色,还有一点要说明这个进度圆环和底部圆环的半径是一样的。
添加如下属性:

        <!--进度圆环宽度-->        <attr name="progress_ring_width" format="dimension"/>        <!--进度圆环开始颜色-->        <attr name="progress_ring_start_color" format="color"/>        <!--进度圆环结束颜色-->        <attr name="progress_ring_end_color" format="color"/>

在RoundView.java中添加属性:

 /**     * 进度条圆环宽度     */    private float mProgressRingWidth = 0;    /**     * 进度条圆环开始颜色,进度条圆环是渐变的,默认     */    private int mProgressRingStartColor = 0;    /**     * 进度条圆环结束颜色,进度条圆环是渐变的,默认     */    private int mProgressRingEndColor = 0;    /**     * 进度条圆环Paint     */    private Paint mProgressRingPaint;    /**     * 进度条圆环渐变shader     */    private Shader mProgressRingShader;

TypeArray解析:

mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0);        mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9"));        mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80"));

在init方法中初始化进度圆滑的Paint和渐变数组

 // 圆环紫色渐变色        arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor};        mProgressRingPaint = new Paint();        mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果        mProgressRingPaint.setStyle(Paint.Style.STROKE);        mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头        mProgressRingPaint.setStrokeWidth(mProgressRingWidth);

注意进度圆环应该小于等于底部圆环的宽度,如果小于,那么进度圆环应该在底部圆环的中间。
在onDraw方法中绘制完底部圆环后绘制静态的进度圆环:

// 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors,null);            mProgressRingPaint.setShader(mProgressRingShader);        }        canvas.drawArc(new RectF(0,0,getWidth(),getHeight()), 0, 180, false, mProgressRingPaint);

看下效果:
这里写图片描述

出现几个问题:

  1. 一部分超出view边界
  2. 进度圆环没有在底部圆环里
  3. 我们想要的效果是从顶部开始的

其实1、2都是由半径引发的问题,3问题是代码中我们设置的是从0处开始,解决的方法就是设置为-90。

修改后:

 //----绘制进度圆环------        // 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors, null);            mProgressRingPaint.setShader(mProgressRingShader);        }        //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        canvas.drawArc(new RectF(progressRingWidth/2,progressRingWidth/2,getWidth()-progressRingWidth/2,getHeight()-progressRingWidth/2), -90, 180, false, mProgressRingPaint);

我们设置底部圆环的宽度和进度条的宽度:

<qiwei.com.frameworknote.ui.RoundView        android:layout_width="300dp"        android:layout_height="300dp"        app:ring_width="28dp"        app:progress_ring_width="14dp"/>

效果:
这里写图片描述
问题又出现了:

  1. 进度圆环应该在底部圆环的中间
  2. 渐变色的效果不对

    第一个问题是我们的RectF(矩形区域)设置的不对,下面这张图告诉我们怎么算
    这里写图片描述
    修改后:

 // 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors, null);            mProgressRingPaint.setShader(mProgressRingShader);        }        //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        float left = x - radius;        float top = y - radius;        float right = x + radius;        float bottom = y + radius;        canvas.drawArc(new RectF(left, top, right, bottom), -90, 180, false, mProgressRingPaint);

效果:
这里写图片描述

我们看下渐变的问题,我们使用的是SweepGradient,也是所说的雷达渐变,它也是从水平方向开始顺时针渐变,所以就出现了上面的情况,怎么解决呢?我想到的办法是旋转画布:

 //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        float left = x - radius;        float top = y - radius;        float right = x + radius;        float bottom = y + radius;        //保存当前的状态        canvas.save();        //逆时针旋转90度        canvas.rotate(-90, x, y);        canvas.drawArc(new RectF(left, top, right, bottom), 0, 180, false, mProgressRingPaint);

效果:
这里写图片描述

无限接近中啊,可是最上面的圆头有问题啊,看下图:
这里写图片描述
我们旋转90度只旋转到了蓝线处,而实际我们应该旋转到红线处,所以我们要计算出红蓝线间的夹角然后加上90度就是我们要旋转的角度。
修改如下:

/**     * 已知圆半径和切线长求弧长公式     */    private double radianToAngle(float radios) {        double aa = mProgressRingWidth / 2 / radios;        double asin = Math.asin(aa);        double radian = Math.toDegrees(asin);        return radian;    }    @Override    public void onDraw(Canvas canvas) {        // 圆心坐标,当前View的中心        float x = getWidth() / 2;        float y = getHeight() / 2;        //如果未设置半径,则半径的值为view的宽、高一半的较小值        float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius;        //圆环的宽度默认为半径的1/5        float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth;        //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面        radius = radius - ringWidth / 2;        mRingPaint.setStrokeWidth(ringWidth);        // 底环        canvas.drawCircle(x, y, radius, mRingPaint);        //----绘制圆环(最底部)结束------        //----绘制进度圆环------        // 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors, null);            mProgressRingPaint.setShader(mProgressRingShader);        }        //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        float left = x - radius;        float top = y - radius;        float right = x + radius;        float bottom = y + radius;        //保存当前的状态        canvas.save();        // 旋转画布90度+笔头半径转过的角度        double radian = radianToAngle(radius);        double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+        //逆时针旋转90度        canvas.rotate((float)-degrees, x, y);        canvas.drawArc(new RectF(left, top, right, bottom), (float)radian, 180, false, mProgressRingPaint);}

注意绘制弧的开始角度为 radian
效果:
这里写图片描述

如何让我们的圆环动起来呢?就是每次绘制的时候修改圆弧的角度,另外圆弧旋转的速度我们如何控制呢?我们可以使用动画和差值器,动画和差值器我们就不详细讲了。

/** * 自定义进度圆环 */public class RoundView extends View implements AnimationListener {    /**     * 圆环转过的角度     */    private float mSweepAngle = 1;    /**     * 之前的角度     */    private float mOldAngle = 0;    /**     * 新的角度     */    private float mNewAngle = 0;    /**     * 圆环宽度,默认半径的1/5     */    private float mRingWidth = 0;    /**     * 圆环颜色,默认 #CBCBCB     */    private int mRingColor = 0;    /**     * 进度条圆环宽度     */    private float mProgressRingWidth = 0;    /**     * 进度条圆环开始颜色,进度条圆环是渐变的,默认     */    private int mProgressRingStartColor = 0;    /**     * 进度条圆环结束颜色,进度条圆环是渐变的,默认     */    private int mProgressRingEndColor = 0;    /**     * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2)     */    private float mRadius = 0;    /**     * 进度条圆环Paint     */    private Paint mProgressRingPaint;    /**     * 进度条圆环渐变shader     */    private Shader mProgressRingShader;    /**     * 底环画笔     */    private Paint mRingPaint;    private int[] arcColors = {};// 渐变色    /**     * 进度动画     */    private BarAnimation barAnimation;    /**     * 抖动(缩放)动画     */    private ScaleAnimation scaleAnimation;    /**     * 是否正在改变     */    private boolean isAnimation = false;    public RoundView(Context context) {        this(context, null);    }    public RoundView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView);        mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0);        mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB"));        mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0);        mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9"));        mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80"));        mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0);        typedArray.recycle();        init();    }    /**     * 初始化     */    private void init() {        mRingPaint = new Paint();        mRingPaint.setAntiAlias(true);// 抗锯齿效果        mRingPaint.setStyle(Paint.Style.STROKE);        mRingPaint.setColor(mRingColor);// 背景灰        // 圆环紫色渐变色        arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor};        mProgressRingPaint = new Paint();        mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果        mProgressRingPaint.setStyle(Paint.Style.STROKE);        mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头        mProgressRingPaint.setStrokeWidth(mProgressRingWidth);        barAnimation = new BarAnimation();        barAnimation.setAnimationListener(this);        scaleAnimation = new ScaleAnimation();        scaleAnimation.setDuration(100);    }    /**     * 设置新的角度     */    public void setAngle(final float newAngle,boolean isScale) {        if (!isAnimation) {            if(isScale){                scaleAnimation.setAnimationListener(new AnimationListener() {                    @Override                    public void onAnimationStart(Animation animation) {                    }                    @Override                    public void onAnimationEnd(Animation animation) {                        changeAngle(newAngle);                    }                    @Override                    public void onAnimationRepeat(Animation animation) {                    }                });                startAnimation(scaleAnimation);            }else{                changeAngle(newAngle);            }        }    }    private void changeAngle(float newAngle){        mOldAngle = mNewAngle;        mNewAngle = newAngle;        int duration = (int) (Math.abs(mNewAngle - mOldAngle) * 15);        barAnimation.setDuration(duration);        barAnimation.setInterpolator(new DecelerateInterpolator());        startAnimation(barAnimation);    }    @SuppressLint("DrawAllocation")    @Override    public void onDraw(Canvas canvas) {        // 圆心坐标,当前View的中心        float x = getWidth() / 2;        float y = getHeight() / 2;        //如果未设置半径,则半径的值为view的宽、高一半的较小值        float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius;        //圆环的宽度默认为半径的1/5        float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth;        //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面        radius = radius - ringWidth / 2;        mRingPaint.setStrokeWidth(ringWidth);        // 底环        canvas.drawCircle(x, y, radius, mRingPaint);        //----绘制圆环(最底部)结束------        //----绘制进度圆环------        // 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors, null);            mProgressRingPaint.setShader(mProgressRingShader);        }        //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        float left = x - radius;        float top = y - radius;        float right = x + radius;        float bottom = y + radius;        // 旋转画布90度+笔头半径转过的角度        double radian = radianToAngle(radius);        double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+        canvas.save();        canvas.rotate((float) degrees, x, y);        canvas.drawArc(new RectF(left, top, right, bottom), (float) radian, mOldAngle + mSweepAngle, false, mProgressRingPaint);        //----绘制进度圆环结束------        super.onDraw(canvas);    }    /**     * 已知圆半径和切线长求弧长公式     *     * @param radios     * @return     */    private double radianToAngle(float radios) {        double aa = mProgressRingWidth / 2 / radios;        double asin = Math.asin(aa);        double radian = Math.toDegrees(asin);        return radian;    }    /**     * 抖动(缩放动画)     */    public class ScaleAnimation extends Animation {        private int centerX;        private int centerY;        public ScaleAnimation() {        }        @Override        public void initialize(int width, int height, int parentWidth,                               int parentHeight) {            super.initialize(width, height, parentWidth, parentHeight);            centerX = width / 2;            centerY = height / 2;        }        @Override        protected void applyTransformation(float interpolatedTime, Transformation t) {            super.applyTransformation(interpolatedTime, t);            if (interpolatedTime < 0.25f) {                // 1-1.1                float ss = interpolatedTime * 0.4f + 1f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.25f && interpolatedTime < 0.5f) {                // 1.1-1                float ss = (0.5f - interpolatedTime) * 0.4f + 1f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.5f && interpolatedTime < 0.75f) {                // 1-0.9                float ss = (0.75f - interpolatedTime) * 0.4f + 0.9f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.75f && interpolatedTime <= 1f) {                // 0.9-1                float ss = interpolatedTime * 0.4f + 0.6f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            }        }    }    /**     * 进度条动画     */    public class BarAnimation extends Animation {        public BarAnimation() {        }        /**         * 然后调用postInvalidate()不停的绘制view。         */        @Override        protected void applyTransformation(float interpolatedTime,                                           Transformation t) {            super.applyTransformation(interpolatedTime, t);            if (mNewAngle - mOldAngle >= 0) {                // 正向                mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle);            } else {                // 逆向                mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle);            }            postInvalidate();        }    }    @Override    public void onAnimationStart(Animation animation) {        isAnimation = true;    }    @Override    public void onAnimationEnd(Animation animation) {        isAnimation = false;    }    @Override    public void onAnimationRepeat(Animation animation) {        // TODO Auto-generated method stub    }}

这里面主要涉及到了动画和差值器,重写了动画的 applyTransformation 方法。
这里写图片描述
抖动的效果在gif上没有展示出来,运行看看吧。

下面我们绘制下箭头,其实箭头呢就是3条线,重点是计算线的坐标:
这里写图片描述

由图中计算1、2、3、4点到坐标,我们定义1、2点之间长度的一半为箭头的大小(arrowSize)。r代表半径所以:
1点坐标:(x-arrowSize,y-r)
2点坐标:(x+arrowSize,y-r)
3点坐标:(x, y - r - arrowSize)
4点坐标:(x, y - r + arrowSize)

所以绘制箭头的代码:

         //----绘制箭头开始------        //画布恢复到上一次save到状态,也就是旋转的画布        //将我们要画箭头的位置旋转到顶部,方便我们计算箭头的坐标,绘制完箭头在将画布恢复        canvas.save();        double hudu = 2 * Math.PI / 360 * (mOldAngle + mSweepAngle);        double radians = Math.toDegrees(hudu);        // 旋转画布        canvas.rotate((float) radians, x, y);        //绘制箭头        canvas.drawLine(x - arrowSize, y - radius, x + arrowSize, y - radius, mArrowPaint);        canvas.drawLine(x + arrowSize, y - radius, x, y - radius - arrowSize, mArrowPaint);        canvas.drawLine(x + arrowSize, y - radius, x, y - radius + arrowSize, mArrowPaint);        canvas.restore();        //----绘制箭头结束------

完整代码:

/** * 自定义进度圆环 */public class RoundView extends View implements AnimationListener {    /**     * 圆环转过的角度     */    private float mSweepAngle = 1;    /**     * 之前的角度     */    private float mOldAngle = 0;    /**     * 新的角度     */    private float mNewAngle = 0;    /**     * 圆环宽度,默认半径的1/5     */    private float mRingWidth = 0;    /**     * 圆环颜色,默认 #CBCBCB     */    private int mRingColor = 0;    /**     * 进度条圆环宽度     */    private float mProgressRingWidth = 0;    /**     * 进度条圆环开始颜色,进度条圆环是渐变的,默认     */    private int mProgressRingStartColor = 0;    /**     * 进度条圆环结束颜色,进度条圆环是渐变的,默认     */    private int mProgressRingEndColor = 0;    /**     * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2)     */    private float mRadius = 0;    /**     * 进度条圆环Paint     */    private Paint mProgressRingPaint;    /**     * 进度条圆环渐变shader     */    private Shader mProgressRingShader;    /**     * 底环画笔     */    private Paint mRingPaint;    /**     * 箭头画笔     */    private Paint mArrowPaint;    /**     * 箭头大小     */    private int arrowSize = 0;    /**     * 文字画笔     */    private Paint mTextPaint;    /**     * 文字颜色     */    private int mTextColor;    /**     * 文字大小     */    private float mTextSize;    private int[] arcColors = {};// 渐变色    /**     * 进度动画     */    private BarAnimation barAnimation;    /**     * 抖动(缩放)动画     */    private ScaleAnimation scaleAnimation;    /**     * 是否正在改变     */    private boolean isAnimation = false;    public RoundView(Context context) {        this(context, null);    }    public RoundView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr, 0);    }    public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView);        mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0);        mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB"));        mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0);        mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9"));        mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80"));        mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0);        mTextColor = typedArray.getColor(R.styleable.RoundView_text_color, Color.parseColor("#f90aa9"));        mTextSize = typedArray.getDimension(R.styleable.RoundView_text_size, dp2px(context, 40));        arrowSize = dp2px(context, 7);        typedArray.recycle();        init();    }    /**     * 初始化     */    private void init() {        mRingPaint = new Paint();        mRingPaint.setAntiAlias(true);// 抗锯齿效果        mRingPaint.setStyle(Paint.Style.STROKE);        mRingPaint.setColor(mRingColor);// 背景灰        // 圆环紫色渐变色        arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor};        mProgressRingPaint = new Paint();        mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果        mProgressRingPaint.setStyle(Paint.Style.STROKE);        mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头        mProgressRingPaint.setStrokeWidth(mProgressRingWidth);        mTextPaint = new Paint();        mTextPaint.setAntiAlias(true);// 抗锯齿效果        mTextPaint.setStyle(Paint.Style.FILL);        mTextPaint.setTextAlign(Align.CENTER);        mTextPaint.setColor(mTextColor);// 字体颜色        mTextPaint.setTextSize(mTextSize);        mArrowPaint = new Paint();        mArrowPaint.setAntiAlias(true);// 抗锯齿效果        mArrowPaint.setStyle(Paint.Style.FILL);        mArrowPaint.setColor(Color.parseColor("#000000"));        mArrowPaint.setStrokeCap(Cap.ROUND);        mArrowPaint.setStrokeWidth(5);        barAnimation = new BarAnimation();        barAnimation.setAnimationListener(this);        scaleAnimation = new ScaleAnimation();        scaleAnimation.setDuration(100);    }    /**     * 设置新的角度     * newAngle:角度     */    public void setAngle(final float newAngle) {        setAngle(newAngle, true);    }    /**     * 设置新的角度     * newAngle:角度     * isScale:是否抖动     */    public void setAngle(final float newAngle, boolean isScale) {        if (!isAnimation) {            if (isScale) {                scaleAnimation.setAnimationListener(new AnimationListener() {                    @Override                    public void onAnimationStart(Animation animation) {                    }                    @Override                    public void onAnimationEnd(Animation animation) {                        changeAngle(newAngle);                    }                    @Override                    public void onAnimationRepeat(Animation animation) {                    }                });                startAnimation(scaleAnimation);            } else {                changeAngle(newAngle);            }        }    }    private void changeAngle(float newAngle) {        mOldAngle = mNewAngle;        mNewAngle = newAngle;        //计算动画时间        int duration = (int) (Math.abs(mNewAngle - mOldAngle) * 15);        barAnimation.setDuration(duration);        //动画差值器        barAnimation.setInterpolator(new DecelerateInterpolator());        startAnimation(barAnimation);    }    @SuppressLint("DrawAllocation")    @Override    public void onDraw(Canvas canvas) {        // 圆心坐标,当前View的中心        float x = getWidth() / 2;        float y = getHeight() / 2;        //如果未设置半径,则半径的值为view的宽、高一半的较小值        float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius;        //圆环的宽度默认为半径的1/5        float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth;        //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面        radius = radius - ringWidth / 2;        mRingPaint.setStrokeWidth(ringWidth);        // 底环        canvas.drawCircle(x, y, radius, mRingPaint);        //----绘制圆环(最底部)结束------        //----绘制进度圆环------        // 设置渐变色        if (mProgressRingShader == null) {            mProgressRingShader = new SweepGradient(x, y, arcColors, null);            mProgressRingPaint.setShader(mProgressRingShader);        }        //计算进度圆环宽度,默认和底部圆滑一样大        float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth;        mProgressRingPaint.setStrokeWidth(progressRingWidth);        float left = x - radius;        float top = y - radius;        float right = x + radius;        float bottom = y + radius;        // 旋转画布90度+笔头半径转过的角度        double radian = radianToAngle(radius);        double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+        canvas.save();        canvas.rotate((float) degrees, x, y);        canvas.drawArc(new RectF(left, top, right, bottom), (float) radian, mOldAngle + mSweepAngle, false, mProgressRingPaint);        canvas.restore();        //----绘制进度圆环结束------        //----绘制箭头开始------        //画布恢复到上一次save到状态,也就是旋转的画布        //将我们要画箭头的位置旋转到顶部,方便我们计算箭头的坐标,绘制完箭头在将画布恢复        canvas.save();        double hudu = 2 * Math.PI / 360 * (mOldAngle + mSweepAngle);        double radians = Math.toDegrees(hudu);        // 旋转画布        canvas.rotate((float) radians, x, y);        //绘制箭头        canvas.drawLine(x - arrowSize, y - radius, x + arrowSize, y - radius, mArrowPaint);        canvas.drawLine(x + arrowSize, y - radius, x, y - radius - arrowSize, mArrowPaint);        canvas.drawLine(x + arrowSize, y - radius, x, y - radius + arrowSize, mArrowPaint);        canvas.restore();        //----绘制箭头结束------        //----绘制百分比字体开始------        int percentage = (int) ((mOldAngle + mSweepAngle) / 360 * 100);        float sizeHeight = getFontHeight();        canvas.drawText(percentage + "%", x, y + sizeHeight / 2, mTextPaint);        //----绘制百分比字体结束------        super.onDraw(canvas);    }    public float getFontHeight() {        FontMetrics fm = mTextPaint.getFontMetrics();        return fm.descent - fm.ascent;    }    /**     * 已知圆半径和切线长求弧长公式     *     * @param radios     * @return     */    private double radianToAngle(float radios) {        double aa = mProgressRingWidth / 2 / radios;        double asin = Math.asin(aa);        double radian = Math.toDegrees(asin);        return radian;    }    /**     * 抖动(缩放动画)     */    public class ScaleAnimation extends Animation {        private int centerX;        private int centerY;        public ScaleAnimation() {        }        @Override        public void initialize(int width, int height, int parentWidth,                               int parentHeight) {            super.initialize(width, height, parentWidth, parentHeight);            centerX = width / 2;            centerY = height / 2;        }        @Override        protected void applyTransformation(float interpolatedTime, Transformation t) {            super.applyTransformation(interpolatedTime, t);            if (interpolatedTime < 0.25f) {                // 1-1.1                float ss = interpolatedTime * 0.4f + 1f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.25f && interpolatedTime < 0.5f) {                // 1.1-1                float ss = (0.5f - interpolatedTime) * 0.4f + 1f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.5f && interpolatedTime < 0.75f) {                // 1-0.9                float ss = (0.75f - interpolatedTime) * 0.4f + 0.9f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            } else if (interpolatedTime >= 0.75f && interpolatedTime <= 1f) {                // 0.9-1                float ss = interpolatedTime * 0.4f + 0.6f;                Matrix matrix = t.getMatrix();                matrix.setScale(ss, ss, centerX, centerY);            }        }    }    /**     * 进度条动画     */    public class BarAnimation extends Animation {        public BarAnimation() {        }        /**         * 然后调用postInvalidate()不停的绘制view。         */        @Override        protected void applyTransformation(float interpolatedTime,                                           Transformation t) {            super.applyTransformation(interpolatedTime, t);            if (mNewAngle - mOldAngle >= 0) {                // 正向                mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle);            } else {                // 逆向                mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle);            }            postInvalidate();        }    }    @Override    public void onAnimationStart(Animation animation) {        isAnimation = true;    }    @Override    public void onAnimationEnd(Animation animation) {        isAnimation = false;    }    @Override    public void onAnimationRepeat(Animation animation) {        // TODO Auto-generated method stub    }    private int dp2px(Context context, float dpVal) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                dpVal, context.getResources().getDisplayMetrics());    }}
原创粉丝点击