首页 文章详情

Android自定义实现乘风破浪的小船

龙旋 | 26 2021-04-08 10:24 0 0 0

效果图:



一、思路分析


整个效果分为两部分,第一部分是波浪形的水波,第二部分是小船沿着水波移动,并且水波是和小船向着相反的方向移动的。


水波我们可以通过贝塞尔曲线来实现,小船沿着水波移动的效果通过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); }
@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


到这里就结束啦.


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter