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

来源:互联网 发布:快手特效视频软件 编辑:程序博客网 时间:2024/06/05 10:35

在项目中需要用能量球来展示活动余额的剩余量,如下图的效果:
这里写图片描述
看到效果图,首先就会想怎么实现这个波浪的效果,其实这就是两个波浪线一直在左右移动,然后给你一个一直在那浪啊浪的错觉~~~说到曲线,脑阔中第一个蹦出来的就是贝塞尔曲线。要用贝塞尔曲线,首先还是要确定控制点,先看一张图(没有好的作图工具,凑合着看了- -!):
这里写图片描述
如上图,一个完整的波要这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中新增的代码都给出了注释,主要就是画圆,然后进行图像合成,改之后的效果如下:
这里写图片描述
这下就没有锯齿了,到这就基本实现能量球效果。

原创粉丝点击