自定義圓形水波紋View
阿新 • • 發佈:2018-12-27
學了一段時間的自定義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,20);
waveBackColor=typedArray.getColor(R.styleable.CircleWaveView_waveBackColor,Color.GRAY);
innerRadius=typedArray.getDimension(R.styleable.CircleWaveView_innerRadius,50);
outCircleWidth=typedArray.getDimension(R.styleable.CircleWaveView_outCircleWidth,10);
outCircleColor=typedArray.getColor(R.styleable.CircleWaveView_outCircleColor,Color.BLUE);
textSize=typedArray.getDimension(R.styleable.CircleWaveView_textSize,20);
textColor=typedArray.getColor(R.styleable.CircleWaveView_textColor,Color.BLUE);
waveWidth=typedArray.getDimension(R.styleable.CircleWaveView_waveWidth,100);
initPaint();
typedArray.recycle();
}
在onSizeChanged中計算出中心點,波浪上下移動的最大距離,波浪開始時Y方向位置。
波浪上下移動的最大距離:中心圓的直徑(2*innerRadius)+進度零時下一一個波浪的高度(waveHight)+
進度為100時上移一個波浪的高度(waveHight)
波浪開始時Y:開始時進度為零,繪製從中心點下移一個半徑到達圓的底部,在下移一個波浪的高度,此時便看不到波浪了。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX=w/2.0f;
centerY=h/2.0f;
wholeMoveY=2*(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/4;
out.top=centerY-innerRadius-outCircleWidth/4;
out.right=centerX+innerRadius+outCircleWidth/4;
out.bottom=centerY+innerRadius+outCircleWidth/4;
}
canvas.drawArc(out,0,360.0f*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*(1-progress);
curY=startY-wholeMoveY*(curProgress*1.0f/maxProgress);
path.moveTo(curX,curY);
for (float i=-waveWidth;i<centerY+innerRadius+waveWidth;i+=waveWidth){
path.rQuadTo(waveWidth/4,waveHight,waveWidth/2,0);
path.rQuadTo(waveWidth/4,-waveHight,waveWidth/2,0);
}
path.lineTo(getWidth(),getHeight());
path.lineTo(0,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/2);
int textY= (int) (centerY+textHight/2);
canvas.drawText(text,textX,textY,textPaint);
}
要想讓波浪動起來就要用一個ValueAnimator來不停的改變繪製的進度progress,重新整理介面重新繪製介面。
public void setWaveAnim(boolean run){
if (waveAnim==null){
waveAnim=ValueAnimator.ofFloat(0,1);
waveAnim.setDuration(1000);
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=0.3f;//繪製的進度
private float wholeMoveY,startY;//Y軸上總的移動距離與初始位置
private int maxProgress=100;//最大的進度
private int curProgress=50;//當前的進度
private float percentage=0.5f;//當前進度的百分比
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,20);
waveBackColor=typedArray.getColor(R.styleable.CircleWaveView_waveBackColor,Color.GRAY);
innerRadius=typedArray.getDimension(R.styleable.CircleWaveView_innerRadius,50);
outCircleWidth=typedArray.getDimension(R.styleable.CircleWaveView_outCircleWidth,10);
outCircleColor=typedArray.getColor(R.styleable.CircleWaveView_outCircleColor,Color.BLUE);
textSize=typedArray.getDimension(R.styleable.CircleWaveView_textSize,20);
textColor=typedArray.getColor(R.styleable.CircleWaveView_textColor,Color.BLUE);
waveWidth=typedArray.getDimension(R.styleable.CircleWaveView_waveWidth,100);
initPaint();
typedArray.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX=w/2.0f;
centerY=h/2.0f;
wholeMoveY=2*(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,0,360.0f*curProgress/maxProgress,false,outPaint);
drawText(canvas);
}
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/2);
int textY= (int) (centerY+textHight/2);
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*(1-progress);
curY=startY-wholeMoveY*(curProgress*1.0f/maxProgress);
path.moveTo(curX,curY);
for (float i=-waveWidth;i<centerY+innerRadius+waveWidth;i+=waveWidth){
path.rQuadTo(waveWidth/4,waveHight,waveWidth/2,0);
path.rQuadTo(waveWidth/4,-waveHight,waveWidth/2,0);
}
path.lineTo(getWidth(),getHeight());
path.lineTo(0,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/4;
out.top=centerY-innerRadius-outCircleWidth/4;
out.right=centerX+innerRadius+outCircleWidth/4;
out.bottom=centerY+innerRadius+outCircleWidth/4;
}
}
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<0)return;
if (riseAnim!=null){
riseAnim.cancel();
}
riseAnim=generAnim(curProgress,600);
riseAnim.start();
}
public void setWaveAnim(boolean run){
if (waveAnim==null){
waveAnim=ValueAnimator.ofFloat(0,1);
waveAnim.setDuration(1000);
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=1.0f*curProgress/maxProgress;
double end=1.0f*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-=10);
waveView.setWaveAnim(true);
}