Android自定义控件开发入门与实战(6)路径动画(1)

如下图所示:

在这里插入图片描述

如果这个时候我们把 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()

这个函数用来得到路径上某一长度的位置以及该位置的正切值的矩阵。

boolean getMatrix(float distance,Martix matrix,int flags)

  • distance:距离Path起点的长度

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

  • flags:用于指定哪些内容会存入matrix中,flags的值有两个:PathMeasure.POSITION_MATRIX_FLAG:获取位置信息;pathMeasure.TANGENT_MATRIX_FLAG:获取切边信息,使得图片按Path旋转。

可以指定一个,也可以使用“|”同时指定。

可以看的出来,getMatrix是PathMeasure.getPosTan()的另一个实现而已,getPosTan将获取到的位置信息和切边信息保存在pos和tan数组中,而getMartix则直接把这些信息保存到matrix中。

示例:

这里做一个支付宝支付成功的动画。 就是外面先画一个圆,再在圆圈内打一个勾。

因为圆圈和勾是分开的,所以会用到nextContour()函数。

思路是:

  1. 先用path预先按顺序画好外面的圆和里面的勾

  2. 设置动画,大体上分成两个部分,第一个部分画圆,第二个画勾,中间使用nextContour过渡

  3. 通过动画的进度,使用PathMeasure的getSegment来绘制

勾的坐标要确定三个,其中mCenterX,mCenterY为圆心坐标,mRadius为半径

第一个是勾的左边顶点,坐标为(mCenterX-mRadius/2,mCenterY)

第二个为勾的下顶点,坐标为(mCenterX,mCenterY+mRadius/2)

第三个为勾的右顶点,坐标为(mCenterX+mRadius/2,mCenterY-mRadius/3)

代码如下:

public AliPayView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setLayerType(LAYER_TYPE_SOFTWARE, null);

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.STROKE);

mPaint.setStrokeWidth(3);

mPaint.setColor(0xff4b66ed);

mDst = new Path();

mCircle = new Path();

mCenterX = 500;

mCenterY = 1100;

mRadius = 300;

mCircle.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);

mCircle.moveTo(mCenterX - mRadius / 2, mCenterY);

mCircle.lineTo(mCenterX, mCenterY + mRadius / 2);

mCircle.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3);

mPathMeasure = new PathMeasure(mCircle, false);

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 2);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

mProgress = (float) valueAnimator.getAnimatedValue();

invalidate();

}

});

valueAnimator.setDuration(4000);

valueAnimator.setInterpolator(new AccelerateInterpolator());

valueAnimator.start();

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

【附】相关架构及资料

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

5%以上Android开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-0mRqRouX-1711921479904)]

【附】相关架构及资料

[外链图片转存中…(img-A5MFniBC-1711921479905)]

[外链图片转存中…(img-MUOeWisK-1711921479905)]

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值