PathMeasure从名字就可以看出,这个类应该关联一个path类对象。它是对它关联path对象的进一步测量。所以PathMeasure与Path是一一对应的,一个PathMeasure对象如果没有与他关联的Path对象,那么这个PathMeasure也就没什么存在的意义了。它提供了测量path长度,返回一个点的坐标和正切值等方法。
-
构造函数(Constructors)
- 无参构造函数:仅仅是构造了一个PathMeasure对象,并没有关联任何path。要想使用它可以调用setPath传入想要关联的path。
- 有参构造函数:接收一个path对象与之关联,这个path对象就是我们想要测量的实体。第二个boolean类型的forceClosed的意义。如果传入的是true,那么即使传入的path不是闭合的,那么也强制认为它的闭合的。如果是false,就不会又任何变化。一般使用的时候都会传入false。
-
公共方法
- getLength:返回关联path当前轮廓的长度,如果没有关联path,将会返回0;
- getMatrix:传入的distance要大于0,并且小于path当前轮廓的长度。它会计算distance在Path上对应的点的信息,并将信息封装到传入的martrix中。通过返回的boolean值可以判断计算是否成功。
- getPosTan:传入的distance要大于0,并且小于path当前轮廓的长度。它会计算distance在Path上对应的点的信息,并将信息封装到float[] pos和float[] tan中,他们分别是一个长度为2的数组,数组中index=0是x方向上的信息,index=1是y方向上的信息。通过返回的boolean值可以判断计算是否成功。
- getSegment:startD和stopD分别是起始距离和终止距离,此方法会裁剪取出他们中间的path放到dst中。startD和stopD都应该是合法的值,否则返回false。裁剪出来的path长度为0也会返回false,如果是正常的情况,就会返回true。startWithMoveTo为true表示截取到的path的第一个点不变。如果为false,截取到的path的起点会连接到dst之前到的最后一个点。
- isClosed:当前轮廓是否是闭合的
- nextContour:移动到path中的下一个轮廓上
- setPath:设置与之有关的path.第二个boolean类型的forceClosed的意义。如果传入的是true,那么即使传入的path不是闭合的,那么也强制认为它的闭合的。如果是false,就不会又任何变化。一般使用的时候都会传入false。
方法就这么几个,而且很简单。接下来利用这几个方法,来做两个炫酷的效果。
旋转小飞机
public class PlaneView extends View{
private Paint mPaint;
private int mWidth,mHeight;
private Path mPath;
private Bitmap mBitmap;
private int mBitmapWidth,mBitmapHeight;
private Matrix mMatrix;
private float[] pos;
private float[] tan;
private PathMeasure pathMeasure;
private float totalLength,currentLength;
public PlaneView(Context context) {
this(context,null);
}
public PlaneView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public PlaneView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
pos = new float[2];
tan = new float[2];
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mPath = new Path();
// 获取缩放的小飞机图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.arrow,options);
mBitmapWidth = mBitmap.getWidth();
mBitmapHeight = mBitmap.getHeight();
mMatrix = new Matrix();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
mHeight = getHeight();
mPath.addCircle(mWidth/2,mHeight/2,220, Path.Direction.CW);
// 创建一个PathMeasure与Path关联
pathMeasure = new PathMeasure(mPath,false);
// 获取Path的总长度
totalLength = pathMeasure.getLength();
}
@Override
protected void onDraw(Canvas canvas) {
// 画坐标轴
canvas.drawLine(mWidth/2,0,mWidth/2,mHeight,mPaint);
canvas.drawLine(0,mHeight/2,mWidth,mHeight/2,mPaint);
// 画圆圈
canvas.drawPath(mPath,mPaint);
// 获取当前点的信息
boolean posTan = pathMeasure.getPosTan(currentLength, pos, tan);
if (posTan){
mMatrix.reset();
// 计算图片要旋转的角度
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
// 旋转图片
mMatrix.postRotate(degrees,mBitmapWidth / 2,mBitmapHeight / 2);
// 将图片中心绘制到当前点
mMatrix.postTranslate(pos[0] - mBitmapWidth / 2,pos[1] - mBitmapHeight / 2);
canvas.drawBitmap(mBitmap,mMatrix,mPaint);
currentLength += 5;
if (currentLength >= totalLength){
currentLength = 0.0f;
}
invalidate();
}
}
}
一个正在加载中的小特效
public class CircleView extends View {
private int mWidth,mHeight;
private Path mPath,mDst;
private Paint mPaint;
private PathMeasure pathMeasure;
private float animatedValue;
public CircleView(Context context) {
this(context,null);
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPath = new Path();
mDst = new Path();
// 执行动画
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = getWidth();
mHeight = getHeight();
mPath.addCircle(mWidth / 2,mHeight /2,220, Path.Direction.CW);
pathMeasure = new PathMeasure(mPath,false);
}
@Override
protected void onDraw(Canvas canvas) {
mDst.reset();
// 计算新path的终点位置
float stop = pathMeasure.getLength() * animatedValue;
// 计算新path的起点位置
float start = (float) (stop - ((0.5 - Math.abs(animatedValue - 0.5)) * pathMeasure.getLength()));
// 根据计算的值截取出新Path
boolean segment = pathMeasure.getSegment(start, stop, mDst, true);
if (segment){
canvas.drawPath(mDst,mPaint);
}
}
}
小船随波逐流
public class WaveView extends View {
private static int DRAW_BOAT_OFFSET = 15;
private Bitmap bitmap;
private int mWaveLength;
private Path mPath;
private int mHeight;
private int mWidth;
private Paint mPaint;
private Matrix mMatrix;
private PathMeasure pathMeasure;
private float animatedValue;
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) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 加载小船图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);
// 初始化需要用到的变量
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.argb(105,0,0,255));
mPaint.setStyle(Paint.Style.FILL);
mPath = new Path();
mMatrix = new Matrix();
pathMeasure = new PathMeasure();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = getWidth();
mHeight = getHeight();
mWaveLength = mWidth / 2;
}
@Override
protected void onDraw(Canvas canvas) {
mPath.reset();
// 根据动画的进行进度设置波浪形的path路径,并绘制波浪
mPath.moveTo(-mWaveLength+(mWaveLength/2)*animatedValue,mHeight/2);
for (int i = -mWaveLength; i < mWidth + mWaveLength; i+=mWaveLength) {
mPath.rQuadTo(mWaveLength / 2 +(mWaveLength/2)*animatedValue,
60,
mWaveLength+(mWaveLength/2)*animatedValue,
0);
mPath.rQuadTo(mWaveLength / 2 +(mWaveLength/2)*animatedValue,
-60,
mWaveLength+(mWaveLength/2)*animatedValue,
0);
}
mPath.lineTo(mWidth,mHeight);
mPath.lineTo(0,mHeight);
mPath.close();
canvas.drawPath(mPath,mPaint);
// 因为每次绘制的path可能都不同,所以每次都为pathMeasure设置path
pathMeasure.setPath(mPath,false);
// 根据动画进行的进度取出当前长度的matrix
boolean matrix = pathMeasure.getMatrix(animatedValue * pathMeasure.getLength(), mMatrix,
PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
if (matrix){
// 操作matrix,绘制小船
mMatrix.preTranslate(-bitmap.getWidth() / 2,-bitmap.getHeight() + DRAW_BOAT_OFFSET);
canvas.drawBitmap(bitmap,mMatrix,null);
}
}
// 开启动画
public void startAnimator(){
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.setDuration(15000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(INFINITE);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
}
}