Android 进阶——高级UI必知必会之借助PathMeasure打造酷炫Path特效(六)

引言

前一篇文章中总结了关于Path在UI体系当中举足轻重的作用,Android提供了方便的API直接绘制贝塞尔曲线、数学函数、图形组合等简单的图形,但假如我们想要动态的随心所欲的截取某一段路径或者获取任意路径上某一个点的坐标?该如何去做呢?这篇章就总结下这方面的知识。

相关系列文件链接如下:

一、PathMeasure概述

有时候为了更酷炫的效果需要去获取Path上每一个路径点的坐标,此时就需要知道对应的数学算法(比如贝塞尔曲线上的点用的De Casteljau算法);但对于普通的的Path来说,是很难通过简单的函数方法来进行计算的,于是Android提供了PathMeasure,(也许是太简单,官方文档一句介绍都没有),顾名思义PathMeasure是一个用来“测量”Path的类。

二、PathMeasure的主要方法

1、PathMeasure的构造方法

PathMeasure的构造方法只有两个:

  • 默认的空的构造方法创建一个空的PathMeasure对象

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

  • 通过传入已创建完毕的Path创建一个与之相关联的PathMeasure对象

    可创建一个 PathMeasure 并自动关联传入的Path(和创建一个空的 PathMeasure 后调用 setPath 进行关联效果是一样的),同样被关联的 Path 也必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联,forceClosed表示是否闭合传入的路径, true 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话)。

PathMeasure()	创建一个空的PathMeasure
/**
*@param forceClosed,true则表示把传入的路径当成闭合的来进行测量。
*/
PathMeasure(Path path, boolean forceClosed)	

注意:

  1. 不论 forceClosed 设置为何种状态(true 或者 false), 都不会影响原有Path的状态,即 Path 与 PathMeasure 关联之后,之前的的 Path 不会有任何改变。
  2. forceClosed 的设置状态可能会影响测量结果,如果 Path 未闭合但在与 PathMeasure 关联的时候设置 forceClosed 为 true 时,测量结果可能会比 Path 实际长度稍长一点,获取到到是该 Path 闭合时的状态。

2、PathMeasure的主要方法

2.1、setPath关联Path

void **setPath(Path path, boolean forceClosed)**用于关联传入的Path(已经创建完毕的Path),参数和构造方法中的作用类似。

2.2、isClosed()判断闭合

boolean isClosed() 用于判断关联的路径是否闭合,但是如果你在关联 Path 的时设置 forceClosed 为 true 的话,则此方法的返回值则恒为true。

2.3、getLength获取长度

float getLength()|获取Path的长度,是否闭合会直接影响结果。

2.4、nextContour跳转到下一个轮廓

boolean nextContour() 跳转到下一个轮廓,因为Path 可能由多条曲线构成,但不论是 getLength、还是getSegment 抑或其它方法,都只能在其中第一条线段上运行,为了完成所有这条轮廓的测量需要跳转到下一条曲线到方法,若跳转成功则返回 true, 反之,失败则返回 false。

2.5、getSegment截取路径片段

boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取路径的片段,它是PathMeasure最核心的方法,注意此处传入的值并不是对应的坐标值,各参数意义如下:

参数 作用 备注
返回值(boolean) 判断截取是否成功 true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容
startD 开始截取位置距离Path 起点的长度 取值范围为 0<= startD < stopD < = Path总长度
stopD 结束截取位置距离 Path 起点的长度 取值范围为0 <= startD < stopD <= Path总长度
dst 截取的 Path 将会添加到 dst 中 是添加而不是替换
startWithMoveTo 起始点是否使用 moveTo 用于保证截取的 Path 第一个点位置不变

使用getSegment需要注意以下几点:

  • 如果 startD、stopD 的数值不在取值范围 [0, getLength] 内或者 startD == stopD ,则返回值为 false且不会改变 dst 内容。
  • 使用以下规则来决定 startWithMoveTo 的取值,为了保证截取得到的 Path 片段不会发生形变则取true;而保证存储截取片段的 Path(dst) 的连续性则取false

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

2.6、getPosTan获取指定长度的位置坐标及该点切线值tangle

boolean 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 方法是根据正切是数值计算出该角度的大小,得到的单位是弧度,所以上面又将弧度转为了角度。

2.7、getMatrix获取指定长度的位置坐标及该Matrix(矩阵)

**boolean getMatrix(float distance, Matrix matrix, int flags)**获取指定长度的位置坐标及该Matrix(矩阵).

参数 作用 备注
返回值(boolean) 判断获取是否成功 true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变
distance 距离 Path 起点的长度 取值范围: 0 <= distance <= getLength
matrix 根据 falgs 封装好的matrix 会根据 flags 的设置而存入不同的内容
flags 规定哪些内容会存入到matrix中 可选择POSITION_MATRIX_FLAG(位置) 或ANGENT_MATRIX_FLAG(正切)

其实源码没啥好看的,核心操作都是在native层去完成的,Orz…

public class PathMeasure {
    private Path mPath;

    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
     */
    public void setPath(Path path, boolean forceClosed) {
        mPath = path;
        native_setPath(native_instance,
                path != null ? path.readOnlyNI() : 0,
                forceClosed);
    }

    /**
     * 返回当前轮廓的总长度,或者如果没有路径,则返回0。与此度量对象相关联。
     */
    public float getLength() {
        return native_getLength(native_instance);
    }

    /**
     *  获取指定长度的位置坐标及该点切线值
     * @param distance The distance along the current contour to sample 位置
     * @param pos If not null, returns the sampled position (x==[0], y==[1]) 坐标值
     * @param tan If not null, returns the sampled tangent (x==[0], y==[1])  切线值
     * @return false if there was no path associated with this measure object
     */
    public boolean getPosTan(float distance, float pos[], float tan[]) {
        if (pos != null && pos.length < 2 ||
                tan != null && tan.length < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return native_getPosTan(native_instance, distance, pos, tan);
    }

    public static final int POSITION_MATRIX_FLAG = 0x01;    // must match flags in SkPathMeasure.h
    public static final int TANGENT_MATRIX_FLAG  = 0x02;    // must match flags in SkPathMeasure.h

    /**
     * @param distance The distance along the associated path
     * @param matrix Allocated by the caller, this is set to the transformation
     *        associated with the position and tangent at the specified distance
     * @param flags Specified what aspects should be returned in the matrix.
     */
    public boolean getMatrix(float distance, Matrix matrix, int flags) {
        return native_getMatrix(native_instance, distance, matrix.native_instance, flags);
    }
    
    public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
        // Skia used to enforce this as part of it's API, but has since relaxed that restriction
        // so to maintain consistency in our API we enforce the preconditions here.
        float length = getLength();
        if (startD < 0) {
            startD = 0;
        }
        if (stopD > length) {
            stopD = length;
        }
        if (startD >= stopD) {
            return false;
        }

        return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo);
    }

    /**
     *  是否闭合
     */
    public boolean isClosed() {
        return native_isClosed(native_instance);
    }

    /**
     * Move to the next contour in the path. Return true if one exists, or
     * false if we're done with the path.
     */
    public boolean nextContour() {
        return native_nextContour(native_instance);
    }

    protected void finalize() throws Throwable {
        native_destroy(native_instance);
        native_instance = 0;  // Other finalizers can still call us.
    }

    private static native long native_create(long native_path, boolean forceClosed);
    private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);
    private static native float native_getLength(long native_instance);
    private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);
    private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);
    private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);
    private static native boolean native_isClosed(long native_instance);
    private static native boolean native_nextContour(long native_instance);
    private static native void native_destroy(long native_instance);

    /* package */private long native_instance;

未完待续…

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/100031228