美文网首页
Android自定义View之乘风破浪的小船

Android自定义View之乘风破浪的小船

作者: loren325 | 来源:发表于2021-02-05 15:41 被阅读0次

    效果图:


    2.gif

    一、思路分析

    整个效果分为两部分,第一部分是波浪形的水波,第二部分是小船沿着水波移动,并且水波是和小船向着相反的方向移动的。
    水波我们可以通过贝塞尔曲线来实现,小船沿着水波移动的效果通过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的起始点移动到屏幕的一半高度的位置,然后循环画曲线,长度为屏幕的宽度加上一个波长,然后连接到整个屏幕高度的位置,最后形成一个封闭的区域,这样就实现了一个填充的水波效果。


    水波.png

    利用属性动画来不断的改变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();
    }
    
    水波动画.gif

    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起点的长度。
    我们发现小船在沿着水波移动的时候,会根据波浪的高低倾斜


    33.png

    上面提到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

    相关文章

      网友评论

          本文标题:Android自定义View之乘风破浪的小船

          本文链接:https://www.haomeiwen.com/subject/atfgtltx.html