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