Android動(dòng)畫之小球擬合動(dòng)畫實(shí)例
Android動(dòng)畫之小球擬合動(dòng)畫實(shí)例
實(shí)現(xiàn)效果:

動(dòng)畫組成:
1.通過(guò)三階貝塞爾曲線來(lái)擬合圓,擬合系數(shù)的由來(lái),以及怎么選控制點(diǎn).
2.利用畫布canvas.translate,以及scale,rotate的方法,來(lái)漸變繪制的過(guò)程.
3.熟悉擬合過(guò)程.
4.不熟悉的話,先繪制輔助點(diǎn)的移動(dòng)路線,對(duì)理解兩個(gè)圓的分裂的擬合過(guò)程有好處.
package com.example.administrator.animationworkdemo.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import java.util.concurrent.CyclicBarrier;
/**
* 這個(gè)例子中,大家可以發(fā)現(xiàn)作者的擬合做的并不是很好,連接的地方比較生硬,大家可以思考下如何改善
* 貝塞爾曲線繪制比較復(fù)雜,大家在學(xué)習(xí)過(guò)程中,可以仿照示例中的,將輔助點(diǎn)和線繪制出來(lái),這樣會(huì)看的更清楚一點(diǎn)
*/
public class BallShapeChangeView extends View {
// 使用貝塞爾曲線來(lái)擬合圓的magic number
//C 是三階貝塞爾曲線擬合 圓的 誤差最小 獲得控制點(diǎn)的參數(shù).
private static final float C = 0.551915024494f;
private Paint mPaint;
private int mRadiusBig = 120, mRadiusSmall = (int) (mRadiusBig / 2f), mWidth, mHeight, mMimWidth = (int) (mRadiusSmall * 2 * 3)/*fill view mim width*/;
private float mFraction = 0, mFractionDegree = 0, /*degree*/
mLength, mDistanceBezier;
private Path mPathCircle, mPathBezier;
private ValueAnimator mValueAnimator;
private float[] mPointData = new float[8];// 4個(gè)數(shù)據(jù)點(diǎn) 順時(shí)針排序,從左邊開始
private float[] mPointCtrl = new float[16];// 8個(gè)控制點(diǎn)
private float[] mPos = new float[2];
private PathMeasure mPathMeasure;
private Path mPathBezier2;
public BallShapeChangeView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(0xFF7C191E);
mPaint.setAntiAlias(true);
mPathCircle = new Path();
mPathBezier = new Path();
mPathBezier2 = new Path();
mPathMeasure = new PathMeasure();
mValueAnimator = ValueAnimator.ofFloat(0, 1, 0);
mValueAnimator.setDuration(3000);
mValueAnimator.setRepeatCount(Integer.MAX_VALUE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFraction = (float) animation.getAnimatedValue();
mFractionDegree = animation.getAnimatedFraction();
invalidate();
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 為了能夠更好的控制繪制的大小和位置,當(dāng)然,初學(xué)者寫死也是可以的
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode != MeasureSpec.AT_MOST && heightMode != MeasureSpec.AT_MOST) {
if (mWidth < mMimWidth)
mWidth = mMimWidth;
if (mHeight < mMimWidth)
mHeight = mMimWidth;
} else if (widthMeasureSpec != MeasureSpec.AT_MOST) {
if (mWidth < mMimWidth)
mWidth = mMimWidth;
} else if (heightMeasureSpec != MeasureSpec.AT_MOST) {
if (mHeight < mMimWidth)
mHeight = mMimWidth;
}
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 通過(guò)mFraction來(lái)控制繪圖的過(guò)程,這是常用的一種方式
canvas.translate(mWidth / 2, mHeight / 2);
canvas.scale(1, -1);
canvas.rotate(-360 * mFractionDegree);
setDoubleCirClePath();
canvas.drawPath(mPathCircle, mPaint);
if (mFraction < (1 / 3f)) {// 縮小大圓
setCirclePath();
canvas.drawPath(mPathCircle, mPaint);
} else if (mFraction < 3 / 4f) {// 畫貝塞爾曲線
setBezierPath2();
canvas.drawPath(mPathBezier, mPaint);
canvas.drawPath(mPathBezier2, mPaint);
} else {// 畫分離
//setLastBezierPath();
//canvas.drawPath(mPathBezier, mPaint);
}
}
private void setDoubleCirClePath() {
mPathCircle.reset();
if (mFraction < (1 / 3f)) {
mPathCircle.addCircle(-mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
mPathCircle.addCircle(mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
} else {
float distance = (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
mPathCircle.addCircle(-mRadiusSmall / 2f - distance, 0, mRadiusSmall, Path.Direction.CW);
mPathCircle.addCircle(mRadiusSmall / 2f + distance, 0, mRadiusSmall, Path.Direction.CW);
}
}
// mFraction 0 ~ 1/3
private void setCirclePath() {
mPointData[0] = -mRadiusBig + mRadiusSmall / 2f * mFraction * 3f;
mPointData[1] = 0;
mPointData[2] = 0;
mPointData[3] = mRadiusBig - mRadiusBig / 2f * mFraction * 3f;//0到1 的三分之一 用來(lái)給大圓做效果;
mPointData[4] = mRadiusBig - mRadiusSmall / 2f * mFraction * 3f;
mPointData[5] = 0;
mPointData[6] = mPointData[2];
mPointData[7] = -mPointData[3];
mPointCtrl[0] = mPointData[0];// x軸一樣
mPointCtrl[1] = mRadiusBig * C;// y軸向下的
mPointCtrl[2] = mPointData[2] - mRadiusBig * C;
mPointCtrl[3] = mPointData[3];// y軸一樣
mPointCtrl[4] = mPointData[2] + mRadiusBig * C;
mPointCtrl[5] = mPointData[3];
mPointCtrl[6] = mPointData[4];
mPointCtrl[7] = mPointCtrl[1];
mPointCtrl[8] = mPointData[4];
mPointCtrl[9] = -mPointCtrl[1];
mPointCtrl[10] = mPointCtrl[4];
mPointCtrl[11] = mPointData[7];
mPointCtrl[12] = mPointCtrl[2];
mPointCtrl[13] = mPointData[7];
mPointCtrl[14] = mPointData[0];
mPointCtrl[15] = -mPointCtrl[1];
mPathCircle.reset();
mPathCircle.moveTo(mPointData[0], mPointData[1]);
mPathCircle.cubicTo(mPointCtrl[0], mPointCtrl[1], mPointCtrl[2], mPointCtrl[3], mPointData[2], mPointData[3]);
mPathCircle.cubicTo(mPointCtrl[4], mPointCtrl[5], mPointCtrl[6], mPointCtrl[7], mPointData[4], mPointData[5]);
mPathCircle.cubicTo(mPointCtrl[8], mPointCtrl[9], mPointCtrl[10], mPointCtrl[11], mPointData[6], mPointData[7]);
mPathCircle.cubicTo(mPointCtrl[12], mPointCtrl[13], mPointCtrl[14], mPointCtrl[15], mPointData[0], mPointData[1]);
}
// mFraction 1/3 ~ 3/4
private void setBezierPath2() {
mPointData[0] = -mRadiusSmall / 2 - (mFraction - 1 / 3f) * mRadiusBig * 2f;
if (mFraction < 2 / 3f) {
mPointData[1] = -mRadiusSmall;
} else {
mPointData[1] = -mRadiusSmall + (mFraction - 2 / 3f) * 3 * mRadiusSmall;
}
if (mFraction < 3 / 4f) {
mPointData[2] = 0;
} else {
//當(dāng)分裂超過(guò)一定程度讓結(jié)束點(diǎn)的位置變遠(yuǎn)
mPointData[2] = (mFraction - 3 / 4f) * 16 * mPointData[0];
}
//當(dāng)動(dòng)畫執(zhí)行進(jìn)度大于2/3時(shí),此時(shí)該點(diǎn)接近于0
mPointData[3] = -mRadiusBig + mFraction * mRadiusBig * 1.5f < -0.01f * mRadiusBig ? -mRadiusBig + mFraction * mRadiusBig * 1.5f : 0.01f * -mRadiusBig;
mPointData[4] = mPointData[2];
mPointData[5] = -mPointData[3];
mPointData[6] = mPointData[0];
mPointData[7] = -mPointData[1];
mPointCtrl[0] = mPointData[0] + mRadiusSmall;
mPointCtrl[1] = mPointData[3];
mPointCtrl[2] = mPointData[0] + mRadiusSmall;
mPointCtrl[3] = -mPointData[3];
mPathBezier.reset();
mPathBezier.moveTo(mPointData[0], mPointData[1]);
mPathBezier.quadTo(mPointCtrl[0], mPointCtrl[1], mPointData[2], mPointData[3]);
mPathBezier.lineTo(mPointData[4], mPointData[5]);
mPathBezier.quadTo(mPointCtrl[2], mPointCtrl[3], mPointData[6], mPointData[7]);
mPathBezier2.reset();
mPathBezier2.moveTo(-mPointData[0], mPointData[1]);
mPathBezier2.quadTo(-mPointCtrl[0], mPointCtrl[1], -mPointData[2], mPointData[3]);
mPathBezier2.lineTo(-mPointData[4], mPointData[5]);
mPathBezier2.quadTo(-mPointCtrl[2], mPointCtrl[3], -mPointData[6], mPointData[7]);
}
// mFraction 1/3 ~ 3/4
private void setBezierPath() {
mPathBezier.reset();
float distance = (2 * mRadiusSmall + mRadiusSmall / 2f) * mFraction;
//float topY = mRadiusSmall * (1 - 0.6f * mFraction);
float topY = mRadiusSmall - mRadiusSmall * (mFraction - 1 / 3f);
float distanceBezier = topY - distance * C * (0.5f + 0.5f * mFraction);
if (mDistanceBezier != 0 && distanceBezier < (mDistanceBezier)) {
distanceBezier = mDistanceBezier;
}
mPathBezier.moveTo(-distance, topY);
mPathBezier.cubicTo(-distance, distanceBezier, distance, distanceBezier, distance, topY);
if (mDistanceBezier == 0) {
mPathMeasure.setPath(mPathBezier, false);
mLength = mPathMeasure.getLength();
mPathMeasure.getPosTan(mLength / 2, mPos, null);
if (mPos[1] <= 8) {
mDistanceBezier = distanceBezier;
mPathBezier.reset();
mPathBezier.moveTo(-distance, topY);
mPathBezier.cubicTo(-distance, mDistanceBezier, distance, mDistanceBezier, distance, topY);
mPathBezier.lineTo(distance, -topY);
mPathBezier.cubicTo(distance, -mDistanceBezier, -distance, -mDistanceBezier, -distance, -topY);
mPathBezier.close();
return;
}
}
mPathBezier.lineTo(distance, -topY);
mPathBezier.cubicTo(distance, -distanceBezier, -distance, -distanceBezier, -distance, -topY);
mPathBezier.close();
}
// mFraction 3/4 ~ 1
private void setLastBezierPath() {
float x = -mRadiusSmall / 2f - (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
mPathBezier.reset();
mPathBezier.moveTo(x, mRadiusSmall);
mPathBezier.quadTo(x, 0, x + mRadiusSmall + mRadiusSmall * (4 - mFraction * 4), 0);
mPathBezier.quadTo(x, 0, x, -mRadiusSmall);
mPathBezier.lineTo(x, mRadiusSmall);
mPathBezier.moveTo(-x, mRadiusSmall);
mPathBezier.quadTo(-x, 0, -x - mRadiusSmall - mRadiusSmall * (4 - mFraction * 4), 0);
mPathBezier.quadTo(-x, 0, -x, -mRadiusSmall);
mPathBezier.lineTo(-x, mRadiusSmall);
mPathBezier.close();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mValueAnimator.isRunning())
mValueAnimator.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mValueAnimator.isRunning())
mValueAnimator.cancel();
}
}
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Android IntentService詳解及使用實(shí)例
這篇文章主要介紹了Android IntentService詳解及使用實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-03-03
Flutter之可滾動(dòng)組件子項(xiàng)緩存?KeepAlive詳解
這篇文章主要為大家詳細(xì)介紹了Flutter之可滾動(dòng)組件子項(xiàng)緩存?KeepAlive,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
Android自定義ViewGroup實(shí)現(xiàn)側(cè)滑菜單
這篇文章主要為大家詳細(xì)介紹了Android如何通過(guò)自定義ViewGroup實(shí)現(xiàn)側(cè)滑菜單,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-01-01
android中用xml文件實(shí)現(xiàn)帶邊框背景效果的方法
這篇文章主要給大家介紹了在android中xml文件實(shí)現(xiàn)帶邊框背景效果的方法,其實(shí)實(shí)現(xiàn)的功能不是很難,僅作記錄,幫助需要的朋友們做個(gè)參考,需要的朋友們下面來(lái)一起看看吧。2017-06-06
Android使用 Coroutine + Retrofit打造簡(jiǎn)單的HTTP請(qǐng)求庫(kù)
這篇文章主要介紹了Android使用 Coroutine + Retrofit打造簡(jiǎn)單的HTTP請(qǐng)求庫(kù),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
Android Flutter實(shí)現(xiàn)3D動(dòng)畫效果示例詳解
在Flutter中提供了AnimatedWidget組件用于構(gòu)建可復(fù)用的動(dòng)畫組件。本文我們用AnimatedWidget來(lái)實(shí)現(xiàn)組件的3D旋轉(zhuǎn)效果,感興趣的可以了解一下2022-03-03
android中px、sp與dp之間進(jìn)行轉(zhuǎn)換詳解
android中在xml布局中我們可以使用dp和px都可以,但是在代碼中,很多方法只提供了設(shè)置px的方法,這時(shí)候就需要用到dp和px相互切換了,下面這篇文章主要給大家介紹了關(guān)于android中px、sp與dp之間進(jìn)行轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下2022-08-08
Android Chronometer控件實(shí)現(xiàn)計(jì)時(shí)器函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了Android Chronometer控件實(shí)現(xiàn)計(jì)時(shí)器函數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04
Android頁(yè)面中可編輯與不可編輯切換的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于在Android頁(yè)面中可編輯與不可編輯切換的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07

