浅谈Android PathMeasure详解和应用

PathMeasure,顾名思义,就是一个用来测量Path的类,主要有以下方法:

构造方法

无参构造方法:

PathMeasure()

创建一个空的PathMeasure,用这个构造函数可创建一个空的 PathMeasure,但是使用之前需要先调用 setPath 方法来与 Path 进行关联。被关联的 Path 必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联。

有参构造方法

PathMeasure(Path path, boolean forceClosed)

该构造函数是创建一个 PathMeasure 并关联一个 Path, 其实和创建一个空的 PathMeasure 后调用 setPath 进行关联效果是一样的,同样,被关联的 Path 也必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联。 该方法有两个参数,第一个参数自然就是被关联的 Path 了,第二个参数是用来确保 Path 闭合,如果设置为 true, 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话)。

这里需要说明以下forceClosed:

1)不论 forceClosed 设置为何种状态(true 或者 false), 都不会影响原有Path的状态,即 Path 与 PathMeasure 关联之后,之前的的 Path 不会有任何改变。

2)forceClosed 的设置状态可能会影响测量结果,如果 Path 没有闭合但在与 PathMeasure 关联的时候设置 forceClosed 为 true 时,测量结果可能会比 Path 实际长度稍长一点,获取得到的是该 Path 闭合时的状态。

setPath

setPath(Path path, boolean forceClosed)方法就是关联一个Path,需要预先创建好。

isClosed

isClosed方法用于判断 Path 是否闭合,但是如果你在关联 Path 的时候设置 forceClosed 为 true 的话,这个方法的返回值则一定为true。

getLength

getLength()方法用于获取Path的长度。

public class PathMeasureView extends View {

 private static final String TAG = "lwj";
 private int mViewHeight;
 private int mViewWidth;
 private Paint paint;

 public PathMeasureView(Context context) {
  super(context);
  init(context);
 }

 private void init(Context context) {
  paint = new Paint();
  paint.setColor(Color.RED);
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(10);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  canvas.translate(mViewWidth/2, mViewHeight/2);

  Path path = new Path();
		path.lineTo(0, 300);
		path.lineTo(300, 300);
		path.lineTo(300, 0);

		PathMeasure measure = new PathMeasure(path, false);
		PathMeasure measure2 = new PathMeasure(path, true);
		Log.i(TAG, "length:"+measure.getLength());//900
		Log.i(TAG,"length:"+ measure2.getLength());//1200
  canvas.drawPath(path, paint);

 }

 //该方法在当前View尺寸变化时被调用
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  mViewHeight = h;
  mViewWidth = w;
 }
}

nextContour

我们知道 Path 可以由多条曲线构成,但不论是 getLength , getgetSegment 或者是其它方法,都只会在其中第一条线段上运行,而这个 nextContour 就是用于跳转到下一条曲线到方法,如果跳转成功,则返回 true, 如果跳转失败,则返回 false。 注意:使用多路径的效果需要关闭硬件加速。

setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Path path = new Path();
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
path.addRect(-100, -100, 100, 100, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float length = measure.getLength();
//获取下一个路径,有可能没有多个路径了,返回false
boolean nextContour = measure.nextContour();
float length2 = measure.getLength();
Log.i("damon", "length1:"+length);
Log.i("damon", "length2:"+length2);
canvas.drawPath(path, paint);

getSegment

boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo):用于获取Path的一个片段。

解析:

1)返回值(boolean):判断截取是否成功,true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容。

2)startD:开始截取位置距离 Path 起点的长度 取值范围: 0 <= startD < stopD <= Path总长度;

3)stopD:结束截取位置距离 Path 起点的长度 取值范围: 0 <= startD < stopD <= Path总长度;

4)dst:截取的 Path 将会添加到 dst 中 注意: 是添加,而不是替换;

5)startWithMoveTo:起始点是否使用 moveTo,用于保证截取的 Path 第一个点位置不变。

注意:

• 如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容。

• 如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,请关闭硬件加速或者给 dst 添加一个单个操作,例如: dst.rLineTo(0, 0)

• 可以用以下规则来判断 startWithMoveTo 的取值:

true:保证截取得到的 Path 片段不会发生形变;

false:保证存储截取片段的 Path(dst) 的连续性。

Path path = new Path();
//多路径的效果需要关闭硬件加速!!
path.addRect(-200, -200, 200, 200, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float length = measure.getLength();
Log.i("damon", "length1:"+length);
canvas.drawPath(path, paint);
Path dst = new Path();
dst.lineTo(-300, -300);
//startWithMoveTo:false,代表该起始点是否位上一个的结束点(是否保持连续性)。
measure.getSegment(200, 600, dst , false);
paint.setColor(Color.RED);
canvas.drawPath(dst, paint);

getMatrix

getMatrix(float distance, Matrix matrix, int flags):获取路径上某一长度的位置以及该位置的正切值的矩阵。

返回值(boolean):判断获取是否成功,true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变;

distance:距离 Path 起点的长度,取值范围: 0 <= distance <= getLength;

matrix:根据 falgs 封装好的matrix 会根据 flags 的设置而存入不同的内容;

flags:规定哪些内容会存入到matrix中,可选择:

POSITION_MATRIX_FLAG(位置)

ANGENT_MATRIX_FLAG(正切)

getPosTan

getPosTan(float distance, float[] pos, float[] tan):获取指定长度的位置坐标及该点切线值tangle。

返回值(boolean):判断获取是否成功,true表示成功,数据会存入 pos 和 tan 中, false 表示失败,pos 和 tan 不会改变;

distance:距离 Path 起点的长度,取值范围: 0 <= distance <= getLength;

pos:该点的坐标值,坐标值: (x==[0], y==[1]);

tan:该点的正切值,正切值: (x==[0], y==[1])。

通过 三角函数tan 得值计算出图片旋转的角度,tan 是 tangent 的缩写, 其中tan0是邻边边长,tan1是对边边长,而Math中 atan2 方法是根据正切是数值计算出该角度的大小,得到的单位是弧度,所以上面又将弧度转为了角度。

Path path = new Path();
path.addCircle(0, 0, 300, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float[] pos = new float[2];
float[] tan = new float[2];//tan=y/x
measure.getPosTan(measure.getLength()/4, pos , tan );
Log.i("damon", "position:x-"+pos[0]+", y-"+pos[1]);
Log.i("damon", "tan:x-"+tan[0]+", y-"+tan[1]);
canvas.drawPath(path, paint);

应用

绘制一个放大镜,然后慢慢沿着放大镜的路径慢慢撤退消失,变成圆形搜索的loading,接着loading完成之后,沿着路径绘制出放大镜。 如效果图所示:

这样一个自定义View,需要用到PathMeasure,动画等知识配合来做。

public class SearchView extends View {

 // 画笔
 private Paint mPaint;

 // View 宽高
 private int mViewWidth;
 private int mViewHeight;

 // 这个视图拥有的状态
 public static enum State {
  NONE,
  STARTING,
  SEARCHING,
  ENDING
 }

 // 当前的状态(非常重要)
 private State mCurrentState = State.NONE;

 // 放大镜与外部圆环
 private Path path_srarch;
 private Path path_circle;

 // 测量Path 并截取部分的工具
 private PathMeasure mMeasure;

 // 默认的动效周期 2s
 private int defaultDuration = 2000;

 // 控制各个过程的动画
 private ValueAnimator mStartingAnimator;
 private ValueAnimator mSearchingAnimator;
 private ValueAnimator mEndingAnimator;

 // 动画数值(用于控制动画状态,因为同一时间内只允许有一种状态出现,具体数值处理取决于当前状态)
 private float mAnimatorValue = 0;

 // 动效过程监听器
 private ValueAnimator.AnimatorUpdateListener mUpdateListener;
 private Animator.AnimatorListener mAnimatorListener;

 // 用于控制动画状态转换
 private Handler mAnimatorHandler;

 // 判断是否已经搜索结束
 private boolean isOver = false;

 private int count = 0;

 public SearchView(Context context) {
  super(context);

  initPaint();

  initPath();

  initListener();

  initHandler();

  initAnimator();

  // 进入开始动画
  mCurrentState = State.STARTING;
  mStartingAnimator.start();

 }

 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);
 }

 private void initPath() {
  path_srarch = new Path();
  path_circle = new Path();

  mMeasure = new PathMeasure();

  // 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值
  RectF oval1 = new RectF(-50, -50, 50, 50);   // 放大镜圆环
  path_srarch.addArc(oval1, 45, 359.9f);

  RectF oval2 = new RectF(-100, -100, 100, 100);  // 外部圆环
  path_circle.addArc(oval2, 45, -359.9f);

  float[] pos = new float[2];

  mMeasure.setPath(path_circle, false);    // 放大镜把手的位置
  mMeasure.getPosTan(0, pos, null);

  path_srarch.lineTo(pos[0], pos[1]);     // 放大镜把手

  Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);
 }

 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) {
    // getHandle发消息通知动画状态更新
    mAnimatorHandler.sendEmptyMessage(0);
   }

   @Override
   public void onAnimationCancel(Animator animation) {

   }

   @Override
   public void onAnimationRepeat(Animator animation) {

   }
  };
 }

 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();
       Log.e("Update", "RESTART");

       count++;
       if (count>2){  // count大于2则进入结束状态
        isOver = true;
       }
      } else {  // 如果搜索已经结束 则进入结束动画
       mCurrentState = State.ENDING;
       mEndingAnimator.start();
      }
      break;
     case ENDING:
      // 从结束动画转变为无状态
      mCurrentState = State.NONE;
      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);
 }

 @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);

  drawSearch(canvas);
 }

 private void drawSearch(Canvas canvas) {

  mPaint.setColor(Color.WHITE);

  canvas.translate(mViewWidth / 2, mViewHeight / 2);

  canvas.drawColor(Color.parseColor("#0082D7"));

  switch (mCurrentState) {
   case NONE:
    canvas.drawPath(path_srarch, mPaint);
    break;
   case STARTING:
    mMeasure.setPath(path_srarch, false);
    Path dst = new Path();
    mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst, true);
    canvas.drawPath(dst, mPaint);
    break;
   case SEARCHING:
    mMeasure.setPath(path_circle, false);
    Path dst2 = new Path();
    float stop = mMeasure.getLength() * mAnimatorValue;
    float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
//    float start = stop-50;
    mMeasure.getSegment(start, stop, dst2, true);
    canvas.drawPath(dst2, mPaint);
    break;
   case ENDING:
    mMeasure.setPath(path_srarch, false);
    Path dst3 = new Path();
    mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst3, true);
    canvas.drawPath(dst3, mPaint);
    break;
  }
 }
}

以上就是关于PathMeasure的详解和应用,需要读者去多点动手才能理解其中的精髓的地方。

(0)

相关推荐

  • Android中PathMeasure仿支付宝支付动画

    前言 在 Android 自定义 View 中,Path 可能用的比较多,PathMeasure 可能用的比较少,就我而言,以前也没有使用过 PathMeasure 这个 api,看到别人用 PathMeasure 和 ValueAnimator 结合在一起完成了很好的动画效果,于是我也学习下 PathMeasure ,此处记录下. PathMeasure 构造器: forceClosed 含义: // 创建一个 Path 对象 path = new Path(); path.moveTo(20

  • Android自定义View 使用PathMeasure简单模仿系统ProgressBar(四)

    使用PathMeasure简单模仿系统ProgressBar,效果如下: 还蛮像的吧,有的人问了,系统自带的你闲的搞这个干嘛,当然是纯粹为了学习PathMeasure这个类. PathMeasure是用来测量Path路径的,可以截取路径中某一段路径,通过改变这段路径的起点.终点,达到类似VectorDrawable中的路径动画效果: 直接new就可以获得PathMeasure对象: PathMeasure pathMeasure = new PathMeasure(); 或者 PathMeasu

  • 浅谈Android PathMeasure详解和应用

    PathMeasure,顾名思义,就是一个用来测量Path的类,主要有以下方法: 构造方法 无参构造方法: PathMeasure() 创建一个空的PathMeasure,用这个构造函数可创建一个空的 PathMeasure,但是使用之前需要先调用 setPath 方法来与 Path 进行关联.被关联的 Path 必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联. 有参构造方法 PathMeasure(Path path, boolean fo

  • 浅谈android Fragment横竖屏翻转对重新加载的要求

    有时候,我们在同一个activity里面有很多fragment,在横竖屏的时候,有些fragment要求重新加载数据,有些不需要,如何简单控制这些需求,本人分享一点小经验,欢迎大家指正里面的错误. 大家都知道横竖屏翻转的时候,activity会重启,fragment也会重新加载,如果现在要求在横竖屏翻转的时候,同一个activity中,一个fragment要重新加载,一个不要重新加载,那该怎么办呢? 如果在activity里面继承了onConfigurationChanged这个方法之后,这个a

  • 浅谈Android中Service的注册方式及使用

    Service通常总是称之为"后台服务",其中"后台"一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: 1.并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的): 2.具有较长时间的运行特性. 1.Service AndroidManifest.xml 声明 一般而言,从Service的启动方式上

  • 浅谈Android Studio导出javadoc文档操作及问题的解决

    1.在Android studio中进行打开一个项目的文件之后,然后进行点击Android stuio中菜单中的"tools"的选项.在弹出了下拉菜单中,进行选中下拉菜单中的"Generate JavaDoc"的选项. 2.在弹出界面中 Output directory是你即将生产的javadoc文件的存储位置,图中1指示的位置:正常点击ok即可: 但是如果有异常情况 比如空指针异常或者文档乱码 java.lang.NullPointerException 或者 j

  • 浅谈Android应用安全防护和逆向分析之apk反编译

    概述 这里是Mac环境,如果是window环境的同学,在环境搭建和工具上可以选择Window环境的.先看看需要到的工具: 1.apktool:https://ibotpeaches.github.io/Apktool/install/ 2.dex2jar:https://github.com/pxb1988/dex2jar 3.jd-gui:http://jd.benow.ca 注意:工具一定要是当前最新版本的,否则很容易出现一些莫名其妙的错误. 先看一下项目的包结构 然后在简单看MainAct

  • 浅谈Android插件化

    目录 一.认识插件化 1.1 插件化起源 1.2 插件化优点 1.3 与组件化的区别 二.插件化的技术难点 三.ClassLoader Injection 3.1 java 中的 ClassLoader 3.2 android 中的 ClassLoader 3.3 双亲委派机制 3.4 如何加载插件中的类 3.5 执行插件类的方法 四.Runtime Container 4.1 为什么没有注册的 Activity 不能和系统交互 4.2 运行时容器技术 4.3 字节码替换 五.Resource

  • 浅谈android中数据库的拷贝

    SQLiteDatabase不支持直接从assets读取文件,所以要提前拷贝数据库.在读取数据库时,先在项目中建立assets文件夹用于存放外部文件,将数据库文件拷到该目录下. 代码方法: /** * 拷贝数据库至file文件夹下 * @param dbName 数据库名称 */ private void initAddressDB(String dbName) { //1,在files文件夹下创建同名dbName数据库文件过程 File files=getFilesDir();//获取/dat

  • Android AsyncTask详解及使用方法

     Android AsyncTask详解及使用方法  简介: AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务. AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程. 一.如果想自定义一个AsyncTask,可以写一个类,继承AsyncTask. eg: . //第一个参数为doInBackground中传入的类型,第二个为

  • 浅谈Android Activity与Service的交互方式

    实现更新下载进度的功能 1. 通过广播交互 Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上. 优缺点分析: 通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用.但缺点也十分明显,发送广播受到系统制约.系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟.同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应

  • Android CoordinatorLayout详解及实例代码

    Android CoordinatorLayout详解 一.CoordinatorLayout有什么作用 CoordinatorLayout作为"super-powered FrameLayout"基本实现两个功能: 1.作为顶层布局 2.调度协调子布局 CoordinatorLayout使用新的思路通过协调调度子布局的形式实现触摸影响布局的形式产生动画效果.CoordinatorLayout通过设置子View的 Behaviors来调度子View.系统(Support V7)提供了A

随机推荐