Android自定义实现乘风破浪的小船
效果图:

一、思路分析
整个效果分为两部分,第一部分是波浪形的水波,第二部分是小船沿着水波移动,并且水波是和小船向着相反的方向移动的。
水波我们可以通过贝塞尔曲线来实现,小船沿着水波移动的效果通过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);}
至此我们实现了小船的绘制,但是小船并没有移动起来,我们还需要利用属性动画来使小船移动起来
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);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mHeight = h;startAnim();}@Overrideprotected 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
到这里就结束啦.
评论
