一、前言
之前有一篇重点讲了三角形的绕环运动,主要重点内容是将不规则物体构造成一个正方形矩阵,便于计算中点,然后通过圆与切线的垂直关系计算出旋转角度。但实际上这种计算是利用了圆的特性,如果是不规则路径,物体该如何旋转呢 ?
实际上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游戏中地图路线的计算,因此掌握好路径测量工具,可以方便我们做更多的东西。