話說瑞凑,在前面兩篇文章中,我們學(xué)習(xí)了BitmapShader、Path的基本使用会前,那么這一篇文章好乐,咱們接著來學(xué)習(xí)一下PathMeasure的用法。什么瓦宜,你沒聽說過PathMeasure蔚万?那你就要OUT咯~
項(xiàng)目效果圖
廢話不多說,在開始講解之前临庇,先看下最終實(shí)現(xiàn)的效果反璃。
效果一:
仿支付寶支付成功效果
![](http://i1.tietuku.com/741489ade26607ee.gif)
效果二:
![](http://i1.tietuku.com/1d129fc0af46155d.gif)
這兩個(gè)項(xiàng)目都是使用Path和PathMeature配合完成的,由其他項(xiàng)目改造而來
項(xiàng)目一是七叔寫的假夺,我對(duì)代碼進(jìn)行了大量改造淮蜈。
項(xiàng)目二是不小心搜到的,然后進(jìn)行了改造已卷,原文請(qǐng)戳這里
本文代碼請(qǐng)到這里下載
PathMeasure介紹
PathMeasure這個(gè)類確實(shí)是不太常見的梧田,關(guān)于這個(gè)類的介紹也是甚少,那么這個(gè)類是用來干嘛的呢侧蘸?主要其實(shí)是配合Path裁眯,來計(jì)算Path里面點(diǎn)的坐標(biāo)的,或者是給一個(gè)范圍讳癌,來截取Path其中的一部分的穿稳。
這么說,你肯定也迷糊晌坤,咱們先簡(jiǎn)單看一下有哪些方法逢艘,然后根據(jù)案例來進(jìn)行講解更好一些。
構(gòu)造方法有兩個(gè)骤菠,很好理解它改,不多解釋。
PathMeasure()
PathMeasure(Path path, boolean forceClosed)
重點(diǎn)看下常用方法:
- float getLength() 返回當(dāng)前contour(解釋為輪廓不太恰當(dāng)娩怎,我覺得更像是筆畫)的長(zhǎng)度搔课,也就是這一個(gè)Path有多長(zhǎng)
- boolean getPosTan(float distance, float[] pos, float[] tan) 傳入一個(gè)距離distance(0<=distance<=getLength()),然后會(huì)計(jì)算當(dāng)前距離的坐標(biāo)點(diǎn)和切線截亦,注意爬泥,pos會(huì)自動(dòng)填充上坐標(biāo),這個(gè)方法很重要
- boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 傳入一個(gè)開始和結(jié)束距離崩瓤,然后會(huì)返回介于這之間的Path袍啡,在這里就是dst,他會(huì)被填充上內(nèi)容却桶,這個(gè)方法很重要
- boolean nextContour() 移動(dòng)到下一個(gè)筆畫境输,如果你的Path是由多個(gè)筆畫組成的話蔗牡,那么就可以使用這個(gè)方法
- void setPath(Path path, boolean forceClosed)這個(gè)方法也比較重要,用來設(shè)置新的Path對(duì)象的嗅剖,算是對(duì)第一個(gè)構(gòu)造函數(shù)的一個(gè)補(bǔ)充
仿支付寶實(shí)現(xiàn)原理解析
下面辩越,我將介紹一下如何實(shí)現(xiàn)下面的這個(gè)效果
![](http://i1.tietuku.com/741489ade26607ee.gif)
首先分析需求:
- 需要有三種狀態(tài):加載中,成功信粮,失敗
- 加載中時(shí)黔攒,需要不斷更換顏色
- 加載中狀態(tài)時(shí),圓弧要不斷的變換長(zhǎng)度和位置
- 成功狀態(tài)和失敗狀態(tài)强缘,需要把√和×一筆一劃的畫出來
OK督惰,基本就是這些需求,那么對(duì)應(yīng)著需求旅掂,咱們看一下解決方案
- 有三種狀態(tài)好說赏胚,用靜態(tài)常量或者是枚舉類型進(jìn)行區(qū)分
- 不斷變換顏色也好說,只要改變Paint的顏色就可以啦
- 不斷的變化長(zhǎng)度和位置商虐,從效果圖上可以看出來觉阅,我們需要畫一段圓弧,那就要用下面的drawArc()秘车,需要知道范圍留拾,起始角度和繪制角度,由于需要不斷的變化長(zhǎng)度鲫尊,因此就需要用Animator,具體實(shí)現(xiàn)一會(huì)詳談
Canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint)
- 需要畫出來形狀沦偎,其實(shí)就是一些線段疫向,那么就需要用Path了,但是如何能一筆一劃的效果呢豪嚎?那就要靠PathMeasure啦
下面開始講解代碼實(shí)現(xiàn)搔驼,最好參照著源代碼看下面的文章。
首先看怎么用ConfirmView呢侈询?很簡(jiǎn)單舌涨,只需要調(diào)用animatedWithState()然后傳入一個(gè)枚舉類型即可
confirmView.animatedWithState(ConfirmView.State.Progressing);
這個(gè)枚舉類型在類的內(nèi)部,代表三種狀態(tài)
public enum State {
Success, Fail, Progressing
}
再看構(gòu)造函數(shù)扔字,很簡(jiǎn)單囊嘉,只是進(jìn)行了變量的初始化,這些變量的具體作用革为,我將在下面用到的時(shí)候重點(diǎn)介紹
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();
}
那么調(diào)用了animatedWithState()之后扭粱,進(jìn)行了什么操作呢?
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;
}
}
}
結(jié)合著上面的代碼震檩,我簡(jiǎn)單解釋一下琢蛤。
首先進(jìn)行重復(fù)性的判斷蜓堕,如果當(dāng)前所處的狀態(tài)與要改變的狀態(tài)相同則不進(jìn)行操作。
接下來博其,對(duì)動(dòng)畫狀態(tài)進(jìn)行了判斷套才,mPhareAnimator是用來實(shí)現(xiàn)√和×的動(dòng)畫繪制效果的,如果正在運(yùn)行慕淡,則停掉背伴。
再往下的一個(gè)switch則是開始真正的操作了,updatePath()是更新Path儡率,一會(huì)重點(diǎn)看下挂据,mCircleAnimator這個(gè)則是實(shí)現(xiàn)外部弧形的偏移量的控制的,現(xiàn)在看不明白也沒事儿普,重點(diǎn)看下下面的代碼崎逃,當(dāng)mStartAngleAnimator和mEndAngleAnimator都不在運(yùn)行狀態(tài)的時(shí)候(這兩個(gè)Animator是為了控制外部弧形的起點(diǎn)和終點(diǎn)的),會(huì)進(jìn)入下面的代碼眉孩,
mStartAngle = 360;
mEndAngle = 0;
startPhareAnimation();
mStartAngle和mEndAngle分別代表起點(diǎn)轉(zhuǎn)過的角度和終點(diǎn)轉(zhuǎn)過的角度个绍,然后就startPhareAnimation(),這個(gè)時(shí)候浪汪,真正的繪制√和×的動(dòng)畫才開始執(zhí)行巴柿。
如果是Progressing呢,則執(zhí)行下面的代碼死遭,重置mCircleAngle广恢,startCircleAnimation()這個(gè)方法是繪制外部的弧形的動(dòng)畫
mCircleAngle = 0;
startCircleAnimation();
至此,咱們知道了傳入不同狀態(tài)的枚舉類型會(huì)進(jìn)行什么操作呀潭,下面钉迷,開始看真正的操作。
咱先看一個(gè)簡(jiǎn)單的钠署,就是startCircleAnimation()到底做了什么糠聪。
前面說過,這個(gè)方法是為了繪制加載中狀態(tài)時(shí)谐鼎,外部不斷變化的彩色弧形的舰蟆,下面是代碼實(shí)現(xiàn)
public void startCircleAnimation() {
if (mCircleAnimator == null || mStartAngleAnimator == null || mEndAngleAnimator == null) {
initAngleAnimation();
}
mStartAngleAnimator.setDuration(NORMAL_ANGLE_ANIMATION_DURATION);
mEndAngleAnimator.setDuration(NORMAL_ANGLE_ANIMATION_DURATION);
mCircleAnimator.setDuration(NORMAL_CIRCLE_ANIMATION_DURATION);
mStartAngleAnimator.start();
mEndAngleAnimator.start();
mCircleAnimator.start();
}
首先前面的if語句是為空判斷,從而進(jìn)行初始化的操作狸棍,后面則是簡(jiǎn)單的設(shè)置動(dòng)畫的持續(xù)時(shí)間和開啟動(dòng)畫身害。這里一共出現(xiàn)了三個(gè)動(dòng)畫,完成外部弧形的效果控制
- mStartAngleAnimator 控制圓弧起點(diǎn)
- mEndAngleAnimator 控制圓弧終點(diǎn)
- mCircleAnimator 控制圓弧的整體偏移量
這么說隔缀,你可能還是不很明白题造,沒關(guān)系,咱們一點(diǎn)點(diǎn)的看代碼猾瘸,首先界赔,咱們看在初始化的時(shí)候丢习,到底做了什么操作,也就是initAngleAnimation()淮悼。
private void initAngleAnimation() {
mStartAngleAnimator = ValueAnimator.ofFloat(0.0F, 1.0F);
mEndAngleAnimator = ValueAnimator.ofFloat(0.0F, 1.0F);
mCircleAnimator = ValueAnimator.ofFloat(0.0F, 1.0F);
mStartAngleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
setStartAngle(value);
}
});
mEndAngleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
setEndAngle(value);
}
});
mStartAngleAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (mCurrentState == State.Progressing) {
if (mEndAngleAnimator != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mEndAngleAnimator.start();
}
}, 400L);
}
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCurrentState != State.Progressing && mEndAngleAnimator != null && !mEndAngleAnimator.isRunning() && !mEndAngleAnimator.isStarted()) {
startPhareAnimation();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mEndAngleAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (mStartAngleAnimator != null) {
if (mCurrentState != State.Progressing) {
mStartAngleAnimator.setDuration(NORMAL_ANIMATION_DURATION);
}
colorCursor++;
if (colorCursor >= colors.length) colorCursor = 0;
mPaint.setColor(colors[colorCursor]);
mStartAngleAnimator.start();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
setCircleAngle(value);
}
});
mStartAngleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mEndAngleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mCircleAnimator.setInterpolator(new LinearInterpolator());
mCircleAnimator.setRepeatCount(-1);
}
這段代碼雖然長(zhǎng)咐低,但是也沒有太大的難度,無非就是進(jìn)行了初始化操作袜腥,ValueAnimator的范圍是0-1见擦,這個(gè)在后面將用于計(jì)算角度。在值不斷的更新的過程中羹令,分別調(diào)用了下面這三個(gè)方法鲤屡,更新一些值
setStartAngle(value);
setEndAngle(value);
setCircleAngle(value);
在這三個(gè)方法里面,都對(duì)成員變量進(jìn)行了更新福侈,并且酒来!調(diào)用了invalidate()!看到這里是不是激動(dòng)了肪凛,改變一次就重繪一次堰汉,這三個(gè)值肯定和弧形的動(dòng)畫效果有關(guān)啊伟墙!
private void setStartAngle(float startAngle) {
this.mStartAngle = startAngle;
invalidate();
}
private void setEndAngle(float endAngle) {
this.mEndAngle = endAngle;
invalidate();
}
private void setCircleAngle(float circleAngle) {
this.mCircleAngle = circleAngle;
invalidate();
}
咱知道了這個(gè)翘鸭,先不著急去看onDraw(),仔細(xì)看下動(dòng)畫的執(zhí)行順序戳葵。
在mStartAngleAnimator執(zhí)行之后就乓,調(diào)用了下面的方法,這當(dāng)然很簡(jiǎn)單拱烁,就是說档址,mStartAngleAnimator執(zhí)行了400毫秒之后,mEndAngleAnimator才會(huì)執(zhí)行,而且插值器設(shè)置的是AccelerateDecelerateInterpolator,為啥呢站粟?很簡(jiǎn)單呢灶,因?yàn)橹挥羞@樣,才能做出弧形長(zhǎng)度先長(zhǎng)后短的效果呀~
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mEndAngleAnimator.start();
}
}, 400L);
而在mEndAngleAnimator執(zhí)行結(jié)束之后见芹,會(huì)調(diào)用下面的代碼
if (mStartAngleAnimator != null) {
if (mCurrentState != State.Progressing) {
mStartAngleAnimator.setDuration(NORMAL_ANIMATION_DURATION);
}
colorCursor++;
if (colorCursor >= colors.length) colorCursor = 0;
mPaint.setColor(colors[colorCursor]);
mStartAngleAnimator.start();
}
在這個(gè)設(shè)置mStartAngleAnimator的動(dòng)畫時(shí)間剂娄,是為了畫√或者是×的時(shí)候快一些效果更流暢。下面的代碼很簡(jiǎn)單了吧玄呛,改變畫筆顏色阅懦,然后mStartAngleAnimator又開啟啦!這就是為啥一直轉(zhuǎn)啊轉(zhuǎn)的原因徘铝。
但是說到這里耳胎,咱們還沒看onDraw()做了什么呢惯吕!
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (mCurrentState) {
case Fail:
for (int i = 0; i < PATH_SIZE_TWO; i++) {
Path p = mRenderPaths.get(i);
if (p != null) {
canvas.drawPath(p, mPaint);
}
}
drawCircle(canvas);
break;
case Success:
Path p = mRenderPaths.get(0);
if (p != null) {
canvas.drawPath(p, mPaint);
}
drawCircle(canvas);
break;
case Progressing:
drawCircle(canvas);
break;
}
}
咱先看Progressing分支里面的drawCircle(canvas),其他的先不要管
private void drawCircle(Canvas canvas) {
float offsetAngle = mCircleAngle * 360;
float startAngle = mEndAngle * 360;
float sweepAngle = mStartAngle * 360;
if (startAngle == 360)
startAngle = 0;
sweepAngle = sweepAngle - startAngle;
startAngle += offsetAngle;
if (sweepAngle < 0)
sweepAngle = 1;
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
}
是的怕午,上面這段代碼就是繪制不斷變幻的環(huán)的代碼咯
float startAngle = mEndAngle * 360;是計(jì)算終點(diǎn)的位置废登,有人會(huì)感到奇怪,為啥終點(diǎn)的位置叫startAngle坝粝А堡距!因?yàn)榻K點(diǎn)的位置就是開始繪制的位置,所以不要奇怪了兆蕉。
sweepAngle = sweepAngle - startAngle;則是計(jì)算要畫多少角度的弧線羽戒,因?yàn)槠瘘c(diǎn)先跑到前面的,所以減去終點(diǎn)的位置虎韵,就是旋轉(zhuǎn)角度易稠。
startAngle += offsetAngle;那么這句是干嘛的?這個(gè)就是所謂的偏移量劝术,為了要實(shí)現(xiàn)更隨性的從非固定點(diǎn)開始結(jié)束的效果缩多。沒聽懂?我給你去掉你看下效果养晋!
private void drawCircle(Canvas canvas) {
float offsetAngle = mCircleAngle * 360;
float startAngle = mEndAngle * 360;
float sweepAngle = mStartAngle * 360;
if (startAngle == 360)
startAngle = 0;
sweepAngle = sweepAngle - startAngle;
// startAngle += offsetAngle;
if (sweepAngle < 0)
sweepAngle = 1;
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
}
![](http://i3.tietuku.com/b9e461f3d003f5ad.gif)
這下子明白了吧衬吆,去掉漂移量效果就沒有之前那么隨性了~
ok,關(guān)于弧線的問題就說這么多绳泉,下面就要說咱們今天的主角PathMeasure了逊抡。
在前面的代碼中,我們提到零酪,成功和失敗狀態(tài)會(huì)執(zhí)行updatePath()和startPhareAnimation()冒嫡,那么到底做了些什么呢?
private void updatePath() {
int offset = (int) (mSignRadius * 0.15F);
mRenderPaths.clear();
switch (mCurrentState) {
case Success:
mSuccessPath.reset();
mSuccessPath.moveTo(mCenterX - mSignRadius, mCenterY + offset);
mSuccessPath.lineTo(mCenterX - offset, mCenterY + mSignRadius - offset);
mSuccessPath.lineTo(mCenterX + mSignRadius, mCenterY - mSignRadius + offset);
mRenderPaths.add(new Path());
break;
case Fail:
mSuccessPath.reset();
float failRadius = mSignRadius * 0.8F;
mSuccessPath.moveTo(mCenterX - failRadius, mCenterY - failRadius);
mSuccessPath.lineTo(mCenterX + failRadius, mCenterY + failRadius);
mSuccessPath.moveTo(mCenterX + failRadius, mCenterY - failRadius);
mSuccessPath.lineTo(mCenterX - failRadius, mCenterY + failRadius);
for (int i = 0; i < PATH_SIZE_TWO; i++) {
mRenderPaths.add(new Path());
}
break;
default:
mSuccessPath.reset();
}
mPathMeasure.setPath(mSuccessPath, false);
}
在updatePath()我們可以很清楚的看到四苇,在這里初始化了mSuccessPath孝凌,通過moveTo()和lineTo()?首先勾勒除了√和×的形狀,至于這個(gè)坐標(biāo)是怎么確定的月腋,這個(gè)可以自己想法來蟀架,我就不介紹了。還要需要注意的是榆骚,Success中最后在mRenderPaths中添加了一個(gè)Path對(duì)象片拍,而在Fail則添加了兩個(gè)對(duì)象,這個(gè)其實(shí)是和要繪制的圖形的筆畫數(shù)有關(guān)的妓肢,×是兩筆捌省,所以是兩個(gè),這里添加的Path議會(huì)將用來紀(jì)錄每一筆畫的形狀碉钠。
最后纲缓,咱們的主角終于現(xiàn)身了
mPathMeasure.setPath(mSuccessPath, false);
調(diào)用完這個(gè)方法卷拘,會(huì)馬上調(diào)用下面的方法
public void startPhareAnimation() {
if (mPhareAnimator == null) {
mPhareAnimator = ValueAnimator.ofFloat(0.0F, 1.0F);
mPhareAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
setPhare(value);
}
});
mPhareAnimator.setDuration(NORMAL_ANIMATION_DURATION);
mPhareAnimator.setInterpolator(new LinearInterpolator());
}
mPhare = 0;
mPhareAnimator.start();
}
其實(shí)也很簡(jiǎn)單,初始化了mPhareAnimator色徘,然后開啟動(dòng)畫恭金,不斷調(diào)用setPhare(value),
private void setPhare(float phare) {
mPhare = phare;
updatePhare();
invalidate();
}
在這里updatePhare()褂策,然后重繪界面横腿,那么玄機(jī)應(yīng)該都在updatePhare()了吧!
private void updatePhare() {
if (mSuccessPath != null) {
switch (mCurrentState) {
case Success: {
if (mPathMeasure.getSegment(0, mPhare * mPathMeasure.getLength(), mRenderPaths.get(0), true)) {
mRenderPaths.get(0).rLineTo(0, 0);
}
}
break;
case Fail: {
//i = 0,畫一半斤寂,i=1,畫另一半
float seg = 1.0F / PATH_SIZE_TWO;
for (int i = 0; i < PATH_SIZE_TWO; i++) {
float offset = mPhare - seg * i;
offset = offset < 0 ? 0 : offset;
offset *= PATH_SIZE_TWO;
Log.d("i:" + i + ",seg:" + seg, "offset:" + offset + ", mPhare:" + mPhare + ", size:" + PATH_SIZE_TWO);
boolean success = mPathMeasure.getSegment(0, offset * mPathMeasure.getLength(), mRenderPaths.get(i), true);
if (success) {
mRenderPaths.get(i).rLineTo(0, 0);
}
mPathMeasure.nextContour();
}
mPathMeasure.setPath(mSuccessPath, false);
}
break;
}
}
}
在這里耿焊,一個(gè)很重要的方法調(diào)用了,那就是mPathMeasure.getSegment()
當(dāng)Success的時(shí)候遍搞,會(huì)執(zhí)行下面的代碼罗侯。mPhare就是動(dòng)畫的百分比,從0到1溪猿,那么钩杰,下面的這段代碼就很好理解了,這是為了根據(jù)動(dòng)畫的百分比诊县,獲取畫出√的整個(gè)Path的一部分讲弄,然后把這部分,填充到了mRenderPaths.get(0)里面依痊,這里面存放的就是在上面方法中添加進(jìn)去的一個(gè)Path對(duì)象避除。mPhare不斷的變化,我們就能獲取到畫整個(gè)√形狀所需的所有Path對(duì)象胸嘁,還記得這個(gè)方法之后是什么嗎瓶摆?invalidate()!所以性宏,現(xiàn)在在onDraw()里面肯定用這Path對(duì)象群井,畫出√的一部分,不斷的更新從mPhare毫胜,不斷繪制蝌借,從無到有,而出現(xiàn)了動(dòng)畫效果指蚁。
mRenderPaths.get(0).rLineTo(0, 0);這個(gè)代碼則是為了在4.4以下不能繪制出圖形BUG的解決方法,不要在意自晰。
if (mPathMeasure.getSegment(0, mPhare * mPathMeasure.getLength(), mRenderPaths.get(0), true)) {
mRenderPaths.get(0).rLineTo(0, 0);
}
不信咱們看下onDraw()凝化,是不是!那么現(xiàn)在你應(yīng)該知道×是怎么畫出來的吧酬荞?
case Success:
Path p = mRenderPaths.get(0);
if (p != null) {
canvas.drawPath(p, mPaint);
}
drawCircle(canvas);
break;
來來來搓劫,咱們看下代碼瞧哟!
case Fail: {
//i = 0,畫一半,i=1,畫另一半
float seg = 1.0F / PATH_SIZE_TWO;
for (int i = 0; i < PATH_SIZE_TWO; i++) {
float offset = mPhare - seg * i;
offset = offset < 0 ? 0 : offset;
offset *= PATH_SIZE_TWO;
Log.d("i:" + i + ",seg:" + seg, "offset:" + offset + ", mPhare:" + mPhare + ", size:" + PATH_SIZE_TWO);
boolean success = mPathMeasure.getSegment(0, offset * mPathMeasure.getLength(), mRenderPaths.get(i), true);
if (success) {
mRenderPaths.get(i).rLineTo(0, 0);
}
mPathMeasure.nextContour();
}
mPathMeasure.setPath(mSuccessPath, false);
}
break;
與繪制√相比枪向,因?yàn)椤潦莾晒P勤揩,所以有些小復(fù)雜,但是也不難秘蛔,offset *= PATH_SIZE_TWO;是為了保證在mPhare從0-0.5過程中控制第一筆畫陨亡,0.5-1則控制第二條筆畫,你仔細(xì)看下代碼深员,這樣可以實(shí)現(xiàn)offset從0-1兩次负蠕。由于×是兩筆畫,所以在i=0取到第一筆畫的Path部分倦畅,存儲(chǔ)在mRenderPaths的第一個(gè)Path之后遮糖,調(diào)用了mPathMeasure.nextContour();切換到下一筆畫,再次完成相同的操作叠赐。
而由于PathMeasure只能往下找Contour欲账,所以最后 mPathMeasure.setPath(mSuccessPath, false);回復(fù)到最初狀態(tài),然后我們看下onDraw()
for (int i = 0; i < PATH_SIZE_TWO; i++) {
Path p = mRenderPaths.get(i);
if (p != null) {
canvas.drawPath(p, mPaint);
}
}
drawCircle(canvas);
其實(shí)和Success差不多的芭概,只不過是兩個(gè)Path赛不,畫出兩筆。
OK谈山,到這里俄删,這個(gè)效果就算是全部實(shí)現(xiàn)了,累死我了
"天女散花"實(shí)現(xiàn)效果解析
其實(shí)這個(gè)我并不打算詳細(xì)講奏路,因?yàn)橐煌ò偻ǔ胍嗾f無益,更多的東西需要你自己研究代碼吸收鸽粉,咱們就重點(diǎn)看下PathMeasure的用法斜脂。
其實(shí)這種效果實(shí)現(xiàn)的真相是這樣滴
![](http://i1.tietuku.com/4019c6322ba6d859.gif)
YES!就是一些Bitmap對(duì)象沿著Path路徑移動(dòng)!
那么和PathMeasure有啥關(guān)系呢触机?
看下onDraw()帚戳!
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFllower(canvas, fllowers1);
drawFllower(canvas, fllowers2);
drawFllower(canvas, fllowers3);
}
OK,再看下drawFllower()
private void drawFllower(Canvas canvas, List<Fllower> fllowers) {
for (Fllower fllower : fllowers) {
float[] pos = new float[2];
canvas.drawPath(fllower.getPath(), mPaint);
pathMeasure.setPath(fllower.getPath(), false);
pathMeasure.getPosTan(height * fllower.getValue(), pos, null);
canvas.drawBitmap(mBitmap, pos[0], pos[1] - top, null);
}
}
首先儡首,遍歷一個(gè)Fllower集合片任,然后把每個(gè)Fllower所屬的Path畫出來,就是上面藍(lán)色的曲線蔬胯,然后很眼熟了吧对供,給PathMeasure設(shè)置Path對(duì)象,然后呢,就是重點(diǎn)啦产场!height是屏幕的高度鹅髓,fllower.getValue()也是一個(gè)百分比,從0-1京景,和前面的Animator作用相同窿冯,這句代碼就是說,我要距離為height * fllower.getValue()處的點(diǎn)的坐標(biāo)确徙,給我放在pos里面醒串!
好了,點(diǎn)的坐標(biāo)都有了米愿,剩下的還需要說么...
不行了厦凤,再不回家,就真回不去了育苟,拜拜较鼓,同學(xué)們
更多參考資料
尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明:From 凱子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵權(quán)必究违柏!