前言
前一段事件接觸到了小米手機(jī)的卸載動畫吭产。炫酷效果个从,瞬間爆炸好嗎脉幢?好奇心也瞬間爆炸,憑什么我們的圖標(biāo)就只能移來移去信姓,秉著不服氣的心情鸵隧,我決定仔細(xì)研究研究它是怎么爆炸的。
正文
上面說了很多次圖標(biāo)爆炸意推,那到底是怎么爆炸的呢豆瘫?我們來看效果。
這是github上的一個(gè)開源項(xiàng)目菊值,該項(xiàng)目的連接地址為:https://github.com/tyrantgit/ExplosionField
使用動畫
已經(jīng)有大神為我等菜鳥封裝好了git遠(yuǎn)程引用庫外驱,這樣很方便就可以使用圖標(biāo)爆炸功能。
- 在module的buildGradle添加內(nèi)容
dependencies {
compile 'tyrantgit:explosionfield:1.0.1'
}
- 在程序中調(diào)用
//實(shí)現(xiàn)爆炸動畫
mExplosionField = ExplosionField.attach2Window(this);
mExplosionField.explode(v);
怎么樣腻窒,是不是很昵宇!簡!單儿子!
動畫實(shí)現(xiàn)原理
那么這看起來如此炫酷的動畫到底是如何實(shí)現(xiàn)的呢瓦哎?我們知道調(diào)用mExplosionField.explode(v);
可以實(shí)現(xiàn)爆炸動畫。而explode方法是ExplosionField類為我們提供的一個(gè)方法它本身又繼承于View柔逼。首先export方法有什么內(nèi)容:
public void explode(final View view) {
Rect r = new Rect();
//getLocalVisibleRect(Rect r)方法可以把視圖的長和寬映射到一個(gè)Rect對象上蒋譬。
view.getGlobalVisibleRect(r);
int[] location = new int[2];
//獲取當(dāng)前坐標(biāo)
getLocationOnScreen(location);
//使矩形與view重疊
r.offset(-location[0], -location[1]);
r.inset(-mExpandInset[0], -mExpandInset[1]);
//執(zhí)行view抖動的動畫
int startDelay = 100;
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
Random random = new Random();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);
}
});
animator.start();
//隱藏view動畫
view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();
//執(zhí)行真正的炸裂動畫
explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
}
可以看到,export方法中無非就是將VIew映射到一個(gè)矩形中愉适,然后做抖動動畫犯助。其中explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
才是真正調(diào)用完成爆炸的功能。
public void explode(Bitmap bitmap, Rect bound, long startDelay, long duration) {
final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);
explosion.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mExplosions.remove(animation);
}
});
explosion.setStartDelay(startDelay);
explosion.setDuration(duration);
mExplosions.add(explosion);
explosion.start();
}
在explode(Utils.createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
方法中维咸,重點(diǎn)內(nèi)容就是ExplosionAnimator類的實(shí)例化剂买。這個(gè)類是一個(gè)繼承ValueAnimator的類惠爽,它實(shí)現(xiàn)了爆炸動畫的主要內(nèi)容。
public class ExplosionAnimator extends ValueAnimator {
static long DEFAULT_DURATION = 0x400;
private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f);
private static final float END_VALUE = 1.4f;
private static final float X = Utils.dp2Px(5);
private static final float Y = Utils.dp2Px(20);
private static final float V = Utils.dp2Px(2);
private static final float W = Utils.dp2Px(1);
private Paint mPaint;
private Particle[] mParticles;
private Rect mBound;
private View mContainer;
public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
mPaint = new Paint();
mBound = new Rect(bound);
int partLen = 15;
mParticles = new Particle[partLen * partLen];
Random random = new Random(System.currentTimeMillis());
int w = bitmap.getWidth() / (partLen + 2);
int h = bitmap.getHeight() / (partLen + 2);
for (int i = 0; i < partLen; i++) {
for (int j = 0; j < partLen; j++) {
//GetPixel瞬哼,函數(shù)功能婚肆,該函數(shù)檢索指定坐標(biāo)點(diǎn)的像素的RGB顏色值。
mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random);
}
}
mContainer = container;
setFloatValues(0f, END_VALUE);
setInterpolator(DEFAULT_INTERPOLATOR);
setDuration(DEFAULT_DURATION);
}
private Particle generateParticle(int color, Random random) {
Particle particle = new Particle();
particle.color = color;
particle.radius = V;
if (random.nextFloat() < 0.2f) {
//2 - 5
particle.baseRadius = V + ((X - V) * random.nextFloat());
} else {
//1 - 2
particle.baseRadius = W + ((V - W) * random.nextFloat());
}
float nextFloat = random.nextFloat();
//0.2 - 0.38
particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
//0.2 - 0.236 : 0.4 - 0.58
particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());
//bottom可能是x軸位移偏移總量
//-0.9 - 0.9
particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;
float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;
particle.bottom = f;
particle.mag = 4.0f * particle.top / particle.bottom;
particle.neg = (-particle.mag) / particle.bottom;
f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));
particle.baseCx = f;
particle.cx = f;
f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));
particle.baseCy = f;
particle.cy = f;
particle.life = END_VALUE / 10 * random.nextFloat();
particle.overflow = 0.4f * random.nextFloat();
particle.alpha = 1f;
return particle;
}
public boolean draw(Canvas canvas) {
if (!isStarted()) {
return false;
}
for (Particle particle : mParticles) {
particle.advance((float) getAnimatedValue());
if (particle.alpha > 0f) {
mPaint.setColor(particle.color);
mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
}
}
mContainer.invalidate();
return true;
}
@Override
public void start() {
super.start();
mContainer.invalidate(mBound);
}
private class Particle {
float alpha;
int color;
float cx;
float cy;
float radius;
float baseCx;
float baseCy;
float baseRadius;
float top;
float bottom;
float mag;
float neg;
float life;
float overflow;
//根據(jù)屬性動畫進(jìn)度倒槐,來獲得小圓點(diǎn)當(dāng)前屬性
public void advance(float factor) {
float f = 0f;
float normalization = factor / END_VALUE;
//這里設(shè)定當(dāng)小圓點(diǎn)動畫進(jìn)行到進(jìn)度旬痹,也就是大于或者小于某一個(gè)臨界值時(shí),置為透明讨越。
if (normalization < life || normalization > 1f - overflow) {
alpha = 0f;
return;
}
//計(jì)算出在可顯示區(qū)間的進(jìn)度
normalization = (normalization - life) / (1f - life - overflow);
//計(jì)算此時(shí)應(yīng)有的值
float f2 = normalization * END_VALUE;
//當(dāng)進(jìn)度超過0.7f時(shí)两残,使小圓點(diǎn)開始變透明。
if (normalization >= 0.7f) {
f = (normalization - 0.7f) / 0.3f;
}
alpha = 1f - f;
//小圓點(diǎn)下一個(gè)位移點(diǎn)
f = bottom * f2;
cx = baseCx + f;
cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
radius = V + (baseRadius - V) * f2;
}
}
}
以上為ExplosionAnimator類的所有代碼把跨,也是實(shí)現(xiàn)爆炸動畫的主要內(nèi)容人弓。在其內(nèi)部存在一個(gè)內(nèi)部類Particle,該類盛放了一個(gè)小圓點(diǎn)的所有屬性信息着逐。ExplosionAnimator類的實(shí)例化過程中將傳入的view映射的矩形依照像素為顏色分解成15*15個(gè)小圓點(diǎn)將其屬性數(shù)據(jù)存儲在Particle實(shí)例中崔赌,通過generateParticle()
方法使得小圓點(diǎn)按照一定的比例獲得不同的屬性值,決定之后的運(yùn)動軌跡以及透明度變化耸别。
現(xiàn)在已經(jīng)搞清楚健芭,圖標(biāo)是怎么變成很多個(gè)小球的了,最后來看看秀姐,小球運(yùn)動的動畫如何實(shí)現(xiàn)慈迈。
在上面我們有看到ExplosionAnimator實(shí)例化之后緊接著調(diào)用了start()方法。這就要看到ExplosionField類的onDraw()
方法了省有。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (ExplosionAnimator explosion : mExplosions) {
explosion.draw(canvas);
}
}
其中主要內(nèi)容便是調(diào)用了explosion.draw(canvas);
public boolean draw(Canvas canvas) {
if (!isStarted()) {
return false;
}
for (Particle particle : mParticles) {
particle.advance((float) getAnimatedValue());
if (particle.alpha > 0f) {
mPaint.setColor(particle.color);
mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
}
}
mContainer.invalidate();
return true;
}
這個(gè)方法才真正實(shí)現(xiàn)了對view映射的矩形進(jìn)行操作痒留,根據(jù)getAnimatedValue()
動畫的進(jìn)度來進(jìn)行小圓點(diǎn)狀態(tài)的刷新,從而實(shí)現(xiàn)爆炸動畫蠢沿。
總結(jié)
爆炸動畫的實(shí)現(xiàn)伸头,其實(shí)就是一個(gè)屬性動畫的靈活運(yùn)用,使用屬性動畫為我們提供一個(gè)動畫進(jìn)度舷蟀,利用再view的刷新機(jī)制去完成動畫效果恤磷。