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);
但是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:代表该起始点是否是上一个的结束点(是否保持连续性)。
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的代码都在上面,都有表明注释,在布局文件中直接使用就可以了;
<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>