文章目錄
參考:
https://blog.csdn.net/weixin_33935505/article/details/88000720
https://blog.csdn.net/coderinchina/article/details/53665632
PathMeasure基本概念
路徑測量,一個用來測量Path的工具類
PathMeasure常用API
常用API如Path長度測量,Path跳轉,Path片段獲取等。
PathMeasure案例實現
先畫座標軸
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);
Log.i(TAG, "onSizeChanged" + "w = " + w + ", h = " + h + ", oldw = " + oldw + ", oldh = " + oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
// 平移座標系
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);
// testNextContour(canvas);
// testGetSegment(canvas);
// testGetSegmentMoveTo(canvas);
}
forceClosed
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);
//一但測量如果是閉合狀態,那麼不管原型的Path是怎樣的,
//這邊在進行測量的時候回默認閉合
//Path的測量不會影響原本的Path
PathMeasure measure2 = new PathMeasure(path, true);
Log.i(TAG, "forceClosed=false length = " + measure1.getLength());
Log.i(TAG, "forceClosed=true length = " + measure2.getLength());
// path.close();
canvas.drawPath(path, mDeafultPaint);
}
打印結果:
forceClosed=false length = 600.0
forceClosed=true length = 800.0
1、不論 forceClosed 設置爲何種狀態(true 或者 false),都不會影響原有Path的狀態,即 Path 與 PathMeasure 關聯之後,之前的Path 不會有任何改變
2、forceClosed 的設置狀態可能會影響測量結果,如果 Path 未閉合但在與 PathMeasure 關聯的時候設置 forceClosed 爲 true 時,測量結果可能會比 Path 實際長度稍長一點.
nextContour
方法描述: 獲取在路徑中下一個輪廓,如果有下一個輪廓,則返回true,且PathMeasure切至下一個輪廓的數據;如果沒有下一個輪廓則返回false。
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);
PathMeasure measure2 = new PathMeasure(path1, false);
Log.i(TAG,"path1:"+measure2.getLength());
// 添加大矩形
//path.addRect(-200, 200, 200, 600, Path.Direction.CW);
path2.addRect(-200, -200, 200, 200, Path.Direction.CW);
PathMeasure measure3 = new PathMeasure(path2, false);
Log.i(TAG,"path2:"+measure3.getLength());
/**
* 對兩個Path進行布爾運算
* XOR保留path1與path2不共同的部分;
*/
path.op(path1,path2, Path.Op.XOR);
canvas.drawPath(path,mDeafultPaint);
PathMeasure measure = new PathMeasure(path, false);
Log.i(TAG,"path:"+measure.getLength());
float[] tan = new float[2];
float[] pos = new float[2];
//三個參數
//distance 指定Path路徑上的長度
// 兩個長度爲2的浮點數組,這個數組時用來接收數據
//tan 當前位置的正切點XY
//pos 當前Path路徑點的XY
measure.getPosTan(50f,pos,tan);
Log.i(TAG,"----------------------pos[0] = " + pos[0] + "---pos[1] = " +pos[1]);
Log.i(TAG,"----------------------tan[0] = " + tan[0] + "---tan[1] = " +tan[1]);
canvas.drawLine(tan[0],tan[1],pos[0],pos[1],mPaint);
float len1 = measure.getLength();
// 跳轉到下一條路徑
measure.nextContour();
measure.getPosTan(0f,pos,tan);
Log.i(TAG,"----------------------pos[0] = " + pos[0] + "pos[1] = " +pos[1]);
Log.i(TAG,"----------------------tan[0] = " + tan[0] + "tan[1] = " +tan[1]);
canvas.drawLine(tan[0],tan[1],pos[0],pos[1],mDeafultPaint);
float len2 = measure.getLength();
Log.i(TAG,"len1 = "+len1);
Log.i(TAG,"len2 = "+len2);
}
path1:800.0
path2:1600.0
path:1600.0
----------------------pos[0] = 150.0, pos[1] = -200.0
----------------------tan[0] = -1.0, tan[1] = 0.0
----------------------pos[0] = 100.0, pos[1] = -100.0
----------------------tan[0] = -1.0, tan[1] = 0.0
len1 = 1600.0
len2 = 800.0
getSegment
方法描述: 獲取關聯的path的片段路徑,添加至dst路徑中(並非替換,是增加)
返回值:
1、爲true時,說明截取成功,添加至dst路徑中;
2、爲false時,說明截取失敗,dst路徑不變動;
/**
* 路徑截取,且不移動開始點
*/
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, true);
canvas.drawPath(path, mPaint);
// 繪製 dst
canvas.drawPath(dst, mDeafultPaint);
}
/**
* 路徑截取,移動開始點
*/
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(200, 600, dst, true);
canvas.drawPath(path, mPaint);
// 繪製 dst
canvas.drawPath(dst, mDeafultPaint);
}
示例:箭頭圍繞圓運動
/**
* 箭頭圍繞圓運動
*/
public class PathMeasureView extends View {
private float currentValue = 0; // 用於紀錄當前的位置,取值範圍[0,1]映射Path的整個長度
private float[] pos; // 當前點的實際位置
private float[] tan; // 當前點的tangent值,用於計算圖片所需旋轉的角度
private Bitmap mBitmap; // 箭頭圖片
private Matrix mMatrix; // 矩陣,用於對圖片進行一些操作
private Paint mDeafultPaint;
private int mViewWidth;
private int mViewHeight;
private Paint mPaint;
public PathMeasureView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
pos = new float[2];
tan = new float[2];
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8; // 縮放圖片
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options);
mMatrix = new Matrix();
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.drawColor(Color.WHITE);
// 平移座標系
canvas.translate(mViewWidth/2,mViewHeight/2);
// 畫座標線
canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);
canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);
Path path = new Path(); // 創建 Path
/**
* Path.Direction.CCW 逆時針
* Path.Direction.CW 順時針
*/
path.addCircle(0, 0, 200, Path.Direction.CCW); // 添加一個圓形
PathMeasure measure = new PathMeasure(path, false); // 創建 PathMeasure
currentValue += 0.005; // 計算當前的位置在總長度上的比例[0,1]
if (currentValue >= 1) {
currentValue = 0;
}
// // 方案一
// // 獲取當前位置的座標以及趨勢
// measure.getPosTan(measure.getLength() * currentValue, pos, tan);
// canvas.drawCircle(tan[0],tan[1],20,mDeafultPaint);
//
// // 重置Matrix
// mMatrix.reset();
// // 計算圖片旋轉角度(或弧度)
// float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
// // 旋轉圖片
// mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
// // 將圖片繪製中心調整到與當前點重合
// mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);
// 方案二
// 獲取當前位置的座標以及趨勢的矩陣
measure.getMatrix(measure.getLength() * currentValue, mMatrix,
PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
// 將圖片繪製中心調整到與當前點重合(偏移加旋轉)
mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);
canvas.drawPath(path, mDeafultPaint);
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);
invalidate();
}
}
示例:圓圈加載
/**
* 圓圈加載
*/
public class LoadingView extends View {
private Path mPath;
private Paint mPaint;
private PathMeasure mPathMeasure;
private float mAnimatorValue;
private Path mDst;
private float mLength;
public LoadingView(Context context) {
this(context,null);
}
public LoadingView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPathMeasure = new PathMeasure();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPath = new Path();
mPath.addCircle(400, 400, 100, Path.Direction.CCW);
mPathMeasure.setPath(mPath, true);
mLength = mPathMeasure.getLength();
mDst = new Path();
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDst.reset();
// 硬件加速的BUG
/**
* 由於硬件加速的問題,PathMeasure中的getSegment添加到dst數組中時會被導致一些錯誤,
* 需要通過mDst.lineTo(0,0)來避免這樣一個Bug。
*/
mDst.lineTo(0,0);
/*float stop = mLength * mAnimatorValue;
mPathMeasure.getSegment(0, stop, mDst, true);*/
float stop = mLength * mAnimatorValue;
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
mPathMeasure.getSegment(start, stop, mDst, true);
canvas.drawPath(mDst, mPaint);
}
}
示例:笑臉加載
/**
* 笑臉加載
*/
public class FaceLoadingView extends View {
/**
* 左眼距離左邊的距離(控件寬度*EYE_PERCENT_W),
* 右眼距離右邊的距離(控件寬度*EYE_PERCENT_W)
*/
private static final float EYE_PERCENT_W = 0.35F;
/**
* 眼睛距離top的距離(控件的高度*EYE_PERCENT_H)
*/
private static final float EYE_PERCENT_H = 0.38F;
/**
* 嘴巴左邊跟右邊距離top的距離(控件的高度*MOUCH_PERCENT_H)
*/
private static final float MOUCH_PERCENT_H = 0.55F;
/**
* 嘴巴中間距離top的距離(控件的高度*MOUCH_PERCENT_H2)
*/
private static final float MOUCH_PERCENT_H2 = 0.7F;
/**
* 嘴巴左邊跟右邊距離邊緣的位置(控件寬度*MOUCH_PERCENT_W)
*/
private static final float MOUCH_PERCENT_W = 0.23F;
/**
* 眼睛跟嘴巴擺動的區間範圍
*/
private static final float DURATION_AREA = 0.15F;
/**
* 眼睛跟嘴巴擺動的動畫
*/
Animation mAmin = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float offset = interpolatedTime * DURATION_AREA;
mMouchH = MOUCH_PERCENT_H + offset;
mMouchH2 = MOUCH_PERCENT_H2 + offset;
mEyesH = EYE_PERCENT_H + offset;
postInvalidate();
}
};
private Paint reachedPaint;
private Paint unreachedPaint;
private Path reachedPath;
private Path unreachedPath;
private Path mouthPath = new Path();
private float mProgress = 0.1f;
private float lineWidth = dp2px(2);
private float mRadius;
private float mMouchH = MOUCH_PERCENT_H;
private float mMouchH2 = MOUCH_PERCENT_H2;
private float mEyesH = EYE_PERCENT_H;
public FaceLoadingView(Context context) {
this(context, null);
}
public FaceLoadingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FaceLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void startAni() {
mAmin.setDuration(500);
mAmin.setRepeatCount(Animation.INFINITE);
mAmin.setRepeatMode(Animation.REVERSE);
startAnimation(mAmin);
}
private void initView() {
reachedPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
reachedPaint.setStyle(Paint.Style.STROKE);
reachedPaint.setStrokeWidth(lineWidth);
reachedPaint.setColor(Color.WHITE);
reachedPaint.setStrokeJoin(Paint.Join.ROUND);
reachedPaint.setStrokeCap(Paint.Cap.ROUND);
unreachedPaint = new Paint(reachedPaint);
unreachedPaint.setColor(Color.GRAY);
}
private boolean isStart = true;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (isStart) {
startAni();
isStart = false;
}
mRadius = getWidth() / 7F / 2;
if (unreachedPath == null) {
unreachedPath = new Path();
}
unreachedPath.addRoundRect(new RectF(lineWidth, lineWidth, w - lineWidth, h - lineWidth), w / 6, w / 6, Path.Direction.CCW);
if (reachedPath == null) {
reachedPath = new Path();
}
reachedPath.addRoundRect(new RectF(lineWidth, lineWidth, w - lineWidth, h - lineWidth), w / 6, w / 6, Path.Direction.CW);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
canvas.save();
//draw face
drawFace(canvas);
//drawreached rect
drawReachedRect(canvas);
canvas.restore();
}
/**
* draw face
*/
private void drawFace(Canvas canvas) {
unreachedPaint.setStyle(Paint.Style.FILL);
//畫左邊的眼睛
canvas.drawCircle(getWidth() * EYE_PERCENT_W, getHeight() * mEyesH - mRadius, mRadius, unreachedPaint);
//畫右邊的眼睛
canvas.drawCircle(getWidth() * (1 - EYE_PERCENT_W), getHeight() * mEyesH - mRadius, mRadius, unreachedPaint);
mouthPath.reset();
//畫嘴巴
mouthPath.moveTo(getWidth() * MOUCH_PERCENT_W, getHeight() * mMouchH);
mouthPath.quadTo(getWidth() / 2, getHeight() * mMouchH2, getWidth() * (1 - MOUCH_PERCENT_W), getHeight() * mMouchH);
unreachedPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mouthPath, unreachedPaint);
}
private void drawReachedRect(Canvas canvas) {
unreachedPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(unreachedPath, unreachedPaint);
PathMeasure measure = new PathMeasure(reachedPath, false);
float length = measure.getLength();
//獲取當前path長度
float currLength = length * mProgress;
Path path = new Path();
/**
* 因爲uc的起始位置是在頂部的位置,而我們的path的起始位置是在左下的位置,
* 所以我們需要加上一個length*1/3f偏移量
*/
measure.getSegment(length * 1 / 3f, currLength + length * 1 / 3f, path, true);
canvas.drawPath(path, reachedPaint);
/**
* 當mProgress>=2/3f的時候,也就是回到了起點的時候,我們得截取兩段path了
* 一段是1/3的位置到2/3
* 一段是0到1/3的位置
*/
// if(mProgress>=2/3f){
// Path path2=new Path();
// measure.getSegment(0,length*(mProgress-2/3f),path2,true);
// canvas.drawPath(path2,reachedPaint);
// }
}
public float dp2px(float dpValue) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}
public void setProgress(float progress) {
Log.d("TAG", "" + progress);
if (progress < mProgress) {
return;
}
this.mProgress = progress;
postInvalidate();
}
public void loadComplete() {
mAmin.cancel();
clearAnimation();
setVisibility(View.GONE);
}
}
faceview = (FaceLoadingView) findViewById(R.id.faceview);
faceview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
while (progress <= 100){
progress += 2;
faceview.setProgress(progress / 100);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
});
示例:小船滑動
/**
* 小船滑動
*/
public class WaveView extends View {
private static final int INT_WAVE_LENGTH = 1000;
private static final String TAG = "WaveView";
private Path mPath;
private Paint mPaint;
private int mDeltaX;
private Bitmap mBitMap;
private PathMeasure mPathMeasure;
private float[] pos;
private float[] tan;
private Matrix mMatrix;
private float faction;
public WaveView(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
pos = new float[2];
tan = new float[2];
mMatrix = new Matrix();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
mBitMap = BitmapFactory.decodeResource(getResources(),R.drawable.timg,options);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
int orginY = 800;
int halfWaveLength = INT_WAVE_LENGTH / 2;
mPath.moveTo(-INT_WAVE_LENGTH + mDeltaX, orginY);
for(int i = -INT_WAVE_LENGTH ; i < getWidth() + INT_WAVE_LENGTH;
i += INT_WAVE_LENGTH){
mPath.rQuadTo(halfWaveLength/2,120,halfWaveLength,0);
mPath.rQuadTo(halfWaveLength/2,-120,halfWaveLength,0);
}
mPath.lineTo(getWidth(),getHeight());
mPath.lineTo(0,getHeight());
mPath.close();
canvas.drawPath(mPath,mPaint);
mPathMeasure = new PathMeasure(mPath,false);
float length = mPathMeasure.getLength();
mMatrix.reset();
boolean posTan = mPathMeasure.getPosTan(length*faction,pos,tan);
canvas.drawCircle(tan[0],tan[1],20,mPaint);
canvas.drawLine(tan[0],tan[1],pos[0],pos[1],mPaint);
Log.i(TAG,"----------------------pos[0] = " + pos[0] + "pos[1] = " +pos[1]);
Log.i(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, mBitMap.getWidth()/2, mBitMap.getHeight() / 2);
mMatrix.postTranslate(pos[0]- mBitMap.getWidth() / 2,pos[1] - mBitMap.getHeight());
canvas.drawBitmap(mBitMap,mMatrix,mPaint);*/
// 方案二 :直接使用API
//直接幫你算了角度並且幫你進行了旋轉
mPathMeasure.getMatrix(length*faction, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
mMatrix.preTranslate(- mBitMap.getWidth() / 2, - mBitMap.getHeight());
canvas.drawBitmap(mBitMap,mMatrix,mPaint);
}
//
}
public void startAnimation(){
// ValueAnimator anim = ValueAnimator.ofInt(0,INT_WAVE_LENGTH);
// anim.setDuration(1000);
// anim.setInterpolator(new LinearInterpolator());
// anim.setRepeatCount(ValueAnimator.INFINITE);
// anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// @Override
// public void onAnimationUpdate(ValueAnimator animation) {
// mDeltaX = (int) animation.getAnimatedValue();
// postInvalidate();
// }
// });
// anim.start();
ValueAnimator 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.i(TAG,"----------------------faction = " + faction);
postInvalidate();
}
});
animator.start();
}
}