效果图:
一、思路分析
整个效果分为两部分,第一部分是波浪形的水波,第二部分是小船沿着水波移动,并且水波是和小船向着相反的方向移动的。
水波我们可以通过贝塞尔曲线来实现,小船沿着水波移动的效果通过PathMeasure来实现,然后使用属性动画让水波和小船动起来。
二、功能实现
1.首先通过贝塞尔曲线实现水波
private void drawWave(Canvas canvas){
mPath.reset();
mPath.moveTo(0 - mDeltaX, mHeight / 2);
for (int i = 0; i <= getWidth() + waveLength; i += waveLength) {
mPath.rQuadTo(halfWaveLength / 2, waveHeight, halfWaveLength, 0);
mPath.rQuadTo(halfWaveLength / 2, -waveHeight, halfWaveLength, 0);
}
mPath.lineTo(getWidth() + waveLength, getHeight());
mPath.lineTo(0, getHeight());
mPath.close();
canvas.drawPath(mPath, mPaint);
}
mDeltaX为水波水平方向移动的距离。
waveLength为水波长度,一个上弧加一个下弧为一个波长。
halfWaveLength为半个水波长度。
先把path的起始点移动到屏幕的一半高度的位置,然后循环画曲线,长度为屏幕的宽度加上一个波长,然后连接到整个屏幕高度的位置,最后形成一个封闭的区域,这样就实现了一个填充的水波效果。
利用属性动画来不断的改变path起始点的x值,使水波水平移动
private void startAnim(){
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(animation -> {
mDeltaX = waveLength * ((float) animation.getAnimatedValue());
postInvalidate();
});
animator.setDuration(13000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
2.接着就是把小船给绘制到水波上,要绘制小船,得先知道要绘制到哪里,也就是要知道绘制的坐标点,这里可以通过PathMeasure来得到。
PathMeasure有个getMatrix方法,通过这个方法可以获取指定长度的位置坐标及该点Matrix。先来看下这个方法:
boolean getMatrix (float distance, Matrix matrix, int flags)
distance:距离Path起点的长度
matrix:根据flags封装好的矩阵
flags:选择哪些内容会传入到matrix中,可选值有POSITION_MATRIX_FLAG(位置)和ANGENT_MATRIX_FLAG(正切)。
有了这个方法,我们就可以绘制小船了
private void drawBitmap(Canvas canvas) {
mPathMeasure.setPath(mPath, false);
mMatrix.reset();
mPathMeasure.getMatrix(mDistance, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
canvas.drawBitmap(mBitMap,mMatrix,mPaint);
}
mDistance就是距离Path起点的长度。
我们发现小船在沿着水波移动的时候,会根据波浪的高低倾斜
上面提到getMatrix方法,不仅返回指定长度的位置坐标,还会返回该点的matrix,这个时候就需要用到返回的matrix,使用matrix的preTranslate方法来实现小船角度的倾斜。
private void drawBitmap(Canvas canvas) {
mPathMeasure.setPath(mPath, false);
mMatrix.reset();
mPathMeasure.getMatrix(mDistance, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
mMatrix.preTranslate(- mBitMap.getWidth() / 2, - mBitMap.getHeight());
canvas.drawBitmap(mBitMap,mMatrix,mPaint);
}
关于matrix以及matrix的preTranslate方法详解,请移步这里。
至此我们实现了小船的绘制,但是小船并没有移动起来,我们还需要利用属性动画来使小船移动起来
private void startAnim(){
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(animation -> {
mDeltaX = waveLength * ((float) animation.getAnimatedValue());
mDistance = (getWidth() + waveLength + halfWaveLength) * ((float)animation.getAnimatedValue());
postInvalidate();
});
animator.setDuration(13000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
贴上完整代码:
public class WaveView extends View {
private Paint mPaint;
private Path mPath;
// 水波长度
private int waveLength = 800;
// 水波高度
private int waveHeight = 150;
private int mHeight;
private int halfWaveLength = waveLength / 2;
private float mDeltaX;
private Bitmap mBitMap;
private PathMeasure mPathMeasure;
private Matrix mMatrix;
private float mDistance;
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
mPaint = new Paint();
mPaint.setColor(getResources().getColor(R.color.sea_blue));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPath = new Path();
mMatrix = new Matrix();
mPathMeasure = new PathMeasure();
Options opts = new Options();
opts.inSampleSize = 3;
mBitMap = BitmapFactory.decodeResource(getResources(), R.drawable.ship, opts);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = h;
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawWave(canvas);
drawBitmap(canvas);
}
/**
* 绘制水波
* @param canvas
*/
private void drawWave(Canvas canvas){
mPath.reset();
mPath.moveTo(0 - mDeltaX, mHeight / 2);
for (int i = 0; i <= getWidth() + waveLength; i += waveLength) {
mPath.rQuadTo(halfWaveLength / 2, waveHeight, halfWaveLength, 0);
mPath.rQuadTo(halfWaveLength / 2, -waveHeight, halfWaveLength, 0);
}
mPath.lineTo(getWidth() + waveLength, getHeight());
mPath.lineTo(0, getHeight());
mPath.close();
canvas.drawPath(mPath, mPaint);
}
/**
* 绘制小船
* @param canvas
*/
private void drawBitmap(Canvas canvas) {
mPathMeasure.setPath(mPath, false);
mMatrix.reset();
mPathMeasure.getMatrix(mDistance, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
mMatrix.preTranslate(- mBitMap.getWidth() / 2, - mBitMap.getHeight());
canvas.drawBitmap(mBitMap,mMatrix,mPaint);
}
/**
* 平移动画
*/
private void startAnim(){
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(animation -> {
mDeltaX = waveLength * ((float) animation.getAnimatedValue());
mDistance = (getWidth() + waveLength + halfWaveLength) * ((float)animation.getAnimatedValue());
postInvalidate();
});
animator.setDuration(13000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
}
完整项目地址:https://github.com/loren325/CustomerView