效果圖

這裡寫圖片描述

原理分析

首先需要了解的水波紋實現效果,可以在部落格的自定義View專題找到,其實現原理如下

  • 利用貝塞爾曲線繪製螢幕外和螢幕內的sin曲線
  • 利用path將sin曲線的左下角和右下角連線起來成為一塊區域
  • 通過不斷的平移sin曲線,然後平移完一個週期則重新回到原點

這裡寫圖片描述

實現步驟

繪製實現的步驟如下

  • 裁剪畫布為圓形
  • 繪製圓形邊框
  • 繪製波浪區域
  • 繪製進度文字
  • 自動增長進度

1、初始化變數

public class WaveView extends View {

    //View的寬高
    private int width;
    private int height;
    //View的畫筆
    private Paint wavePaint;
    private Paint textPaint;
    private Paint circlePaint;

    //波浪的路徑
    private Path path;
    //sin曲線的長度:一個週期長度
    private int cycle = 160;
    //每次平移的長度,為四分之一個週期
    private int translateX = cycle / 4;
    //sin曲線振幅的高度
    private int waveHeight = 80;
    //sin曲線的起點座標
    private Point startPoint;
    //當前波浪的進度
    private int progress = 0;
    //當前波浪的速度
    private int waveSpeech = 150;
    //是否啟用了自動增長進度
    private boolean isAutoIncrease = false;

    public WaveView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint(context);
    }

    public WaveView(Context context) {
        super(context);
        initPaint(context);
    }

    private void initPaint(Context context) {
        path = new Path();

        wavePaint = new Paint();
        wavePaint.setAntiAlias(true);
        wavePaint.setStyle(Paint.Style.FILL);
        wavePaint.setColor(Color.parseColor("#1998FA"));

        circlePaint = new Paint();
        circlePaint.setStrokeWidth(5);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.parseColor("#1998FA"));

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(50);
        textPaint.setColor(Color.BLUE);
    }
}

2、獲取寬和高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //由於是一個圓形,所以取的值是寬高的最小值
    width = measureSize(400, widthMeasureSpec);
    height = measureSize(400, heightMeasureSpec);
    width = Math.min(width, height);
    height = Math.min(width, height);
    setMeasuredDimension(width, height);
    //初始化起點,為螢幕外的一個週期
    startPoint = new Point(-cycle * 4, 0);
}

/**
 * 測量寬高
 *
 * @param defaultSize
 * @param measureSpec
 * @return
 */
private int measureSize(int defaultSize, int measureSpec) {
    int result = defaultSize;
    int mode = MeasureSpec.getMode(measureSpec);
    int size = MeasureSpec.getSize(measureSpec);
    switch (mode) {
        case MeasureSpec.UNSPECIFIED:
            result = defaultSize;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = size;
            break;
    }
    return result;
}

3、繪製圖形

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //設定內間距
    setPadding(20, 20, 20, 20);
    //裁剪畫布為圓形
    clipCicle(canvas);
    //繪製圓形邊框
    drawCicleBorder(canvas);
    //繪製波浪區域
    drawWavePath(canvas);
    //繪製進度文字
    drawProcessText(canvas);

    //自動增長進度
    if (isAutoIncrease) {
        if (progress >= 100) {
            progress = 0;
        } else {
            progress++;
        }
    }
    //更新UI
    postInvalidateDelayed(waveSpeech);
}

/**
 * 裁剪畫布為圓形
 * 
 * @param canvas
 */
private void clipCicle(Canvas canvas) {
    Path circlePath = new Path();
    circlePath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW);
    canvas.clipPath(circlePath);
}

/**
 * 繪製圓形邊框
 *
 * @param canvas
 */
private void drawCicleBorder(Canvas canvas) {
    canvas.drawPaint(circlePaint);
    canvas.drawCircle(width / 2, height / 2, width / 2, circlePaint);
}

/**
 * 繪製波浪區域
 *
 * @param canvas
 */
private void drawWavePath(Canvas canvas) {
    //根據進度改變起點座標的y值
    startPoint.y = (int) ((1 - (progress / 100.0)) * (height / 2 + width / 2));
    Log.e("TAG", "startPoint.y:" + startPoint.y);
    //移動區域起點
    path.moveTo(startPoint.x, startPoint.y);
    int j = 1;
    //迴圈繪製正弦曲線區域,迴圈兩個週期
    for (int i = 1; i <= 8; i++) {
        if (i % 2 == 0) {
            //波峰
            path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight,
                    startPoint.x + (cycle * 2) * i, startPoint.y);
        } else {
            //波谷
            path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight,
                    startPoint.x + (cycle * 2) * i, startPoint.y);
        }
        j += 2;
    }
    //繪製封閉的區域
    path.lineTo(width, height);//右下角
    path.lineTo(startPoint.x, height);//左下角
    path.lineTo(startPoint.x, startPoint.y);//起點
    path.close();
    //繪製區域
    canvas.drawPath(path, wavePaint);
    path.reset();
    //一開始的起點是在-160,160 = 40 + 40 + 40 + 40,走完一個週期則回到原點
    if (startPoint.x + translateX >= 0) {
        startPoint.x = -cycle * 4;
    } else {
        startPoint.x += translateX;
    }
}

/**
 * 繪製進度文字
 *
 * @param canvas
 */
private void drawProcessText(Canvas canvas) {
    //畫布的大小
    Rect targetRect = new Rect(0, 0, width, height);
    Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
    int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
    // 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX()
    textPaint.setTextAlign(Paint.Align.CENTER);
    canvas.drawText(progress + "%", targetRect.centerX(), baseline, textPaint);
}

4、提供對外API

/**
 * 開啟自動增長
 */
public void startIncrease() {
    isAutoIncrease = true;
    invalidate();
}

/**
 * 設定當前進度
 *
 * @param progress 進度
 */
public void setProgress(int progress) {
    if (progress > 100 || progress < 0)
        throw new RuntimeException(getClass().getName() + "請設定[0,100]之間的值");

    this.progress = progress;
    invalidate();
}

/**
 * 通過動畫設定當前進度
 *
 * @param targetProcess 進度 <=100
 * @param duration      動畫時長
 */
public void setProgress(final int targetProcess, int duration) {
    if (progress > 100 || progress < 0)
        throw new RuntimeException(getClass().getName() + "請設定[0,100]之間的值");

    ValueAnimator progressAnimator = ValueAnimator.ofInt(progress, targetProcess);
    progressAnimator.setDuration(duration);
    progressAnimator.setTarget(progress);
    progressAnimator.setInterpolator(new LinearInterpolator());
    progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            setProgress((Integer) animation.getAnimatedValue());
        }
    });
    progressAnimator.start();
}

/**
 * 獲取當前進度
 *
 * @return
 */
public int getProgress() {
    return progress;
}

5、使用API

public class WaterWaveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_water_wave);

        // API三選一即可
        // 設定當前進度條
        ((WaveView) findViewById(R.id.wv)).setProgress(50);
        // 在10秒鐘內將進度條增長到50%
        ((WaveView) findViewById(R.id.wv)).setProgress(50, 10000);
        // 自動增長進度從0到100
        ((WaveView) findViewById(R.id.wv)).startIncrease();
    }
}

6、原始碼下載