學了一段時間的自定義view了,現在回顧一下關于貝塞爾曲線的用法。不說廢話,直接撸代碼。
首先在attrs中定義一個命名空間
<declare-styleable name="CircleWaveView">
<attr name="waveColor" format="color"></attr>
<attr name="waveHight" format="dimension"></attr>
<attr name="waveBackColor" format="color"></attr>
<attr name="innerRadius" format="dimension"></attr>
<attr name="outCircleWidth" format="dimension"></attr>
<attr name="outCircleColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="textColor" format="color"></attr>
<attr name="waveWidth" format="dimension"></attr>
</declare-styleable>
然後是要用到的一些基本資料
private int waveColor;//波浪的顔色
private float waveHight;//波浪的高度
private int waveBackColor;//波浪的後部的顔色
private float innerRadius;//内部圓的半徑,即波浪要顯示的區域
private float outCircleWidth;//外部圓環的寬度
private int outCircleColor;//外部圓環的顔色
private float textSize;//字型大小
private int textColor;//字型顔色
private float waveWidth;//波浪的寬度
private float centerX,centerY;//view中心點
private float progress=0.3f;//繪制的進度
private float wholeMoveY,startY;//Y軸上總的移動距離與初始位置
private int maxProgress=100;//最大的進度
private int curProgress=50;//目前的進度
private float percentage=0.5f;//目前進度的百分比
構造方法裡擷取基本參數
public CircleWaveView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.CircleWaveView);
waveColor=typedArray.getColor(R.styleable.CircleWaveView_waveColor, Color.WHITE);
waveHight=typedArray.getDimension(R.styleable.CircleWaveView_waveHight,);
waveBackColor=typedArray.getColor(R.styleable.CircleWaveView_waveBackColor,Color.GRAY);
innerRadius=typedArray.getDimension(R.styleable.CircleWaveView_innerRadius,);
outCircleWidth=typedArray.getDimension(R.styleable.CircleWaveView_outCircleWidth,);
outCircleColor=typedArray.getColor(R.styleable.CircleWaveView_outCircleColor,Color.BLUE);
textSize=typedArray.getDimension(R.styleable.CircleWaveView_textSize,);
textColor=typedArray.getColor(R.styleable.CircleWaveView_textColor,Color.BLUE);
waveWidth=typedArray.getDimension(R.styleable.CircleWaveView_waveWidth,);
initPaint();
typedArray.recycle();
}
在onSizeChanged中計算出中心點,波浪上下移動的最大距離,波浪開始時Y方向位置。
波浪上下移動的最大距離:中心圓的直徑(*innerRadius)+進度零時下一一個波浪的高度(waveHight)+
進度為時上移一個波浪的高度(waveHight)
波浪開始時Y:開始時進度為零,繪制從中心點下移一個半徑到達圓的底部,在下移一個波浪的高度,此時便看不到波浪了。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX=w/f;
centerY=h/f;
wholeMoveY=*(innerRadius+waveHight);//這是波浪上下移動的最大距離
startY=centerY+innerRadius+waveHight;//這是波浪最初的位置即progress=0,
}
繪制波浪的背景圓與外部環形進度
/**
* 繪制波浪的背景
* @param canvas
*/
private void drawCircles(Canvas canvas){
canvas.drawCircle(centerX,centerY,innerRadius,centerPaint);
if (out==null){
out=new RectF();
out.left=centerX-innerRadius-outCircleWidth/;
out.top=centerY-innerRadius-outCircleWidth/;
out.right=centerX+innerRadius+outCircleWidth/;
out.bottom=centerY+innerRadius+outCircleWidth/;
}
canvas.drawArc(out,,f*curProgress/maxProgress,false,outPaint);
}
開始繪制水波紋,這裡因為繪制的水波紋是圓形的,而我們的path繪制的路徑後顯示的是一個不規則的類似四邊形的一個圖形是以要裁剪一下畫布,将畫布裁剪呈圓形,這樣繪制出來的曲線邊緣呈現出來就是圓形的了。
/**
* 在view中心的圓上開始繪制水波紋,即繪制一段貝塞爾曲線
* @param canvas
*/
private void drawWave(Canvas canvas){
if(clipPath==null){
path=new Path();
clipPath=new Path();
//要裁剪的圓形畫布,innerRadius是在xml檔案中定義的内部的圓的半徑
//即波浪最終顯示的範圍
clipPath.addCircle(centerX,centerY,innerRadius, Path.Direction.CCW);
}
canvas.save();
//裁剪畫布在圓形的畫布上繪制曲線
canvas.clipPath(clipPath);
path.reset();
//預設progress=0,也就是從-waveWidth處開始繪制,多繪制了一個波紋的長度用于後邊的移動
curX=centerX-innerRadius-waveWidth*(-progress);
curY=startY-wholeMoveY*(curProgress*f/maxProgress);
path.moveTo(curX,curY);
for (float i=-waveWidth;i<centerY+innerRadius+waveWidth;i+=waveWidth){
path.rQuadTo(waveWidth/,waveHight,waveWidth/,);
path.rQuadTo(waveWidth/,-waveHight,waveWidth/,);
}
path.lineTo(getWidth(),getHeight());
path.lineTo(,getHeight());
path.close();
canvas.drawPath(path,wavePaint);
canvas.restore();
}
最後是繪制文字,沒啥好說的就是注意一下文字的中心要與view的中心對齊。
private void drawText(Canvas canvas){
String text=(int)(percentage*100)+"%";
Paint.FontMetrics metrics=textPaint.getFontMetrics();
//擷取文字的長度
float textWidth=textPaint.measureText(text);
//計算文字的高度,這裡計算可能有點小問題不過我們需要的隻是一個大約高度,不影響,不糾結,重點是貝塞爾曲線的繪制
float textHight=metrics.descent-metrics.top;
//計算文字的位置,我們讓文字的中心與view的中心重合
int textX= (int) (centerX-textWidth/);
int textY= (int) (centerY+textHight/);
canvas.drawText(text,textX,textY,textPaint);
}
要想讓波浪動起來就要用一個ValueAnimator來不停的改變繪制的進度progress,重新整理界面重新繪制界面。
public void setWaveAnim(boolean run){
if (waveAnim==null){
waveAnim=ValueAnimator.ofFloat(,);
waveAnim.setDuration();
waveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress=animation.getAnimatedFraction();
invalidate();
}
});
waveAnim.setInterpolator(new LinearInterpolator());
//無限循環
waveAnim.setRepeatCount(ValueAnimator.INFINITE);
}
if (run){
if (!waveAnim.isRunning()){
waveAnim.start();
}
}else {
if (waveAnim.isRunning()){
waveAnim.cancel();
}
}
}
最後附上全部代碼,複制粘貼即可,别忘了在開頭的命名空間
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.LinearInterpolator;
/**
* @description: <描述目前版本功能>
* <p>
* </p>
* @author: ZBK
* @date: 2017-05-08 16:31
*/
public class CircleWaveView extends View{
public static final String TAG="CircleWaveView";
private int waveColor;//波浪的顔色
private float waveHight;//波浪的高度
private int waveBackColor;//波浪的後部的顔色
private float innerRadius;//内部圓的半徑,即波浪要顯示的區域
private float outCircleWidth;//外部圓環的寬度
private int outCircleColor;//外部圓環的顔色
private float textSize;//字型大小
private int textColor;//字型顔色
private float waveWidth;//波浪的寬度
private float centerX,centerY;//view中心點
private float progress=f;//繪制的進度
private float wholeMoveY,startY;//Y軸上總的移動距離與初始位置
private int maxProgress=;//最大的進度
private int curProgress=;//目前的進度
private float percentage=f;//目前進度的百分比
private ValueAnimator waveAnim,riseAnim;
private Paint wavePaint,textPaint,centerPaint,outPaint;
public CircleWaveView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.CircleWaveView);
waveColor=typedArray.getColor(R.styleable.CircleWaveView_waveColor, Color.WHITE);
waveHight=typedArray.getDimension(R.styleable.CircleWaveView_waveHight,);
waveBackColor=typedArray.getColor(R.styleable.CircleWaveView_waveBackColor,Color.GRAY);
innerRadius=typedArray.getDimension(R.styleable.CircleWaveView_innerRadius,);
outCircleWidth=typedArray.getDimension(R.styleable.CircleWaveView_outCircleWidth,);
outCircleColor=typedArray.getColor(R.styleable.CircleWaveView_outCircleColor,Color.BLUE);
textSize=typedArray.getDimension(R.styleable.CircleWaveView_textSize,);
textColor=typedArray.getColor(R.styleable.CircleWaveView_textColor,Color.BLUE);
waveWidth=typedArray.getDimension(R.styleable.CircleWaveView_waveWidth,);
initPaint();
typedArray.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX=w/f;
centerY=h/f;
wholeMoveY=*(innerRadius+waveHight);//這是波浪上下移動的最大距離
startY=centerY+innerRadius+waveHight;//這是波浪最初的位置即progress=0,
}
private void initPaint(){
wavePaint=new Paint();
textPaint=new Paint();
centerPaint=new Paint();
outPaint=new Paint();
wavePaint.setStyle(Paint.Style.FILL_AND_STROKE);
centerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
outPaint.setStyle(Paint.Style.STROKE);
outPaint.setStrokeWidth(outCircleWidth);
wavePaint.setAntiAlias(true);
centerPaint.setAntiAlias(true);
outPaint.setAntiAlias(true);
textPaint.setAntiAlias(true);
wavePaint.setColor(waveColor);
centerPaint.setColor(waveBackColor);
outPaint.setColor(outCircleColor);
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
outPaint.setStrokeCap(Paint.Cap.ROUND);
outPaint.setStrokeJoin(Paint.Join.ROUND);
}
private RectF out;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircles(canvas);
drawWave(canvas);
canvas.drawArc(out,,f*curProgress/maxProgress,false,outPaint);
drawText(canvas);
}
private void drawText(Canvas canvas){
String text=(int)(percentage*)+"%";
Paint.FontMetrics metrics=textPaint.getFontMetrics();
//擷取文字的長度
float textWidth=textPaint.measureText(text);
//計算文字的高度,這裡計算可能有點小問題不過我們需要的隻是一個大約高度,不影響,不糾結,重點是貝塞爾曲線的繪制
float textHight=metrics.descent-metrics.top;
//計算文字的位置,我們讓文字的中心與view的中心重合
int textX= (int) (centerX-textWidth/);
int textY= (int) (centerY+textHight/);
canvas.drawText(text,textX,textY,textPaint);
}
private Path path,clipPath;
private float curX,curY;
/**
* 在view中心的圓上開始繪制水波紋,即繪制一段貝塞爾曲線
* @param canvas
*/
private void drawWave(Canvas canvas){
if(clipPath==null){
path=new Path();
clipPath=new Path();
//要裁剪的圓形畫布,innerRadius是在xml檔案中定義的内部的圓的半徑
//即波浪最終顯示的範圍
clipPath.addCircle(centerX,centerY,innerRadius, Path.Direction.CCW);
}
canvas.save();
//裁剪畫布在圓形的畫布上繪制曲線
canvas.clipPath(clipPath);
path.reset();
//預設progress=0,也就是從-waveWidth處開始繪制,多繪制了一個波紋的長度用于後邊的移動
curX=centerX-innerRadius-waveWidth*(-progress);
curY=startY-wholeMoveY*(curProgress*f/maxProgress);
path.moveTo(curX,curY);
for (float i=-waveWidth;i<centerY+innerRadius+waveWidth;i+=waveWidth){
path.rQuadTo(waveWidth/,waveHight,waveWidth/,);
path.rQuadTo(waveWidth/,-waveHight,waveWidth/,);
}
path.lineTo(getWidth(),getHeight());
path.lineTo(,getHeight());
path.close();
canvas.drawPath(path,wavePaint);
canvas.restore();
}
/**
* 繪制波浪的背景
* @param canvas
*/
private void drawCircles(Canvas canvas){
canvas.drawCircle(centerX,centerY,innerRadius,centerPaint);
if (out==null){
out=new RectF();
out.left=centerX-innerRadius-outCircleWidth/;
out.top=centerY-innerRadius-outCircleWidth/;
out.right=centerX+innerRadius+outCircleWidth/;
out.bottom=centerY+innerRadius+outCircleWidth/;
}
}
public int getMaxProgress() {
return maxProgress;
}
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
}
public int getCurProgress() {
return curProgress;
}
public void setCurProgress(int curProgress) {
if (curProgress<)return;
if (riseAnim!=null){
riseAnim.cancel();
}
riseAnim=generAnim(curProgress,);
riseAnim.start();
}
public void setWaveAnim(boolean run){
if (waveAnim==null){
waveAnim=ValueAnimator.ofFloat(,);
waveAnim.setDuration();
waveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress=animation.getAnimatedFraction();
invalidate();
}
});
waveAnim.setInterpolator(new LinearInterpolator());
//無限循環
waveAnim.setRepeatCount(ValueAnimator.INFINITE);
}
if (run){
if (!waveAnim.isRunning()){
waveAnim.start();
}
}else {
if (waveAnim.isRunning()){
waveAnim.cancel();
}
}
}
/**
*
* @return
*/
private ValueAnimator generAnim(int finalProgress,long du){
double start=f*curProgress/maxProgress;
double end=f*finalProgress/maxProgress;
Log.i(TAG,"start:"+start+" end:"+end+" finalProgress:"+finalProgress+" curProgress:"+curProgress);
final ValueAnimator animator=ValueAnimator.ofFloat((float) start,(float)end);
animator.setDuration(du);
animator.setInterpolator(new AnticipateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percentage=(float)animation.getAnimatedValue();
curProgress= (int)(maxProgress*percentage);
Log.i(TAG,"curProgress:"+curProgress+" animation.getAnimatedFraction()"+animation.getAnimatedFraction());
postInvalidate();
}
});
return animator;
}
}
用法
public void wave(View view){
CircleWaveView waveView= (CircleWaveView) view;
waveView.setCurProgress(i-=);
waveView.setWaveAnim(true);
}