该函数的作用为计算路径长度,使用非常广泛。
我们用路径来画一个没有闭合的正方形:
canvas.translate(50, 50);
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(0, 100);
path.lineTo(100, 100);
path.lineTo(100, 0);
PathMeasure pathMeasure1 = new PathMeasure(path, false);
PathMeasure pathMeasure2 = new PathMeasure(path, true);
Log.e(TAG, "forceClose = false : " + pathMeasure1.getLength());
Log.e(TAG, "forceClose = true : " + pathMeasure2.getLength());
canvas.drawPath(path,paint);
打出的第一个Log长度为300,而第二个则为400.因为第二个已经考虑到闭合了。
2、isClose()
判断测量的path是否闭合。
如果PathMeasurei的forceClosed设置为true时,则isClosed()一定为true
3、nextContour()
Path可以有很多曲线、线段构成,但是getLength()只会取第一条线进行计算。
而nextContour()是跳转到下一条曲线的函数。如果跳转成功则返回true,否则返回false。
注:pathMeasure.getLength()只针对第一条曲线
getSegment
用法
boolean getSegment(float startD,float stopD,Path dst,boolean startWithMoveTo);
顾名思义,这个函数用截取一个Path中的某一个片段。
通过参数startD和stopD来控制截取的长度。并将截取后的Path保存到参数dst中。
最后一个参数startWithMoveTo表示起始点是否使用moveTo将路径的新起始点移到结果Path的起始点,通常设置为true。用来保证每次截取segment都是连续的、完整的。
-
其中startD为开始截取位置距离Path起点的长度,stopD为结束时截取位置距离Path起点的长度。如果startD和stopD的范围不再Path的长度范围内或者 startD==stopD该函数返回false
-
如果在开启硬件加速并使用该方法,绘图会出现问题,所以在使用getSegment时要禁用硬件加速。
这里来截取一个path,代码如下:
canvas.translate(100, 100);
Path path = new Path();
path.addRect(-50,-50,50,50, Path.Direction.CW);
Path dst = new Path();
PathMeasure pathMeasure = new PathMeasure(path, false);
pathMeasure.getSegment(0,150,dst,true);
canvas.drawPath(dst, paint);
截取的path如下
这说明截取是左上角开始截取,并且方向是根据Path的绘制方向截取,上面path绘制是CW(顺时针),所以截取了上半部分。
如果dst本来就已经是一个路径,这个时候再去取别的path的路径,会怎么样呢?
答案是 原来的路径不会被覆盖,反而和新的截取到的路径一起绘制出来。
如下图所示:
如果这个时候我们把 PathMeasure的startWithMoveTo改为false会怎么样呢?下过如下所示:
这里咋一看不是很好理解,其实画个图就ok,因为startWithMoveTo设置为false就是将新的Path的起始点拉到自己原本dst的结束点(因为dst自己画的是不能变的) ,然后目标path其他位置的点不变
就像是使用processon、viso软件画图的时候,用一条线的起点去连另一条线的终点这样。
示例
路径绘制是PathMeasure最常用的功能,下面实现一个转圈圈的加载效果图。
思路是通过ValueAnimator动画算出当前的动画的进度,通过进度获取转圈圆的周长,拿到周长后通过PathMeasure的getLength和getSegment去画圆。
我们再构造函数中做new的操作:
public PathMeasureView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
setLayerType(LAYER_TYPE_SOFTWARE, null);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
dst = new Path();
circlePath = new Path();
circlePath.addCircle(100, 100, 50, Path.Direction.CW);
pathMeasure = new PathMeasure(circlePath, true);
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawProgress = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.setDuration(2000);
animator.start();
}
之后再draw函数中做下面的操作:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
float stop = pathMeasure.getLength() * drawProgress;
dst.reset();
pathMeasure.getSegment(0, stop, dst, true);
canvas.drawPath(dst, paint);
}
最后效果如下所示:
但是这个加载圈的起点一直在画圆的起点,和我们平时看到的加载圆有点不一样,所以我们可以去改变它的起始点,来让圆更加生动:
当动画开始到一半的时候,起点都是最开始的画圆的起点,到后半段,dst圆的起始点开始逐渐向结束点靠拢,最后到达开始位置的时候,两个端点重合
可以得出当
-
进度drawProgress< 0.5时 startD=0
-
进度drawProgress>0.5时 startD=(2*drawProgress-1)*length
-
通过合并公式可以得出 startD = stopD - (0.5 - |drawProgress - 0.5| )*length
float start = (float) (stop - (0.5 - Math.abs(drawProgress - 0.5)) * pathMeasure.getLength());
pathMeasure.getSegment(start, stop, dst, true);
canvas.drawPath(dst, paint);
这就很顶啦。
getPosTan()
getPosTan()函数用于得到路径上某一长度的位置以及该位置的正切值。
boolean getPosTan(float distance ,float[]pos,float[]tan);
-
float distance: 距离Path起始点的长度,取值范围为0≤distance≤getLength
-
float[]pos:该点的坐标值 pos[0]表示x坐标 pos[1]表示y坐标
-
float[]tan:该点的tan值。
所谓的求tan,就是将该点与坐标轴原点连接在一起,与x轴的夹角为α,而tanα就是该点的正切值。
我们通过坐标(x,y)用y/x来获取正切值。
又通过正切值我们就可以通过 Math atan2(double y,double x) 来获取一个α
这个夹角其实用处很大,比如我们在上面加载圈圈的例子中,添加一个箭头,但是如果箭头没有任何知识,它是不会跟着圆圈转的,所以就有必要知道它的夹角,根据夹角来让这个箭头转。
如果想让箭头的旋转角度和切向方向相同,则该点旋转角度要和该点正切角度相同。
下面来实现一下
private Bitmap mArrawBmp;
private float[] pos = new float[2];
private float[] tan = new float[2];
public PathMeasureView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//加载图片
mArrawBmp = BitmapFactory.decodeResource(getResources(), R.drawable.arrow);
…
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
…
//旋转箭头图片,并绘制
pathMeasure.getPosTan(stop, pos, tan);
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180f / Math.PI);
Matrix matrix = new Matrix();
matrix.postRotate(degrees, mArrawBmp.getWidth() / 2, mArrawBmp.getHeight() / 2);
matrix.postTranslate(pos[0] - mArrawBmp.getWidth() / 2, pos[1] - mArrawBmp.getHeight() / 2);
canvas.drawBitmap(mArrawBmp, matrix, paint);
}
就可以实现下面的效果:
getMatrix()
这个函数用来得到路径上某一长度的位置以及该位置的正切值的矩阵。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
以下是今天给大家分享的一些独家干货:
8810)]
最后
对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
以下是今天给大家分享的一些独家干货:
[外链图片转存中…(img-IEJmdPVl-1711921508810)]