美文网首页自定义view自定义viewAndroid自定义控件
Android自定义View之酷炫吊炸天的圆环(二)

Android自定义View之酷炫吊炸天的圆环(二)

作者: hellsam | 来源:发表于2016-01-08 20:46 被阅读6455次

    先看下最终的效果

    静态:

    动态:

    在线mp4 to gif http://ezgif.com/video-to-gif

    开始实现

    新建一个DoughnutProgress继承View

        public class DoughnutProgress extends View {
        
        }
    

    先给出一些常量、变量以及公共方法的代码,方便理解后面的代码

        private static final int DEFAULT_MIN_WIDTH = 400; //View默认最小宽度
        private static final int RED = 230, GREEN = 85, BLUE = 35; //基础颜色,这里是橙红色
        private static final int MIN_ALPHA = 30; //最小不透明度
        private static final int MAX_ALPHA = 255; //最大不透明度
        private static final float doughnutRaduisPercent = 0.65f; //圆环外圆半径占View最大半径的百分比
        private static final float doughnutWidthPercent = 0.12f; //圆环宽度占View最大半径的百分比
        private static final float MIDDLE_WAVE_RADUIS_PERCENT = 0.9f; //第二个圆出现时,第一个圆的半径百分比
        private static final float WAVE_WIDTH = 5f; //波纹圆环宽度
    
        //圆环颜色
        private static int[] doughnutColors = new int[]{
                Color.argb(MAX_ALPHA, RED, GREEN, BLUE),
                Color.argb(MIN_ALPHA, RED, GREEN, BLUE),
                Color.argb(MIN_ALPHA, RED, GREEN, BLUE)};
    
        private Paint paint = new Paint(); //画笔
        private float width; //自定义view的宽度
        private float height; //自定义view的高度
        private float currentAngle = 0f; //当前旋转角度
        private float raduis; //自定义view的最大半径
        private float firstWaveRaduis;
        private float secondWaveRaduis;
        
        //
        private void resetParams() {
            width = getWidth();
            height = getHeight();
            raduis = Math.min(width, height)/2;
        }
    
        private void initPaint() {
            paint.reset();
            paint.setAntiAlias(true);
        }
    

    重写onMeasure方法,为什么要重写onMeasure方法可以看我的上一篇文章,点这里

        /**
         * 当布局为wrap_content时设置默认长宽
         *
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
        }
    
        private int measure(int origin) {
            int result = DEFAULT_MIN_WIDTH;
            int specMode = MeasureSpec.getMode(origin);
            int specSize = MeasureSpec.getSize(origin);
            if (specMode == MeasureSpec.EXACTLY) {
                result = specSize;
            } else {
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }
    

    下面就是最重要的重写onDraw方法,大致流程如下

    在开始绘制之前,先初始化widthheightraduis, 以及将View的中心作为原点

        resetParams();
    
        //将画布中心设为原点(0,0), 方便后面计算坐标
        canvas.translate(width / 2, height / 2);
    

    实现静态的渐变圆环

    • 画渐变圆环

          float doughnutWidth = raduis * doughnutWidthPercent;//圆环宽度
          //圆环外接矩形
          RectF rectF = new RectF(
          -raduis * doughnutRaduisPercent, 
          -raduis * doughnutRaduisPercent, 
          raduis * doughnutRaduisPercent, 
          raduis * doughnutRaduisPercent);
          initPaint();
          paint.setStrokeWidth(doughnutWidth);
          paint.setStyle(Paint.Style.STROKE);
          paint.setShader(new SweepGradient(0, 0, doughnutColors, null));
          canvas.drawArc(rectF, 0, 360, false, paint);
      

    通过修改doughnutColors可以实现不同的渐变效果

    • 画圆环旋转头部的圆

          //画旋转头部圆
          initPaint();
          paint.setStyle(Paint.Style.FILL);
          paint.setColor(Color.argb(MAX_ALPHA, RED, GREEN, BLUE));
          canvas.drawCircle(raduis * doughnutRaduisPercent, 0, doughnutWidth / 2, paint);
      

    此时运行代码得到效果如下图:

    我们还可以在绘制圆环之前通过旋转画布得到不同初始状态

        canvas.rotate(-45, 0, 0);
    
        canvas.rotate(-180, 0, 0);
    

    此时聪明的你应该已经想到怎么让这个圆环旋转起来了吧_

    对!正如你所想的,就是通过canvas.rotate方法不停地旋转画布(这个“地”是这么用的吧o(╯□╰)o)

    让圆环旋转起来

    在绘制圆环之前加上下面的代码:

        //转起来
        canvas.rotate(-currentAngle, 0, 0);
        if (currentAngle >= 360f){
            currentAngle = currentAngle - 360f;
        } else{
            currentAngle = currentAngle + 2f;
        }
    

    然后再让一个线程循环刷新就好了

    private Thread thread = new Thread(){
        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                postInvalidate();
            }
        }
    };
    

    试试!转起来了吗O(∩_∩)O~

    下面是比较有意思的部分,实现类似水波涟漪的效果

    分析水波涟漪效果的实现原理(画了张草图方便理解):

    假设淡黄色背景区域为整个View的大小

    黑色圆圈为View内的最大圆(半径为R3)

    橙色圆环代表渐变圆环

    红色圆圈代表圆环的外圆(半径为R1)

    紫色圆圈是干啥子的,待会儿再介绍~(半径为R2)

    通过观察实现的最终效果,可以发现有个圆的半径从R1逐渐增大R3,不透明度逐渐减小到0。

    那是不是这样周而复始就可以实现最终的效果了呢?

    没那么简单。。。

    仔细观察发现,第二个圆不是等到第一个圆的半径增大到R3才开始出现的,而是在将要消失的时候就出现了,有一段时间是两个圆同时存在的。

    那么我们就假设当第一个圆的半径增大到R2,第二个圆开始出现。

    开始想象两个圆的循环运行模型~~~

    我的方案是:

    绘制两个圆,每个圆的半径都从R1增大到R1+2x(R2-R1),不透明度还是从R1到R3的过程中逐渐变为0,也就是当圆的半径大于R3时,不透明度就为0了(不可见了),将第一个圆半径初始值设为R1,第二个圆半径初始值设为R2。这样两个圆半径同时逐渐增大,当半径大于 R1+2x(R2-R1)时又重新回到R1大小继续增大,就实现了类似水波涟漪的效果了。

        //实现类似水波涟漪效果
        initPaint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
        secondWaveRaduis = calculateWaveRaduis(secondWaveRaduis);
        firstWaveRaduis = calculateWaveRaduis(secondWaveRaduis + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2);
        paint.setColor(Color.argb(calculateWaveAlpha(secondWaveRaduis), RED, GREEN, BLUE));
        canvas.drawCircle(0, 0, secondWaveRaduis, paint); //画第二个圆(初始半径较小的)
    
        initPaint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
        paint.setColor(Color.argb(calculateWaveAlpha(firstWaveRaduis), RED, GREEN, BLUE));
        canvas.drawCircle(0, 0, firstWaveRaduis, paint); //画第一个圆(初始半径较大的)
        
        
        /**
         * 计算波纹圆的半径
         * @param waveRaduis
         * @return
         */
        private float calculateWaveRaduis(float waveRaduis){
            if(waveRaduis < raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2){
                waveRaduis = raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2;
            }
            if(waveRaduis > raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2){
                waveRaduis = waveRaduis - (raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2) + raduis*doughnutWidthPercent/2 + raduis*doughnutRaduisPercent;
            }
                waveRaduis += 0.6f;
            return waveRaduis;
        }
    
        /**
         * 根据波纹圆的半径计算不透明度
         * @param waveRaduis
         * @return
         */
        private int calculateWaveAlpha(float waveRaduis){
            float percent = (waveRaduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2)/(raduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2);
            if(percent >= 1f){
                return 0;
            }else{
                return (int) (MIN_ALPHA*(1f-percent));
            }
        }
    

    全部测试代码下载地址:

    https://github.com/hellsam/DoughnutProgressDemo

    <strong>欢迎留言交流,如有描述不当或错误的地方还请留言告知</strong>

    相关文章

      网友评论

      本文标题:Android自定义View之酷炫吊炸天的圆环(二)

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