能量球效果(贝塞尔曲线)

在项目中需要用能量球来展示活动余额的剩余量,如下图的效果:
这里写图片描述
看到效果图,首先就会想怎么实现这个波浪的效果,其实这就是两个波浪线一直在左右移动,然后给你一个一直在那浪啊浪的错觉~~~说到曲线,脑阔中第一个蹦出来的就是贝塞尔曲线。要用贝塞尔曲线,首先还是要确定控制点,先看一张图(没有好的作图工具,凑合着看了- -!):
这里写图片描述
如上图,一个完整的波要这5个点来确定,其中B和D点就是控制点,像我这个是将view的宽度均分成4段,这些点的坐标很容易算出来了。
下面贴出画波的主要代码:

    private float waveHeightPercent = 0.7f;   //波的高度百分比
    private float waveTranslateValue = 0f;    //波水平移动的比例
    private float waveAmplitude;              //波的振幅
    private int waveCount = 4;                //波的数量 

    //这边是为了保证view为正方形,方便后面圆形好弄
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int minSize = Math.min(widthSize, heightSize);
        setMeasuredDimension(minSize, minSize);
    }

    protected void onDraw(Canvas canvas) {
        float levelHeight = (1 - waveHeightPercent) * height;  //波实际高度
        float specWidth = width/waveCount;                     //四分之一波长
        float translateX = width * waveTranslateValue;         //水平移动的实际距离

        //这两个方法是计算波形路径
        setFrontWavePath(levelHeight, specWidth, translateX);
        setBehindWavePath(levelHeight, specWidth, translateX);
        //画后面的波
        wavePaint.setColor(ContextCompat.getColor(context, R.color.behind_color));
        canvas.drawPath(wavePathBehind, wavePaint);
        //画前面的波
        wavePaint.setColor(ContextCompat.getColor(context, R.color.front_color));
        canvas.drawPath(wavePathFront, wavePaint);
    }

下面只贴出setFrontWavePath()这个方法,setBehindWavePath()和它类似,只不过波的方向是反过来的:

private void setFrontWavePath(float levelHeight, float specWidth, float translateX){
        wavePathFront.reset();

        wavePathFront.moveTo(0 - translateX, height);
        wavePathFront.lineTo(0 - translateX, levelHeight);
        for(int i=1;i<=waveCount;i++){
            float controlX = specWidth * (i*2 - 1) - translateX;
            float controlY = i%2 != 0 ? levelHeight - waveAmplitude : levelHeight + waveAmplitude;
            float toX = specWidth * (2 * i) - translateX;
            wavePathFront.quadTo(controlX, controlY, toX, levelHeight);
        }
        wavePathFront.lineTo(specWidth * waveCount + translateX, height);
        wavePathFront.close();
    }

这边画波浪,用的二阶贝塞尔曲线,最后路径闭合,是为了填充颜色。
现在运行一下,基础波形就出来了,入下图:
这里写图片描述
接下来就是加入动画效果,让波来回移动,代码如下:

   public void waveAnimate(){
        ObjectAnimator transAnim = ObjectAnimator.ofFloat(this, "waveTranslateValue", 0, 1);
        transAnim.setInterpolator(new LinearInterpolator());
        transAnim.setDuration(1000);
        transAnim.setRepeatCount(ValueAnimator.INFINITE);

        ObjectAnimator upAnim = ObjectAnimator.ofFloat(this, "waveHeightPercent", 0, waveHeightPercent);
        upAnim.setDuration(3000);

        animatorSet.playTogether(transAnim, upAnim);
        animatorSet.start();
    }

这边用属性动画来完成波的移动,waveTranslateValue这个属性,控制波的水平移动;waveHeightPercent属性,控制波的高度,现在来看下效果:
这里写图片描述
到这里,剩下最后一个就是把矩形变成圆形,这边首先想到的就是裁剪画布,那就直接写上代码如下:

clipPath.addCircle(circleX, circleY, circleRadius, Path.Direction.CW);
canvas.clipPath(clipPath);

圆的坐标和半径根据view的宽高很容易得到,然后在onDraw中调用canvas.clipPath(clipPath),实际效果如下:
这里写图片描述
从图中看到,圆有锯齿,裁剪没有用到Paint类,不好设置抗锯齿,所以效果不是很好。
下面用Paint 的Xfermode模式来修改一下,代码如下:

    //初始化Xfermode
    xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

    protected void onDraw(Canvas canvas) {
        float levelHeight = (1 - waveHeightPercent) * height;
        float specWidth = width/waveCount;
        float translateX = width * waveTranslateValue;

        canvas.drawCircle(circleX, circleY, circleRadius, bgPaint);
        //创建新的图层,进行图像合成
        int saveCount = canvas.saveLayer(0, 0, width, height, wavePaint, Canvas.ALL_SAVE_FLAG);

        setFrontWavePath(levelHeight, specWidth, translateX);
        setBehindWavePath(levelHeight, specWidth, translateX);

        wavePaint.setColor(ContextCompat.getColor(context, R.color.behind_color));
        canvas.drawPath(wavePathBehind, wavePaint);

        wavePaint.setColor(ContextCompat.getColor(context, R.color.front_color));
        canvas.drawPath(wavePathFront, wavePaint);

        //设置Paint的Xfermode
        circlePaint.setXfermode(xfermode);
        if(circleBitmap == null){
            circleBitmap = createCircleBitmap();
        }
        canvas.drawBitmap(circleBitmap,circleX - circleRadius, 0, circlePaint);
        circlePaint.setXfermode(null);
        //释放图层
        canvas.restoreToCount(saveCount);
    }

    //创建圆形Bitmap
    private Bitmap createCircleBitmap(){
        Bitmap bitmap = Bitmap.createBitmap((int)circleDiameter, (int)circleDiameter, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(circleRadius, circleRadius,circleRadius, paint);
        return bitmap;
    }

上面onDraw中新增的代码都给出了注释,主要就是画圆,然后进行图像合成,改之后的效果如下:
这里写图片描述
这下就没有锯齿了,到这就基本实现能量球效果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值