為了加強對自定義 View 的認知以及開發能力,我計劃這段時間陸續來完成幾個難度從易到難的自定義 View,並簡單的寫幾篇部落格來進行介紹,所有的程式碼也都會開源,也希望讀者能給個 star 哈 GitHub 地址:github.com/leavesC/Cus… 也可以下載 Apk 來體驗下:www.pgyer.com/CustomView
先看下效果圖:
一、思路解析
波浪 View(即 WaveView)的重點在於其 onDraw 方法的十行程式碼上,當中運用到了貝塞爾曲線的知識
//每個波浪的起伏高度
private float waveHeight;
//每個波浪的寬度
private float waveWidth;
//波浪的速度
private long speed = DEFAULT_SPEED;
private float animatedValue;
private Path path = new Path();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
path.moveTo(-waveWidth + animatedValue, contentHeight / 2);
for (float i = -waveWidth; i < contentWidth + waveWidth; i += waveWidth) {
path.rQuadTo(waveWidth / 4, -waveHeight, waveWidth / 2, 0);
path.rQuadTo(waveWidth / 4, waveHeight, waveWidth / 2, 0);
}
path.lineTo(contentWidth, contentHeight);
path.lineTo(0, contentHeight);
path.close();
canvas.drawPath(path, paint);
}
複製程式碼
從圖片可以看出來各個波浪的起伏高度和寬度都是一樣的,意味著在貝塞爾曲線中控制點的 Y 座標是保持不變的,以上的邏輯可以利用下圖來幫助理解
藍色背景代表的是 View 所佔的面積,紅色小球連起來的曲線軌跡即為波浪的執行軌跡,waveWidth 代表的是每個波浪的寬度,即每一個綠色方塊的寬度。兩個 path.rQuadTo
方法所繪製出來的分別是向上的曲線和向下的曲線,並在 for
迴圈中不斷重複這個過程,直到綠色方塊所佔的總寬度超出 View 的寬度為止
為了呈現出**“波浪向右前進”的效果,當中就需要用到動畫值 animatedValue
來不斷改變貝塞爾曲線的起始座標點**
private ValueAnimator valueAnimator;
public void initAnimation() {
valueAnimator = new ValueAnimator();
valueAnimator.setDuration(speed);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
}
複製程式碼
然後向外部開放改變波浪寬度、波浪高度、動畫時長的這三個方法,即可以此來改變 View 的形狀
public void setWaveScaleWidth(float waveScaleWidth) {
if (waveScaleWidth <= 0 || waveScaleWidth > 1) {
return;
}
this.waveScaleWidth = waveScaleWidth;
resetWaveParams();
}
public void setWaveScaleHeight(float waveScaleHeight) {
if (waveScaleWidth <= 0 || waveScaleWidth > 1) {
return;
}
this.waveScaleHeight = waveScaleHeight;
resetWaveParams();
}
public void setSpeed(long speed) {
this.speed = speed;
resetWaveParams();
}
private void resetWaveParams() {
waveWidth = contentWidth * waveScaleWidth;
waveHeight = contentHeight * waveScaleHeight;
if (valueAnimator != null) {
valueAnimator.setFloatValues(0, waveWidth);
valueAnimator.setDuration(speed);
}
}
複製程式碼