高级UI-PathMeasure 使用(路径绘制) 小船在波浪上运动 demo

目录


基础介绍看这两个. 讲的都很不错

https://www.jianshu.com/p/9ee023755ce8

https://www.jianshu.com/p/3efa5341abcc

getSegment 的使用

先写个基本使用的demo


public class MyView extends View {

    private static final String TAG = "MyView";
    private int mViewWidth;
    private int mViewHeight;
    private Paint mDeafultPaint;

    private Paint mPaint;

    public MyView(Context context) {
        super(context);
        init();
    }

    private void init() {
        mDeafultPaint = new Paint();
        mDeafultPaint.setColor(Color.RED);
        mDeafultPaint.setStrokeWidth(5);
        mDeafultPaint.setStyle(Paint.Style.STROKE);

        mPaint = new Paint();
        mPaint.setColor(Color.DKGRAY);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 平移坐标系 让圆点到中间
        canvas.translate(mViewWidth/2,mViewHeight/2);
        // 画坐标线
        canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);
        canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);

        
        //testForceClosed(canvas);

        //testGetSegment(canvas);
        
        //testGetSegmentMoveTo(canvas);

        testNextContour(canvas);


    }

    private void testNextContour(Canvas canvas) {
        Path path = new Path();
        Path path1 = new Path();
        Path path2 = new Path();
        // 添加小矩形
        path1.addRect(-100, -100, 100, 100, Path.Direction.CW);
        // 添加大矩形
        //path.addRect(-200, 200, 200, 600, Path.Direction.CW);
        path2.addRect(-200, -200, 200, 200, Path.Direction.CW);
        path.op(path1,path2, Path.Op.XOR);
        canvas.drawPath(path,mDeafultPaint);

        PathMeasure measure = new PathMeasure(path, false);

        float len1 = measure.getLength();
        // 跳转到下一条路径
        measure.nextContour();

        float len2 = measure.getLength();
        Log.d(TAG,"len1 = "+len1);
        Log.d(TAG,"len2 = "+len2);
    }

    private void testGetSegmentMoveTo(Canvas canvas) {
        Path path = new Path();
        // 创建Path并添加了一个矩形
        path.addRect(-200, -200, 200, 200, Path.Direction.CW);

        Path dst = new Path();
        dst.lineTo(-300, -300);
        // 将 Path 与 PathMeasure 关联
        PathMeasure measure = new PathMeasure(path, false);

        // 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
        //measure.getSegment(200, 600, dst, false);
        measure.getSegment(0, 600, dst, true);

        canvas.drawPath(path,mPaint);
        // 绘制 dst
        canvas.drawPath(dst, mDeafultPaint);
    }

//使用getSegment 
    private void testGetSegment(Canvas canvas) {
        Path path = new Path();
        // 创建Path并添加了一个矩形
        path.addRect(-200, -200, 200, 200, Path.Direction.CW);

        Path dst = new Path();
        // 将 Path 与 PathMeasure 关联
        PathMeasure measure = new PathMeasure(path, false);

        // 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
        measure.getSegment(200, 600, dst, false);

        canvas.drawPath(path,mPaint);
        // 绘制 dst
        canvas.drawPath(dst, mDeafultPaint);
    }

    private void testForceClosed(Canvas canvas) {
        Path path = new Path();

        path.lineTo(0,200);
        path.lineTo(200,200);
        path.lineTo(200,0);

        PathMeasure measure1 = new PathMeasure(path,false);
        PathMeasure measure2 = new PathMeasure(path,true);

        Log.e(TAG, "forceClosed=false length = "+measure1.getLength());
        Log.e(TAG, "forceClosed=true length = "+measure2.getLength());

        canvas.drawPath(path,mDeafultPaint);

    }
}

01 通过path画出矩形

效果如下

mark

代码如下:

这里我需要记住一个技巧 很多数据可以通过计算变成 0~1 或者-1~1区间 这样便于计算与理解

package android.mybzdemo.pathMeasure;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * @author liuml
 * @explain 利用getSegment 画
 * @time 2018/2/6 16:55
 */

public class MyPathMeasureBase extends View {

    private Path mPath;
    private Paint mPaint;
    private PathMeasure mPathMeasure;
    private float mAnimatorValue;
    private Path mDst;
    private float mLength;

    public MyPathMeasureBase(Context context) {
        super(context);
    }

    public MyPathMeasureBase(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        //---画笔
        //ANTI_ALIAS_FLAG绘制时不允许使用反锯齿的标志。
        mPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);

        //---路径
        mPath = new Path();
        //画个矩形
        RectF rect = new RectF(200, 200, 500, 500);
        mPath.addRect(rect, Path.Direction.CW);


        //---路径测量
        mPathMeasure = new PathMeasure();
        //和path关联 true getLength时候是包括闭合的
        mPathMeasure.setPath(mPath,true);
        mLength = mPathMeasure.getLength();

        mDst = new Path();


        //---动画
        //这里有个技巧 把所有大数 或者小数 全部改造成0-1 或者-1 到1 这个区间 这样就好操作了
        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimatorValue = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });

        valueAnimator.setDuration(2000);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.start();
    }


    public MyPathMeasureBase(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDst.reset();
        // 硬件加速的BUG
        mDst.lineTo(0,0);
        //通过不断添加结束点 来画出矩形
        float stop = mLength * mAnimatorValue;
        mPathMeasure.getSegment(0, stop, mDst, true);
        canvas.drawPath(mDst, mPaint);


    }

}

那么如何实现下面效果?

mark

同样通过getSegment

 //通过不断添加结束点 来画出矩形
        float stop = mLength * mAnimatorValue;
        float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
        mPathMeasure.getSegment(start, stop, mDst, true);

通过计算改变起始值 这里的原理是当画到一半的时候 不断改变起始值 最终让起始点和终点相等,即可实现.


02 通过path画出小船跟随波浪运动

效果
mark

代码


package android.mybzdemo.pathMeasure;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.mybzdemo.R;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

/**
 * @author liuml
 * @explain pathMeasure 使用  实现小船在波浪上面
 * @time 2018/2/6 16:55
 */

public class BoatView2 extends View {
    private static final String TAG = "MyBzView3";
    ValueAnimator animator;
    private Path mPath;
    private Paint mPaint;
    private static final int INT_WAVE_LENGTH = 1000;//波长
    private int waveHeight = 60;//
    private int mDeltax;//运动的值
    private Bitmap boatBmp;//小船
    private PathMeasure pathMeasure;

    private float[] pos;
    private float[] tan;
    private Matrix mMatrix;
    private float faction = 0;
    private float length;
    private float[] test;

    public BoatView2(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
        //用这种风格绘制的几何图形和文本将会被填充
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        //小船
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;//小船压缩一半
        boatBmp = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);

        pos = new float[2];
        tan = new float[2];
        test = new float[2];
        mMatrix = new Matrix();

        mPath = new Path();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //清除路径上的任何线条和曲线,使其为空。
        mPath.reset();

        int orgin = 800;
        int halfLength = INT_WAVE_LENGTH / 2;
        //起始点移动到左边屏幕的左边,根据不断改变起始点 动态的改变位置  mDeltax =0 让波浪不动
        mPath.moveTo(-INT_WAVE_LENGTH + mDeltax * INT_WAVE_LENGTH, orgin);
//        Log.d(TAG, "onDraw: getWidth= " + getWidth());
        //从起始点开始
        int number = 0;
        for (int i = -INT_WAVE_LENGTH; i < getWidth() + INT_WAVE_LENGTH;
             i += INT_WAVE_LENGTH) {

            //画一个圆点 这个圆点在交界处
//            Paint paint = new Paint();
//            paint.setColor(Color.RED);
//            canvas.drawCircle(-INT_WAVE_LENGTH + halfLength * number + (faction * INT_WAVE_LENGTH), orgin, 20, paint);
//            number++;

            //使用rQuadTo 是相对位移 不用重新设置起始点
            mPath.rQuadTo(halfLength / 2, waveHeight, halfLength, 0);
            mPath.rQuadTo(halfLength / 2, -waveHeight, halfLength, 0);
        }
        //上面是画曲线

        //下面是画出界面左边和右边的一条线 这样就可以闭合
        mPath.lineTo(getWidth(), getHeight());
        mPath.lineTo(0, getHeight());
        mPath.close();//让线闭合

        //把线画出来
        canvas.drawPath(mPath, mPaint);

        //下面画出小船
        //先测量path
        pathMeasure = new PathMeasure(mPath, false);
        //先获取长度
        length = pathMeasure.getLength();
        //将距离标记为0 <= distance <= getLength(),然后进行计算  对应的位置和切线。如果没有路径,返回false,
        //或者指定一条零长度的路径,在这种情况下,位置和切线变。  获取某一个点的tan
        boolean posTan = pathMeasure.getPosTan(length * faction, pos, tan);

        //方案一: 自己计算
//        Log.d(TAG,"pos[0] = " + pos[0] + "pos[1] = " +pos[1]);
//        Log.d(TAG,"tan[0] = " + tan[0] + "tan[1] = " +tan[1]);
        if (posTan) {
            // 方案一 :自己计算
            // 将tan值通过反正切函数得到对应的弧度,在转化成对应的角度度数
          /*  float degrees = (float) (Math.atan2(tan[1],tan[0])*180f / Math.PI);
            mMatrix.postRotate(degrees, boatBmp.getWidth()/2, boatBmp.getHeight() / 2);//旋转
            mMatrix.postTranslate(pos[0]- boatBmp.getWidth() / 2,pos[1] - boatBmp.getHeight());//平移
            canvas.drawBitmap(boatBmp,mMatrix,mPaint);*/
        }
        //方案二:通过api获取matrix  获取指定长度的位置坐标及该点Matrix
        //在这里做一些操作 如果这个点超过屏幕就让他返回来
        float v = 1080 * faction;

        float tmp;
        if (faction > 0.6) {
            tmp = 1 - faction;
        } else {
            tmp = faction;
        }
//
        pathMeasure.getMatrix(length * faction, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);

        //需要减去小船本身的宽高 向上平移
        mMatrix.preTranslate(boatBmp.getWidth() / 2, -boatBmp.getHeight());
        canvas.drawBitmap(boatBmp, mMatrix, mPaint);


    }


    /**
     * 开启动画
     */
    public void startAnimator() {
       /* animator = ValueAnimator.ofInt(0, INT_WAVE_LENGTH);
        animator.setDuration(1000);
        //设置为线性的
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(ValueAnimator.INFINITE);//无限循环
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mDeltax = (int) animation.getAnimatedValue();
                //根据不断改变起始点 动态的改变位置
                postInvalidate();
            }
        });

        animator.start();*/
        //这里就之前说的 技巧  利用0到1的区间 转化数值
        animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(10000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                faction = (float) animation.getAnimatedValue();
                Log.d(TAG, "onAnimationUpdate: =================faction =" + faction);
                Log.d(TAG, "onAnimationUpdate: =================length =" + length);
                postInvalidate();
            }
        });
        animator.start();

    }

    public void stopanimator() {

        animator.cancel();
    }
}




在上一个 贝塞尔曲线 的波浪demo下继续写:

03 使用DashPathEffect 进行路径绘制

Effect效果

具体使用

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0907/3429.html

主要的原理:

PathEffect effect = new DashPathEffect(new float[] { length, length }, 0);

我们可以把DashPathEffect的第一个参数(float数组)只填入两个值,都是path的总长度length,那么按照上面对DashPathEffect的解释,第一次绘制一条实线就已经完全绘制完了,间隔的空白区间得不到绘制的机会。事实上这样绘制完全不能产生虚线效果,跟不设置PathEffect是一样的。

但是我们注意第三个参数即起始位置的偏移量现在是为0的。如果我们不为0呢?

比如为100,那么第一次绘制实线就会跳过100的距离,第一次的实线就只能绘制length-100的长度,那么空白区域就可以绘制100的长度,但是你看不见空白,所以我们只会感觉到绘制了一条length-100的路径。

如果你按照我们的思路去做实验,那么很快你就会想到,把这个偏移量也设置成length,那么第一次的实线区间将完全得不到绘制,而直接进入空白区间,而我们的空白区间总长度也是length,因此它占用了全部的绘制区间,所以此时什么也看不到。如果空白区间小于length的话,是可以看到一点实线的(因为空白区间完了紧接着就是实线了)。

所以,我们可以设置一个百分比,取名叫phase,phase的增长是从0 .0-1.0,如果我们利用属性动画来改变它,然后根据它动态的构造一个这样的DashPathEffect:

new DashPathEffect(new float[] { length, length }, length - phase * length);

这样就能产生跟踪绘制的效果。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值