首先來看一看效果圖:
先簡要說一下這裡需要涉及到的知識點:
- 2D繪圖基礎
- path貝塞爾二階曲線
- ValueAnimator
- PorterDuffXfermode
參考的文章:
繪製思路:
- 繪製兩段二階貝塞爾曲線圍成的波浪封閉圖形,一段波浪的長度為螢幕寬度。
- 水平移動繪製好的圖形,形成波浪滾動的效果,並不斷迴圈。
- 不斷增加繪製圖形的高度,形成水位上升的效果。
- 通過 PorterDuffXfermode 的效果 SRC_IN 繪製成最終效果
第一步:繪製封閉的波浪圖形
首先是繪製下圖中紅色線條區域內的圖形:
如上圖中所示,我們要繪製兩個螢幕寬度的波浪圖形,那麼先來看看我們要的效果圖:
初始化成員變數:
private int mWaveHeight;//水波紋高度
private Paint mPaint;//繪製水波紋的畫筆
private Path mPath;//繪製水波紋的路徑
private int mWL;//螢幕寬度
private String TAG = "zoneLog";//Log 日誌的 Tag
private int mFu;//水波紋的振幅
private int mOffset;//水波紋移動的偏移值
private boolean mMIsAnimatorPlaying;//用於判斷是否第一次執行 ValueAnimator
private Paint mTextPaint;//文字畫筆
private String mText;//進度值文字
構造方法中的初始化資料:
mPaint = new Paint();//用於繪製水波紋的畫筆
mPaint.setAntiAlias(true);
mPaint.setColor(Color.parseColor("#5DCEC6"));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mFu = 100;//水波紋的振幅
mTextPaint = new Paint();//用於繪製文字的畫筆
mTextPaint.setColor(Color.WHITE);
mTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(75);
mOffset= 0;//水波紋的偏移值,用於水波紋的移動效果
mWaveHeight= 0;//進度值,也是水波紋的高度
mPath = new Path();//用於繪製水波紋路徑
首先繪製左邊,右邊,底部的三條線段:
mPath.moveTo(mWL, getHeight()/2);
mPath.lineTo(mWL, getHeight());//繪製右邊的線段
mPath.lineTo(-mWL, getHeight());//繪製底部的線段
mPath.lineTo(-mWL, getHeight()/2);//繪製左邊的線段
繪製螢幕中的波浪曲線:( 此處需要注意,第一段波浪是在螢幕外面的)
mPath.quadTo((-mWL * 3 / 4), getHeight() / 2 mFu, (-mWL / 2), getHeight() / 2);
//畫出第一段波紋的第一條曲線
mPath.quadTo((-mWL / 4), getHeight() / 2 - mFu, 0, getHeight() / 2);
//畫出第一段波紋的第二條曲線
mPath.quadTo((mWL / 4), getHeight() / 2 mFu, (mWL / 2), getHeight() / 2);
//畫出第二段波紋的第一條曲線
mPath.quadTo((mWL * 3 / 4), getHeight() / 2 - mFu, mWL, getHeight() / 2);
//畫出第二段波紋的第二條曲線
mPath.close();//封閉曲線
canvas.drawPath(mPath, mPaint);//繪製曲線
到現在為止,就已經繪製出了一個波浪圖形。
第二步:讓水波紋動起來
先看一下效果圖:
這裡使用 ValueAnimator 來動態改變各個點的 x 軸座標,使水波紋有迴圈流動的效果。那麼新增了偏移值的程式碼是這樣的:
mPath.reset();//每次修改 mPath 的值,需要重置 mPath
mPath.moveTo(mWL mOffset, getHeight() / 2);
mPath.lineTo(mWL mOffset, getHeight());//繪製右邊的線段
mPath.lineTo(-mWL mOffset, getHeight());//繪製底部的線段
mPath.lineTo(-mWL mOffset, getHeight() / 2);//繪製左邊的線段
mPath.quadTo((-mWL * 3 / 4) mOffset, getHeight() / 2 mFu, (-mWL / 2) mOffset, getHeight() / 2);
//畫出第一段波紋的第一條曲線
mPath.quadTo((-mWL / 4) mOffset, getHeight() / 2 - mFu, 0 mOffset, getHeight() / 2);
//畫出第一段波紋的第二條曲線
mPath.quadTo((mWL / 4) mOffset, getHeight() / 2 mFu, (mWL / 2) mOffset, getHeight() / 2);
//畫出第二段波紋的第一條曲線
mPath.quadTo((mWL * 3 / 4) mOffset, getHeight() / 2 - mFu, mWL mOffset, getHeight() / 2);
//畫出第二段波紋的第二條曲線
mPath.close();//封閉曲線
canvas.drawPath(mPath, mPaint);//繪製曲線
通過 ValueAnimator 動態改版 mOffset 的指:
if (!mMIsAnimatorPlaying) {//判斷是否第一次開始 ValueAnimator
mMIsAnimatorPlaying = true;
ValueAnimator animator = ValueAnimator.ofInt(0, mWL);//設定移動範圍為一個螢幕寬度
animator.setDuration(1000);//設定持續時間為1秒
animator.setRepeatCount(ValueAnimator.INFINITE);//設定為無限迴圈
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mOffset = value;//修改偏移量
postInvalidate();//重新整理介面
}
});
animator.start();
}
第三步:讓水位漲起來
先看效果圖:
同理,這裡也是通過 ValueAnimator 來不斷增加 mWaveHeight 的值,但是這裡指執行一次。那麼新增了 mWaveHeight 的程式碼是這樣的。
mPath.reset();//每次修改 mPath 的值,需要重置 mPath
mPath.moveTo(mWL mOffset, getHeight() - mWaveHeight);//因為 y 軸正方向是向下的,所以減去水波紋的高度
mPath.lineTo(mWL mOffset, getHeight());//繪製右邊的線段
mPath.lineTo(-mWL mOffset, getHeight());//繪製底部的線段
mPath.lineTo(-mWL mOffset, getHeight() - mWaveHeight);//繪製左邊的線段
mPath.quadTo((-mWL * 3 / 4) mOffset, getHeight() - mWaveHeight mFu, (-mWL / 2) mOffset, getHeight() - mWaveHeight);
//畫出第一段波紋的第一條曲線
mPath.quadTo((-mWL / 4) mOffset, getHeight() - mWaveHeight - mFu, 0 mOffset, getHeight() - mWaveHeight);
//畫出第一段波紋的第二條曲線
mPath.quadTo((mWL / 4) mOffset, getHeight() - mWaveHeight mFu, (mWL / 2) mOffset, getHeight() - mWaveHeight);
//畫出第二段波紋的第一條曲線
mPath.quadTo((mWL * 3 / 4) mOffset, getHeight() - mWaveHeight - mFu, mWL mOffset, getHeight() - mWaveHeight);
//畫出第二段波紋的第二條曲線
mPath.close();//封閉曲線
canvas.drawPath(mPath, mPaint);//繪製曲線
動態改變水波紋的高度:
if (!mMIsAnimatorPlaying) {//判斷是否第一次開始 ValueAnimator
mMIsAnimatorPlaying = true;
ValueAnimator animator = ValueAnimator.ofInt(0, mWL);//設定移動範圍為一個螢幕寬度
animator.setDuration(1000);//設定持續時間為1秒
animator.setRepeatCount(ValueAnimator.INFINITE);//設定為無限迴圈
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mOffset = value;//修改偏移量
postInvalidate();//重新整理介面
}
});
animator.start();
ValueAnimator animatorHeight = ValueAnimator.ofInt(0, getHeight() mFu);
animatorHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mWaveHeight = (int) animation.getAnimatedValue();
mText = String.valueOf((int) ((double) mWaveHeight / (double) (getHeight() mFu) * 100)) "%";//計算進度的百分比
if (mWaveHeight == getHeight() mFu) {//當水位高於螢幕高度,停止水波紋移動效果
animation.cancel();
}
if (mWaveHeight > getHeight() / 2) {//當水位高於文字的時候,字型為白色,否則為淺藍色
mTextPaint.setColor(Color.WHITE);
} else {
mTextPaint.setColor(Color.parseColor("#5DCEC6"));
}
postInvalidate();
}
});
animatorHeight.setDuration(10000);//持續10秒
animatorHeight.start();
}
canvas.drawText(mText, getWidth() / 2 - mTextPaint.measureText(mText) / 2, getHeight() / 2, mTextPaint);
第四步:使用 PorterDuffXfermode 實現最終效果
最終的效果就是文章一開始時候效果。
使用特效畫筆:
final int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.qq3);//獲取 bitmap 資源
canvas.drawBitmap(bitmap, getWidth() / 2 - bitmap.getWidth() / 2, getHeight() / 2 - bitmap.getHeight() / 2, null);//繪製 bitmap
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//設定特效畫筆
mPaint.setColor(Color.parseColor("#5DCEC6"));
最終整合起來的程式碼是這樣的:
public class WaterWave extends View {
private int mWaveHeight;//水波紋高度
private Paint mPaint;//繪製水波紋的畫筆
private Path mPath;//繪製水波紋的路徑
private int mWL;//螢幕寬度
private String TAG = "zoneLog";//Log 日誌的 Tag
private int mFu;//水波紋的振幅
private int mOffset;//水波紋移動的偏移值
private boolean mMIsAnimatorPlaying;//用於判斷是否第一次執行 ValueAnimator
private Paint mTextPaint;//文字畫筆
private String mText;//進度值文字
public WaterWave(Context context) {
super(context);
init();
}
public WaterWave(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public WaterWave(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public WaterWave(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPaint = new Paint();//用於繪製波浪
mPaint.setAntiAlias(true);
mPaint.setColor(Color.parseColor("#5DCEC6"));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mFu = 100;//波浪的振幅
mTextPaint = new Paint();//用於繪製文字
mTextPaint.setColor(Color.WHITE);
mTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(75);
mOffset = 0;//水波紋移動的偏移值
mWaveHeight = 0;//進度值,也是水波紋的高度
mPath = new Path();//用於繪製波浪
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
mWL = getWidth();
mPath.reset();
final int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.qq3);//獲取 bitmap 資源
canvas.drawBitmap(bitmap, getWidth() / 2 - bitmap.getWidth() / 2, getHeight() / 2 - bitmap.getHeight() / 2, null);//繪製 bitmap
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//設定特效畫筆
mPaint.setColor(Color.parseColor("#5DCEC6"));
mPath.moveTo(mWL mOffset, getHeight() - mWaveHeight);//因為 y 軸正方向是向下的,所以減去水波紋的高度
mPath.lineTo(mWL mOffset, getHeight());//繪製右邊的線段
mPath.lineTo(-mWL mOffset, getHeight());//繪製底部的線段
mPath.lineTo(-mWL mOffset, getHeight() - mWaveHeight);//繪製左邊的線段
mPath.quadTo((-mWL * 3 / 4) mOffset, getHeight() - mWaveHeight mFu, (-mWL / 2) mOffset, getHeight() - mWaveHeight); //畫出第一段波紋的第一條曲線
mPath.quadTo((-mWL / 4) mOffset, getHeight() - mWaveHeight - mFu, 0 mOffset, getHeight() - mWaveHeight); //畫出第一段波紋的第二條曲線
mPath.quadTo((mWL / 4) mOffset, getHeight() - mWaveHeight mFu, (mWL / 2) mOffset, getHeight() - mWaveHeight); //畫出第二段波紋的第一條曲線
mPath.quadTo((mWL * 3 / 4) mOffset, getHeight() - mWaveHeight - mFu, mWL mOffset, getHeight() - mWaveHeight); //畫出第二段波紋的第二條曲線
mPath.close();//封閉曲線
canvas.drawPath(mPath, mPaint);//繪製曲線
if (!mMIsAnimatorPlaying) {//判斷是否第一次開始 ValueAnimator
mMIsAnimatorPlaying = true;
ValueAnimator animator = ValueAnimator.ofInt(0, mWL);//設定移動範圍為一個螢幕寬度
animator.setDuration(1000);//設定持續時間為1秒
animator.setRepeatCount(ValueAnimator.INFINITE);//設定為無限迴圈
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mOffset = value;//修改偏移量
postInvalidate();//重新整理介面
}
});
animator.start();
ValueAnimator animatorHeight = ValueAnimator.ofInt(0, getHeight() mFu);
animatorHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mWaveHeight = (int) animation.getAnimatedValue();
mText = String.valueOf((int) ((double) mWaveHeight / (double) (getHeight() mFu) * 100)) "%";//計算進度的百分比
if (mWaveHeight == getHeight() mFu) {//當水位高於螢幕高度,停止水波紋移動效果
animation.cancel();
}
if (mWaveHeight > getHeight() / 2) {//當水位高於文字的時候,字型為白色,否則為淺藍色
mTextPaint.setColor(Color.WHITE);
} else {
mTextPaint.setColor(Color.parseColor("#5DCEC6"));
}
postInvalidate();
}
});
animatorHeight.setDuration(10000);//持續10秒
animatorHeight.start();
}
canvas.drawText(mText, getWidth() / 2 - mTextPaint.measureText(mText) / 2, getHeight() / 2, mTextPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(sc);// 還原畫布
}
}
使用到的圖片資源:
好了,我們一步一步實現了最終效果,如果文中有什麼知識點是錯誤的或者更好的實現方法,請及時聯絡我進行修改,以免誤導別人。謝謝。
写评论
很抱歉,必須登入網站才能發佈留言。