话说,在前面两篇文章中,我们学习了BitmapShader、Path的基本使用,那么这一篇文章,咱们接着来学习一下PathMeasure的用法。什么,你没听说过PathMeasure?那你就要OUT咯~
项目效果图
废话不多说,在开始讲解之前,先看下最终实现的效果。
效果一:
仿支付宝支付成功效果
效果二:
这两个项目都是使用Path和PathMeature配合完成的,由其他项目改造而来
项目一是七叔写的,我对代码进行了大量改造。
项目二是不小心搜到的,然后进行了改造,原文请戳这里
本文代码请到这里下载
PathMeasure介绍
PathMeasure这个类确实是不太常见的,关于这个类的介绍也是甚少,那么这个类是用来干嘛的呢?主要其实是配合Path,来计算Path里面点的坐标的,或者是给一个范围,来截取Path其中的一部分的。
这么说,你肯定也迷糊,咱们先简单看一下有哪些方法,然后根据案例来进行讲解更好一些。
构造方法有两个,很好理解,不多解释。
PathMeasure()
PathMeasure(Path path, boolean forceClosed)
重点看下常用方法:
- float getLength() 返回当前contour(解释为轮廓不太恰当,我觉得更像是笔画)的长度,也就是这一个Path有多长
- boolean getPosTan(float distance, float[] pos, float[] tan) 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,注意,pos会自动填充上坐标,这个方法很重要
- boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 传入一个开始和结束距离,然后会返回介于这之间的Path,在这里就是dst,他会被填充上内容,这个方法很重要
- boolean nextContour() 移动到下一个笔画,如果你的Path是由多个笔画组成的话,那么就可以使用这个方法
- void setPath(Path path, boolean forceClosed)这个方法也比较重要,用来设置新的Path对象的,算是对第一个构造函数的一个补充
仿支付宝实现原理解析
下面,我将介绍一下如何实现下面的这个效果
首先分析需求:
- 需要有三种状态:加载中,成功,失败
- 加载中时,需要不断更换颜色
- 加载中状态时,圆弧要不断的变换长度和位置
- 成功状态和失败状态,需要把√和×一笔一划的画出来
OK,基本就是这些需求,那么对应着需求,咱们看一下解决方案
- 有三种状态好说,用静态常量或者是枚举类型进行区分
- 不断变换颜色也好说,只要改变Paint的颜色就可以啦
- 不断的变化长度和位置,从效果图上可以看出来,我们需要画一段圆弧,那就要用下面的drawArc(),需要知道范围,起始角度和绘制角度,由于需要不断的变化长度,因此就需要用Animator,具体实现一会详谈
Canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint)
- 需要画出来形状,其实就是一些线段,那么就需要用Path了,但是如何能一笔一划的效果呢?那就要靠PathMeasure啦
下面开始讲解代码实现,最好参照着源代码看下面的文章。
首先看怎么用ConfirmView呢?很简单,只需要调用animatedWithState()然后传入一个枚举类型即可
confirmView.animatedWithState(ConfirmView.State.Progressing);
这个枚举类型在类的内部,代表三种状态
public enum State {
Success, Fail, Progressing
}
再看构造函数,很简单,只是进行了变量的初始化,这些变量的具体作用,我将在下面用到的时候重点介绍
public ConfirmView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mSuccessPath = new Path();
mPathMeasure = new PathMeasure(mSuccessPath, false);
mRenderPaths = new ArrayList<>();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0099CC);
mPaint.setStrokeWidth(STROKEN_WIDTH);
mPaint.setStrokeCap(Paint.Cap.ROUND);
oval = new RectF();
}
那么调用了animatedWithState()之后,进行了什么操作呢?
public void animatedWithState(State state) {
if (mCurrentState != state) {
mCurrentState = state;
if (mPhareAnimator != null && mPhareAnimator.isRunning()) {
stopPhareAnimation();
}
switch (state) {
case Fail:
case Success:
updatePath();
if (mCircleAnimator != null && mCircleAnimator.isRunning()) {
mCircleAngle = (Float) mCircleAnimator.getAnimatedValue();
mCircleAnimator.end();
}
if ((mStartAngleAnimator == null || !mStartAngleAnimator.isRunning() || !mStartAngleAnimator.isStarted()) &&
(mEndAngleAnimator == null || !mEndAngleAnimator.isRunning() || !mEndAngleAnimator.isStarted())) {
mStartAngle = 360;
mEndAngle = 0;
startPhareAnimation();
}
break;
case Progressing:
mCircleAngle = 0;
startCircleAnimation();
break;
}
}
}
结合着上面的代码,我简单解释一下。
首先进行重复性的判断,如果当前所处的状态与要改变的状态相同则不进行操作。
接下来,对动画状态进行了判断,mPhareAnimator是用来实现√和×的动画绘制效果的,如果正在运行,则停掉。
再往下的一个switch则是开始真正的操作了,updatePath()是更新Path,一会重点看下,mCircleAnimator这个则是实现外部弧形的偏移量的控制的,现在看不明白也没事,重点看下下面的代码,当mStartAngleAnimator和mEndAngleAnimator都不在运行状态的时候(这两个Animator是为了控制外部弧形的起点和终点的),会进入下面的代码,
mStartAngle = 360;
mEndAngle = 0;
startPhareAnimation();
mStartAngle和mEndAngle分别代表起点转过的角度和终点转过的角度,然后就startPhareAnimation(),这个时候,真正的绘制√和×的动画才开始执行。
如果是Progressing呢,则执行下面的代码,重置mCircleAngle,startCircleAnimation()这个方法是绘制外部的弧