Android Path路径旋转矩阵计算

一、前言

之前有一篇重点讲了三角形的绕环运动,主要重点内容是将不规则物体构造成一个正方形矩阵,便于计算中点,然后通过圆与切线的垂直关系计算出旋转角度。但实际上这种计算是利用了圆的特性,如果是不规则路径,物体该如何旋转呢 ?

实际上Android提供了一个非常强大的工具------PathMeasure,可以通过片段计算出运动的向量,通过向量和x轴正方向的夹角的斜率就能计算出旋转角度 (这里就不推导了)。

二、效果预览

原理:

通过PathMeasure测量出position和正切的斜率,注意tan和position都是数组,[0]为x或者x方向,[1]为y或者为y方向,当然tan是带方向的矢量,计算公式是 A = ( x1-x2,y1-y2),这些是PathMeasure计算好的。

arduino 复制代码
PathMeasure.getPosTan(mPathMeasure.getLength() * fraction, position, tan);

三、案例

下面是本篇自行车运行的轨迹

scss 复制代码
public class PathMoveView extends View {
    private Bitmap mBikeBitmap;
    // 圆路径
    private Path mPath;
    // 路径测量
    private PathMeasure mPathMeasure;

    // 当前移动值
    private float fraction = 0;
    private Matrix mBitmapMatrix;
    private ValueAnimator animator;
    // PathMeasure 测量过程中的坐标
    private float[] position = new float[2];
    // PathMeasure 测量过程中矢量方向与x轴夹角的的正切值
    private float[] tan = new float[2];
    private RectF rectHolder = new RectF();
    private Paint mDrawerPaint;

    public PathMoveView(Context context) {
        super(context);
        init(context);

    }

    public PathMoveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);

    }

    public PathMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    protected void init(Context context) {
        // 初始化 画笔 [抗锯齿、不填充、红色、线条2px]
        mDrawerPaint = new Paint();
        mDrawerPaint.setAntiAlias(true);
        mDrawerPaint.setStyle(Paint.Style.STROKE);
        mDrawerPaint.setColor(Color.WHITE);
        mDrawerPaint.setStrokeWidth(2);

        // 获取图片
        mBikeBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_bike, null);
        // 初始化矩阵
        mBitmapMatrix = new Matrix();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = 0;
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            height = (int) dp2px(120);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(getMeasuredHeight(), getMeasuredWidth());
        } else {
            height = MeasureSpec.getSize(heightMeasureSpec);
        }

        setMeasuredDimension(getMeasuredWidth(), height);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        int width = getWidth();
        int height = getHeight();
        if (width <= 1 || height <= 1) {
            return;
        }

        if (mPath == null) {
            mPath = new Path();
        } else {
            mPath.reset();
        }
        rectHolder.set(-100, -100, 100, 100);

        mPath.moveTo(-getWidth() / 2F, 0);
        mPath.lineTo(-(getWidth() / 2F + 200) / 2F, -400);
        mPath.lineTo(-200, 0);
        mPath.arcTo(rectHolder, 180, 180, false);
        mPath.quadTo(300, -200, 400, 0);
        mPath.lineTo(500, 0);

        if (mPathMeasure == null) {
            mPathMeasure = new PathMeasure();
            mPathMeasure.setPath(mPath, false);
        }

        int saveCount = canvas.save();
        // 移动坐标矩阵到View中间
        canvas.translate(getWidth() / 2F, getHeight() / 2F);

        // 获取 position(坐标) 和 tan(正切斜率),注意矢量方向与x轴的夹角
        mPathMeasure.getPosTan(mPathMeasure.getLength() * fraction, position, tan);

        // 计算角度(斜率),注意矢量方向与x轴的夹角
        float degree = (float) Math.toDegrees(Math.atan2(tan[1], tan[0]));
        int bmpWidth = mBikeBitmap.getWidth();
        int bmpHeight = mBikeBitmap.getHeight();
        // 重置为单位矩阵
        mBitmapMatrix.reset();
        // 旋转单位举证,中心点为图片中心
        mBitmapMatrix.postRotate(degree, bmpWidth / 2, bmpHeight / 2);
        // 将图片中心和移动位置对齐
        mBitmapMatrix.postTranslate(position[0] - bmpWidth / 2,
                position[1] - bmpHeight / 2);


        // 画圆路径
        canvas.drawPath(mPath, mDrawerPaint);
        // 画自行车,使用矩阵旋转方向
        canvas.drawBitmap(mBikeBitmap, mBitmapMatrix, mDrawerPaint);
        canvas.restoreToCount(saveCount);
    }

    public void start() {

        if (animator != null) {
            animator.cancel();
        }
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f);
        valueAnimator.setDuration(6000);
        // 匀速增长
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 第一种做法:通过自己控制,是箭头在原来的位置继续运行
                fraction = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        valueAnimator.start();
        this.animator = valueAnimator;
    }

    public void stop() {
        if (animator == null) return;
        animator.cancel();
    }

    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

}

缺陷和问题处理:

从图上我们看到,车轮在路线的地下,这种视觉问题需要不断的修正和偏移才能得到解决,比如一段直线和圆面要分别计算偏移。

四、总结

PathMeasure 功能非常强大,可用于一般的在2D游戏中地图路线的计算,因此掌握好路径测量工具,可以方便我们做更多的东西。

相关推荐
清灵xmf3 分钟前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
su1ka1118 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬10 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*11 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_8010741511 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_12 分钟前
报错合计-1
前端
caperxi14 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男14 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
susu108301891115 分钟前
前端css样式覆盖
前端·css
学习路上的小刘16 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js