Android-高级-UI-进阶之路--(六)-PathMeasure-制作路径动画

//需要按下事件
MotionEvent.ACTION_DOWN -> return true
MotionEvent.ACTION_UP -> {
val rectF = RectF()
//计算 Path 边界
mPath.computeBounds(rectF, true)
//将边界放入矩形区域内
region.setPath(
mPath,
Region(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt())
)
if (region.contains(event.x.toInt(), event.y.toInt())) {
Toast.makeText(context, “点击了圆”, Toast.LENGTH_SHORT).show()
}
//用新的路径替换到当前路径所有内容
region.set(300, 800, 800, 1300)
if (region.contains(event.x.toInt(), event.y.toInt())) {
Toast.makeText(context, “点击了矩形”, Toast.LENGTH_SHORT).show()
}
}
}
return super.onTouchEvent(event)
}

  1. 效果

Path 我们就先学习到这里,该篇文章后面会有 Path 实战。

PathMeasure

上部分我们讲解了 Path 路径的知识,现在来看 Path 除了绘制图形好像也没什么作用,当然如果只是单纯显示 Path 绘制的图形,那我也就不介绍该篇的重点了。Android SDK 提供了一个非常有用的 API 来帮组开发者实现一个 Path 路径追踪,这个 API 就是 PathMeasure , 通过它可以实现复杂切绚丽的效果。

概念

PathMeasure 类似一个计算器,可以计算出指定路径的一些信息,比如路径总长、指定长度所对应的坐标点等。

API 使用

构造方法

//1.空参
public PathMeasure()
//2.path 代表一个已经完成的 Path,forceClosed 代表是否最后闭合
public PathMeasure(Path path, boolean forceClosed)

简单函数使用

  1. getLength() 函数

PathMeasure#getLength() 函数的使用非常广泛,其作用就是获取计算的路径长度,下面以一个例子来看下它的用法。

效果:

代码:

override fun draw(canvas: Canvas) {
super.draw(canvas)
/**

  • 1. getLength
    */
    //将起点移动到 100,100 的位置
    mPath.moveTo(100f,100f)
    //绘制连接线
    mPath.lineTo(100f,450f)
    mPath.lineTo(450f,500f)
    mPath.lineTo(500f,100f)
    mPathMeasure.setPath(mPath,false)//不被闭合
    mPathMeasure2.setPath(mPath,true)//闭合
    println(“forceClosed false pathLength = m P a t h M e a s u r e . l e n g t h " ) p r i n t l n ( " f o r c e C l o s e d t r u e p a t h L e n g t h = {mPathMeasure.length}") println("forceClosed true pathLength = mPathMeasure.length")println("forceClosedtruepathLength={mPathMeasure2.length}”)
    canvas.drawPath(mPath,mPathPaint)
    }

输出:

System.out: forceClosed false pathLength =1106.6663
System.out: forceClosed true pathLength =1506.6663

可以看见,如果 forceClosed 设置为 true/false 测量的是各自的 path 。

  1. isClosed() 函数

该函数用于判断测量 Path 时是否计算闭合。所以,如果在关联 Path 的时候设置 forceClosed 为 true ,那么这个函数的返回值也一定为 true.

  1. nextContour() 函数

我们知道,Path 可以由多条曲线构成,但无论是 getLength()、getSegment() 还是其它函数,都只会对针对其中第一条线段进行计算。而 nextContour 就是用于跳转到下一条曲线的函数,如果跳转成功,则返回 true ; 如果跳转失败,则返回 false.下面看一个示例,分别创建 3 条闭合 Path,然后利用 PathMeasure 来依次测量。

效果:

代码:

/**

  • 2. nextContour
    */
    mPath.addCircle(500f,500f,10f,Path.Direction.CW)
    mPath.addCircle(500f,500f,80f,Path.Direction.CW)
    mPath.addCircle(500f,500f,150f,Path.Direction.CW)
    mPath.addCircle(500f,500f,200f,Path.Direction.CW)

mPathMeasure.setPath(mPath,false)//不被闭合

canvas.drawPath(mPath,mPathPaint)

do {
println(“forceClosed pathLength =${mPathMeasure.length}”)
}while (mPathMeasure.nextContour())

输出:

2019-12-03 22:37:22.340 18501-18501/? I/System.out: forceClosed pathLength =62.42697
2019-12-03 22:37:22.341 18501-18501/? I/System.out: forceClosed pathLength =501.84265
2019-12-03 22:37:22.341 18501-18501/? I/System.out: forceClosed pathLength =942.0967
2019-12-03 22:37:22.341 18501-18501/? I/System.out: forceClosed pathLength =1256.1292

在这里,我们通过 do…while 循环和 measure.nextContour() 函数相结合,依次拿到 Path 中所有的曲线

通过这个例子我们可以知道,通过 PathMeasure#nextContour 函数得到的曲线顺序与 Path 添加的顺序相同

getSegment() 函数

//startD:开始截取位置距离 Path 起始点的长度
//stopD: 结束截取位置距离 Path 起始点的长度
//dst: 截取的 Path 将会被添加到 dst 中,注意是添加,而不是替换
//startWithMoveTo: 起始点是否使用 moveTo
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

getSegment 用于截取整个 Path 中的某个片段,通过参数 startD 和 stopD 来控制截取的长度,并将截取后的 Path 保存到参数 dst 中。最后一个参数 startWithMoveTo 表示起始点是否使用 moveTo 将路径的新起始点移到结果 Path 的起始点,通常设置为 true ,以保证每次截取的 Path 都是正常完整的,通常和 dst 一起使用,因为 dst 中保存的 Path 是被不断添加的,而不是每次被覆盖的;如果设置为 false ,则新增的片段会从上一次 Path 终点开始计算,这样可以保证截取的 Path 片段是连续的。

注意:

  • 如果 startD ,stopD 数值不在取值范围内,或者 startD == stopD ,那么就会返回 false ,并且 dst 不会有 Path 数据。
  • 开启硬件加速后,绘图会出现问题,因此,在使用 getSegment 是需要 在构造函数中调用 setLayerType(LAYER_TYPE_SOFTWARE,null) 函数来禁用硬件加速

getSegment 举例:

/**

  • 3. getSegment
    */
    mPath.addCircle(500f,500f,200f,Path.Direction.CCW)
    mPathMeasure.setPath(mPath,false)//不被闭合
    val segment = mPathMeasure.getSegment(50f, 500f, mTempPath, true)
    println(“是否截取成功:$segment”)
    canvas.drawPath(mTempPath,mPathPaint)

效果:

注意:

如果 startWithMoveTo 为 true,则被截取出来的 path 片段保持原状;如果 startWithMoveTo 为 false ,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 路径的连续性。

实现一个实时截取的动画:

代码实现:

  1. 定义一个值动画

val valueAnimator = ValueAnimator.ofFloat(0f, 1f)
valueAnimator.addUpdateListener {
animation -> stopValues = animation.animatedValue as Float
invalidate()
}
valueAnimator.repeatCount = ValueAnimator.INFINITE
valueAnimator.setDuration(1500)
valueAnimator.start()

  1. 实时截取绘制

mPath.addCircle(500f,500f,200f,Path.Direction.CCW)
mPathMeasure.setPath(mPath,false)//不被闭合
mTempPath.rewind()
stop = mPathMeasure.length * stopValues
val start = (stop - (0.5 - Math.abs(stopValues - 0.5)) * mPathMeasure.length).toFloat()
val segment = mPathMeasure.getSegment(start, stop, mTempPath, true)
println(“总长度: m P a t h M e a s u r e . l e n g t h 是否截取成功 : {mPathMeasure.length} 是否截取成功: mPathMeasure.length是否截取成功:segment + start: s t a r t s t o p : start stop: startstop:stop”)
canvas.drawPath(mTempPath,mPathPaint)

getPosTan

这个方法是用于得到路径上某一长度的位置以及该位置的正切值

//distance:距离 Path 起点的长度,取值范围: 0 <= distance <= getLength
//pos:该点的坐标值 , 当前点在画布上的位置,有两个数值,分别为x,y坐标。
//tan:该点的正切值, 当前点在曲线上的方向,使用 Math.atan2(tan[1], tan[0]) 获取到正切角的弧度值。
boolean getPosTan (float distance, float[] pos, float[] tan)

下面以一个 demo 来讲解 getPosTan 具体使用,先来看一个效果图:

感觉是不是很炫,那么我们是怎么实现的呢?先来看一下核心代码,如下:

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//清楚 path 数据
mTempPath.rewind()
//绘制一个模拟公路
addLineToPath()
//测量 path,闭合
mPathMeasure!!.setPath(mTempPath, true)
//动态变化的值
mCurValues += 0.002f
if (mCurValues >= 1) mCurValues = 0f
//拿到当前点上的 正弦值坐标
mPathMeasure!!.getPosTan(mPathMeasure!!.length * mCurValues, pos, tan)
//通过正弦值拿到当前角度
val y = tan!![1].toDouble()
val x = tan!![0].toDouble()
var degrees = (Math.atan2(y, x) * 180f / Math.PI).toFloat()
println(“角度:$degrees”)
mMatrix!!.reset()
//拿到 bitmap 需要旋转的角度,之后将矩阵旋转
mMatrix!!.postRotate(degrees, mBitmap!!.width / 2.toFloat(), mBitmap!!.height / 2.toFloat())
//拿到 path 上的 pos 点随着点移动
mMatrix!!.postTranslate(pos!![0] - mBitmap!!.getWidth() / 2, pos!![1] - mBitmap!!.getHeight() / 2)
//绘制Bitmap和path
canvas!!.drawPath(mTempPath, mTempPaint)
canvas!!.drawBitmap(mBitmap!!, mMatrix!!, mTempPaint)

//重绘
postInvalidate()
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
Android学习PDF+学习视频+面试文档+知识点笔记

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

需要的朋友,可以点赞关注+转发”前往免费领取!

自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
Android学习PDF+学习视频+面试文档+知识点笔记

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-fIHlKStz-1710773333204)]

需要的朋友,可以点赞关注+转发”前往免费领取!

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值