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

先看下最终的效果

静态:


动态:


在线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

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



作者:hellsam
链接:http://www.jianshu.com/p/6c5e4dce11aa
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安果移不动

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值