PathMeasure

2020-06-28  本文已影响0人  echoSuny

PathMeasure是一个路径计算类,可以计算路径总长,指定长度所对应的坐标点等。可以用来实现一些比较复杂的动画效果。

初始化

// 第一种
        val path = Path()
        val pathMeasure = PathMeasure()
        pathMeasure.setPath(path, true)
// 第二种
        val path = Path()
        val pathMeasure = PathMeasure(path, true)

在setPath()和构造方法的第二个参数中都传入了一个布尔变量-forceClosed。这个变量表示Path是否需要闭合。如果为true,则不管传入的Path是否是闭合的,都会被闭合。但是forceClosed参数对传入的Path不会产生任何影响。例如一个没有闭合的Path,当forceClosed为true时,只是PathMeasure在计算的时候是以闭合的方式计算的,而Path本身没有变化,也就是说只对测量的结果有影响。

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val paint = Paint()
        paint.strokeWidth = 12f
        paint.color = Color.RED
        paint.style = Paint.Style.STROKE

        canvas.translate(100f, 100f)
        val path = Path()
        path.moveTo(0f, 0f)
        path.lineTo(0f, 100f)
        path.lineTo(100f, 100f)
        path.lineTo(100f, 0f)
        val pathMeasure1 = PathMeasure(path, false)
        val pathMeasure2 = PathMeasure(path, true)
        Log.d("----->", "pathMeasure1: ${pathMeasure1.length}")
        Log.d("----->", "pathMeasure2: ${pathMeasure2.length}")
        canvas.drawPath(path, paint)
    }

可以看到绘制了一个没有闭口的正方形,每条边长都是100



当forceClosed为false时,计算得到这个path的长度是300,而为true的时候则是400,多了一条边的长度。但是创建出来的path依旧是个没有闭口的路径。

常用函数

        val paint = Paint()
        paint.strokeWidth = 12f
        paint.color = Color.RED
        paint.style = Paint.Style.STROKE

        canvas.translate(100f, 100f)
        val path = Path()
        path.moveTo(0f, 0f)
        path.lineTo(0f, 100f)
        path.lineTo(100f, 100f)
        path.lineTo(100f, 0f)
        path.close()
        val dst = Path()
        val pathMeasure = PathMeasure(path, false)
        pathMeasure.getSegment(0f, 160f, dst, true)
        canvas.drawPath(path, paint)
        canvas.translate(200f, 0f)
        canvas.drawPath(dst, paint)

可以看到右边的路径是左边的一部分。需要注意的是因为我们在添加路径的时候是按照下图的顺序添加的,那么截取的时候也是这样。也就是说添加的时候是什么顺序,截取的时候就是什么顺序。



前面说了最后一个参数是true还是false对最终的结果是会产生影响的。下面把最后一个参数改为false之后看一下效果:




非常奇怪的是没有任何区别。这是因为保存路径片段的dst为空(不是说dst没有初始化,而是它里面没有任何的路径)。下面给dst添加一条路径,再看一下效果:
        val dst = Path()
        dst.moveTo(200f,200f)
        dst.lineTo(200f,100f)
        val pathMeasure = PathMeasure(path, false)
        pathMeasure.getSegment(0f, 160f, dst, true)
        canvas.drawPath(path, paint)
        canvas.translate(200f, 0f)
        canvas.drawPath(dst, paint)

当把getSegment()中最后一个参数改为false之后:



其实只是把两条曲线连接起来了:



也就是把截取的路径片段的起点与dst中的原来路径的终点连接起来了。也就是原来路径的终点作为新的路径的起点了。
下面通过一个例子再熟悉一下PathMeasure的基本使用:
class EasyPathView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    val paint: Paint
    val dst: Path
    val circlePath: Path
    val pathMeasure: PathMeasure
    var currentValue by Delegates.notNull<Float>()

    init {

        setLayerType(LAYER_TYPE_SOFTWARE,null)
        paint = Paint()
        paint.color = Color.RED
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 6f

        dst = Path()

        circlePath = Path()
        circlePath.addCircle(300f, 300f, 160f, Path.Direction.CW)

        pathMeasure = PathMeasure(circlePath, true)

        val animator = ValueAnimator.ofFloat(0f, 1f)
        animator.repeatCount = ValueAnimator.INFINITE
        animator.duration = 2000
        animator.addUpdateListener {
            currentValue = it.animatedValue as Float
            invalidate()
        }
        animator.start()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val stop = pathMeasure.length * currentValue
        dst.reset()
        pathMeasure.getSegment(0f, stop, dst, true)
        canvas.drawPath(dst, paint)
    }
}

稍微修改一下,使其变成一个简单的加载动画:

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val stop = pathMeasure.length * currentValue
        var start = 0f
        if (currentValue >= .5f) {
            start = (2 * currentValue - 1f) * pathMeasure.length
        }
        dst.reset()
        pathMeasure.getSegment(start, stop, dst, true)
        canvas.drawPath(dst, paint)
    }

PathMeasure进阶

getPosTan()

PathMeasure除了上面的基本用法之外还包括getPosTan()函数。这个方法的作用是得到路径某一长度的位置以及该位置的正切值。
boolean getPosTan(float distance,float[] pos , float[) tan)

class EasyPathView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    val paint: Paint
    val dst: Path
    val circlePath: Path
    val pathMeasure: PathMeasure
    var currentValue by Delegates.notNull<Float>()
    val arrow: Bitmap
    // 创建两个数组用来接收对应点的x、y坐标和正切值
    val pos: FloatArray = floatArrayOf(0f, 0f)
    val tan: FloatArray = floatArrayOf(0f, 0f)

    init {
        // 加载箭头图标
        arrow = BitmapFactory.decodeResource(resources, R.drawable.heart)
        setLayerType(LAYER_TYPE_SOFTWARE, null)
        paint = Paint()
        paint.color = Color.RED
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 6f

        dst = Path()

        circlePath = Path()
        circlePath.addCircle(300f, 300f, 160f, Path.Direction.CW)

        pathMeasure = PathMeasure(circlePath, true)

        val animator = ValueAnimator.ofFloat(0f, 1f)
        animator.repeatCount = ValueAnimator.INFINITE
        animator.duration = 2000
        animator.addUpdateListener {
            currentValue = it.animatedValue as Float
            invalidate()
        }
        animator.start()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val stop = pathMeasure.length * currentValue
        dst.reset()
        pathMeasure.getSegment(0f, stop, dst, true)
        canvas.drawPath(dst, paint)
        pathMeasure.getPosTan(stop, pos, tan)
        // 得到箭头所在位置的正切值所对应的弧度
        val atan = Math.atan2(tan[1].toDouble(), tan[0].toDouble())
        // 将弧度值转换为角度值
        val degree = atan * 180.0f / Math.PI
        val matrix = Matrix()
        // 将箭头图标按照正切角度旋转
        matrix.postRotate(degree.toFloat(), arrow.width / 2.toFloat(), arrow.height / 2.toFloat())
        // 将箭头图标平移到相应的位置
        matrix.postTranslate(pos[0] - arrow.width / 2, pos[1] - arrow.height / 2)
        canvas.drawBitmap(arrow, matrix, paint)
    }
}
getMatrix()

boolean getMatrix(float distance,Matrix matrix , int flags)
这个函数用于得到路径上某一长度的位置以及该位置的正切值的矩阵。

上一篇 下一篇

猜你喜欢

热点阅读