PathMeasure顧名思義是Path的一個測量工具類,可以對Path繪製的路徑進行測量、裁剪等操作;在使用的時候直接new一個PathMeasure對象就可以了,系統提供了兩種類型的構造方法:
//無參構造
public PathMeasure() {
mPath = null;
native_instance = native_create(0, false);
}
//有參構造
public PathMeasure(Path path, boolean forceClosed) {
// The native implementation does not copy the path, prevent it from being GC'd
mPath = path;
native_instance = native_create(path != null ? path.readOnlyNI() : 0,
forceClosed);
}
公共方法:
//關聯一個Path
void setPath(Path path, boolean forceClosed)
//是否閉合
boolean isClosed()
//獲取Path的長度
float getLength()
//跳轉到下一個輪廓
boolean nextContour()
//截取片段
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
//獲取指定長度的位置座標及該點 切線值tangle
boolean getPosTan(float distance, float[] pos, float[] tan)
//獲取指定長度的位置座標及該點Matrix(矩陣)
boolean getMatrix(float distance, Matrix matrix, int flags)
如果創建對象的時候使用的是無參構造,可以通過setPath方法關聯Path;
如果創建對象的時候使用的是有參構造,在傳入第二個參數的時候需要注意;
forceClosed=false :path繪製沒有關閉的話就不會進行測量
forceClosed=true:不管path繪製是否關閉,都會進行測量計算長度
//平移畫布 viewWidth--->view寬度的一半 viewHeight--->view高度的一半
canvas.translate(viewWidth / 2, viewHeight / 2);
Path path = new Path();
path.lineTo(0, 100);
path.lineTo(100, 100);
path.lineTo(100, 0);
PathMeasure measure1 = new PathMeasure(path, false);
PathMeasure measure2 = new PathMeasure(path, true);
Log.e("TAG", "measure1---false-->" + measure1.getLength());
Log.e("TAG", "measure2---true-->" + measure2.getLength());
canvas.drawPath(path, paint);
看效果path並沒有閉合;
但是measure1和measure2由於第二參數的不同,在通過getLength()方法獲取path路徑長度的時候結果就不一樣;
measure1所獲取的就是path繪製的長度,measure2獲取的是path繪製的長度及未必閉合處的長度;所有在使用有參構造的時候第二個參數一般傳入false。
nextContour—>跳轉到下一個輪廓
Path path = new Path();
//多路徑的效果需要關閉硬件加速
path.addRect(-100, -100, 100, 100, Path.Direction.CW);
path.addRect(-50, -50, 50, 50, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure(path, false);
//獲取下一個路徑,有可能沒有多個路徑了,返回false
float length = pathMeasure.getLength();
boolean nextContour = pathMeasure.nextContour();//獲取下一個路徑,有可能沒有多個路徑了,返回false
float length2 = pathMeasure.getLength();
Log.i("TAG", "length1:" + length);
Log.i("TAG", "length2:" + length2);
canvas.drawPath(path, paint);
在使用繪製多條路徑的時候需要注意,要關閉硬件加速,可以在AndroidManifest.xml文件中的application中設置
android:hardwareAccelerated="false"
也可以使用View進行設置關閉硬件加速。
getSegment—>截取片段
Path path=new Path();
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
PathMeasure pathMeasure=new PathMeasure(path,false);
canvas.drawPath(path, paint);
Path dst=new Path();
dst.lineTo(-300,-300);
pathMeasure.getSegment(200,600,dst,true);
paint.setColor(Color.RED);
canvas.drawPath(dst, paint);
getSegment()方法可以和Path的lineTo()一起使用的,不過在調用getSegment()方法時,第四個參數是傳入一個boolean值,傳入false和true的效果是不一樣的;
第四個參數false或true:代表該起始點是否是上一個的結束點(是否保持連續性)。
將上面的true改爲false
getPosTan—>獲取指定長度的位置座標及該點 切線值tangle
Path path=new Path();
path.addCircle(0,0,300, Path.Direction.CW);
PathMeasure pathMeasure=new PathMeasure(path,false);
float [] pos=new float[2];
float [] tan=new float[2];
pathMeasure.getPosTan(pathMeasure.getLength()/4,pos,tan);
Log.i("TAG", "position:x-"+pos[0]+", y-"+pos[1]);
Log.i("TAG", "tan:x-"+tan[0]+", y-"+tan[1]);
canvas.drawPath(path, paint);
打印的值:
Path+PathMeasure自定義搜索放大鏡效果
這個效果的話主要涉及到外圓環、放大鏡的圓、放大鏡的手柄這三個東西的繪製,而這三個東西有涉及到幾種狀態,普通、放大鏡開始執行動畫、開始搜索、搜索結束四種狀態的繪製;代碼如下:
public class SearchView extends View {
//畫筆
private Paint mPaint;
//放大鏡和外部圓環
private Path pathSearch;
private Path pathCircle;
//測量Path並截取工具類
private PathMeasure mMeasure;
// 動畫數值(用於控制動畫狀態,因爲同一時間內只允許有一種狀態出現,具體數值處理取決於當前狀態)
private float mAnimatorValue = 0;
//用於控制動畫狀態轉換
private Handler mAnimatorHandler;
//動效過程監聽
private ValueAnimator.AnimatorUpdateListener mUpdateListener;
private Animator.AnimatorListener mAnimatorListener;
//當前繪製的狀態
private State mCurrentState = State.NONE;
//判斷是否已經搜索結束
private boolean isOver = false;
//視圖的所有狀態
public enum State {
NONE,
STARTING,
SEARCHING,
ENDING
}
//控制各個過程的動畫
//開始動畫
private ValueAnimator mStartingAnimator;
//搜索動畫
private ValueAnimator mSearchingAnimator;
//搜索結束動畫
private ValueAnimator mEndingAnimator;
private int count = 0;
//默認動畫的時長
private int defaultDuration = 2000;
private int viewHeight;
private int viewWidth;
public SearchView(Context context) {
this(context, null);
}
public SearchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
initPath();
initListener();
initHandler();
initAnimator();
// 進入開始動畫
mCurrentState = State.STARTING;
mStartingAnimator.start();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//視圖大小改變時會回調
viewWidth = w;
viewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//進行繪製
drawSearch(canvas);
}
/**
* 進行繪製
*
* @param canvas 對應的畫布
*/
private void drawSearch(Canvas canvas) {
mPaint.setColor(Color.WHITE);
//平移畫布
canvas.translate(viewWidth / 2, viewHeight / 2);
canvas.drawColor(Color.parseColor("#0082D7"));
//根據狀態進行繪製
switch (mCurrentState) {
case NONE:
//普通狀態
canvas.drawPath(pathSearch, mPaint);
break;
case STARTING:
//開始狀態
mMeasure.setPath(pathSearch, false);
Path dts = new Path();
//進行裁剪
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dts,true);
//繪製截取部分
canvas.drawPath(dts,mPaint);
break;
case SEARCHING:
//搜索狀態
mMeasure.setPath(pathCircle,false);
Path dts1 = new Path();
float stop = mMeasure.getLength() * mAnimatorValue;
float start=(float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
mMeasure.getSegment(start,stop,dts1,true);
//繪製截取部分
canvas.drawPath(dts1,mPaint);
break;
case ENDING:
//結束狀態
mMeasure.setPath(pathSearch, false);
Path dst2 = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst2, true);
canvas.drawPath(dst2, mPaint);
break;
}
}
/**
* 初始化屬性動畫
*/
private void initAnimator() {
//初始化
mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);
//動畫更新監聽
mStartingAnimator.addUpdateListener(mUpdateListener);
mSearchingAnimator.addUpdateListener(mUpdateListener);
mEndingAnimator.addUpdateListener(mUpdateListener);
//動畫執行監聽
mStartingAnimator.addListener(mAnimatorListener);
mSearchingAnimator.addListener(mAnimatorListener);
mEndingAnimator.addListener(mAnimatorListener);
}
/**
* 初始化Handler 並根據消息更新繪製狀態
*/
private void initHandler() {
mAnimatorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mCurrentState) {
case STARTING://開始
// 從開始動畫轉換好搜索動畫
isOver = false;
mCurrentState = State.SEARCHING;
//移除開始動畫的所有監聽
mStartingAnimator.removeAllListeners();
//開始搜索動畫
mSearchingAnimator.start();
break;
case SEARCHING:
// 如果搜索未結束 則繼續執行搜索動畫
if (isOver) {
mSearchingAnimator.start();
count++;
// count大於2則進入結束狀態
if (count > 2) {
isOver = true;
}
} else {
// 如果搜索已經結束 則進入結束動畫
mCurrentState = State.ENDING;
mEndingAnimator.start();
}
break;
case ENDING://結束
// 從結束動畫轉變爲無狀態
mCurrentState = State.NONE;
break;
}
}
};
}
/**
* 動畫監聽
*/
private void initListener() {
mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
};
mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//動畫開始回調
}
@Override
public void onAnimationEnd(Animator animation) {
//動畫結束回調
//發送消息通知動畫已經結束
mAnimatorHandler.sendEmptyMessage(0);
}
@Override
public void onAnimationCancel(Animator animation) {
//動畫取消回調
}
@Override
public void onAnimationRepeat(Animator animation) {
//動畫重複回調
}
};
}
/**
* 初始化Path
*/
private void initPath() {
pathSearch = new Path();
pathCircle = new Path();
mMeasure = new PathMeasure();
// 注意,不要到360度,否則內部會自動優化,測量不能取到需要的數值
// 放大鏡圓環
RectF oval1 = new RectF(-50, -50, 50, 50);
pathSearch.addArc(oval1, 45, 359.9f);
// 外部圓環
RectF oval2 = new RectF(-100, -100, 100, 100);
pathCircle.addArc(oval2, 45, -359.9f);
float[] pos = new float[2];
// 放大鏡把手的位置
//設置關聯Path
mMeasure.setPath(pathCircle, false);
//獲取座標
mMeasure.getPosTan(0, pos, null);
// 放大鏡把手
pathSearch.lineTo(pos[0], pos[1]);
}
/**
* 初始化畫筆
*/
private void initPaint() {
mPaint = new Paint();
//設置樣式爲描邊
mPaint.setStyle(Paint.Style.STROKE);
//設置畫筆爲白色
mPaint.setColor(Color.WHITE);
//設置畫筆大小
mPaint.setStrokeWidth(15);
//設置畫帽
mPaint.setStrokeCap(Paint.Cap.ROUND);
//設置抗鋸齒
mPaint.setAntiAlias(true);
}
}
自定義View的代碼都在上面,都有表明註釋,在佈局文件中直接使用就可以了;
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.pathmeasuretest.MainActivity">
<com.pathmeasuretest.SearchView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>