Android中View的炸裂特效實現(xiàn)方法詳解
本文實例講述了Android中View的炸裂特效實現(xiàn)方法。分享給大家供大家參考,具體如下:
前幾天微博上被一個很優(yōu)秀的 Android 開源組件刷屏了 - ExplosionField,效果非??犰?,有點類似 MIUI 卸載 APP 時的動畫,先來感受一下。
ExplosionField 不但效果很拉風(fēng),代碼寫得也相當(dāng)好,讓人忍不住要拿來好好讀一下。
創(chuàng)建 ExplosionField
ExplosionField 繼承自 View,在 onDraw 方法中繪制動畫特效,并且它提供了一個 attach2Window 方法,可以把 ExplosionField 最為一個子 View 添加到 Activity 上的 root view 中。
public static ExplosionField attach2Window(Activity activity) {
ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
ExplosionField explosionField = new ExplosionField(activity);
rootView.addView(explosionField, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return explosionField;
}
explosionField 的 LayoutParams 屬性都被設(shè)置為 MATCH_PARENT,
這樣一來,一個 view 炸裂出來的粒子可以繪制在整個 Activity 所在的區(qū)域。
知識點:可以用 Window.ID_ANDROID_CONTENT 來替代 android.R.id.content
炸裂之前的震動效果
在 View 的點擊事件中,調(diào)用 mExplosionField.explode(v)之后,View 首先會震動,然后再炸裂。
震動效果比較簡單,設(shè)定一個 [0, 1] 區(qū)間 ValueAnimator,然后在 AnimatorUpdateListener 的 onAnimationUpdate 中隨機平移 x 和 y坐標(biāo),最后把 scale 和 alpha 值動態(tài)減為 0。
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.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();
根據(jù) View 創(chuàng)建一個 bitmap
View 震動完了就開始進(jìn)行最難的炸裂,并且炸裂是跟隱藏同時進(jìn)行的,先來看一下炸裂的 API -
void explode(Bitmap bitmap, Rect bound, long startDelay, long duration)
前兩個參數(shù) bitmap 和 bound 是關(guān)鍵,通過 View 來創(chuàng)建 bitmap 的代碼比較有意思。
如果 View 是一個 ImageView,并且它的 Drawable 是一個 BitmapDrawable 就可以直接獲取這個 Bitmap。
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
}
如果不是一個 ImageView,可以按照如下步驟創(chuàng)建一個 bitmap:
1. 新建一個 Canvas
2. 根據(jù) View 的大小創(chuàng)建一個空的 bitmap
3. 把空的 bitmap 設(shè)置為 Canvas 的底布
4. 把 view 繪制在 canvas上
5. 把 canvas 的 bitmap 設(shè)置成 null
當(dāng)然,繪制之前要清掉 View 的焦點,因為焦點可能會改變一個 View 的 UI 狀態(tài)。
以下代碼中用到的 sCanvas 是一個靜態(tài)變量,這樣可以節(jié)省每次創(chuàng)建時產(chǎn)生的開銷。
view.clearFocus();
Bitmap bitmap = createBitmapSafely(view.getWidth(),
view.getHeight(), Bitmap.Config.ARGB_8888, 1);
if (bitmap != null) {
synchronized (sCanvas) {
Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
view.draw(canvas);
canvas.setBitmap(null);
}
}
作者創(chuàng)建位圖的辦法非常巧妙,如果新建 Bitmap 時產(chǎn)生了 OOM,可以主動進(jìn)行一次 GC - System.gc(),然后再次嘗試創(chuàng)建。
這個函數(shù)的實現(xiàn)方式讓人佩服作者的功力。
public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
try {
return Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
e.printStackTrace();
if (retryCount > 0) {
System.gc();
return createBitmapSafely(width, height, config, retryCount - 1);
}
return null;
}
}
出了 bitmap,還有一個一個很重要的參數(shù) bound,它的創(chuàng)建相對比較簡單:
Rect r = new Rect(); view.getGlobalVisibleRect(r); int[] location = new int[2]; getLocationOnScreen(location); r.offset(-location[0], -location[1]); r.inset(-mExpandInset[0], -mExpandInset[1]);
首先獲取 需要炸裂的View 的全局可視區(qū)域 - Rect r,然后通過 getLocationOnScreen(location) 獲取 ExplosionField 在屏幕中的坐標(biāo),并根據(jù)這個坐標(biāo)把 炸裂View 的可視區(qū)域進(jìn)行平移,這樣炸裂效果才會顯示在 ExplosionField 中,最后根據(jù) mExpandInset 值(默認(rèn)為 0)擴展一下。
那創(chuàng)建的 bitmap 和 bound 有什么用呢?我們繼續(xù)往下分析。
創(chuàng)建粒子
先來看一下炸裂成粒子這個方法的全貌:
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();
}
這里要解釋一下為什么用一個容器類變量 - mExplosions 來保存一個 ExplosionAnimator。因為 activity 中多個 View 的炸裂效果可能要同時進(jìn)行,所以要把每個 View 對應(yīng)的炸裂動畫保存起來,等動畫結(jié)束的時候再刪掉。
作者自定義了一個繼承自 ValueAnimator 的類 - ExplosionAnimator,它主要做了兩件事情,一個是創(chuàng)建粒子 - generateParticle,另一個是繪制粒子 - draw(Canvas canvas)。
先來看一下構(gòu)造函數(shù):
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++) {
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);
}
根據(jù)構(gòu)造函數(shù)可以知道作者把 bitmap 分成了一個 17 x 17 的矩陣,每個元素的寬度和高度分別是 w 和 h。
int w = bitmap.getWidth() / (partLen + 2); int h = bitmap.getHeight() / (partLen + 2);
所有的粒子是一個 15 x 15 的矩陣,元素色值是位圖對應(yīng)的像素值。
bitmap.getPixel((j + 1) * w, (i + 1) * h)
結(jié)構(gòu)如下圖所示,其中空心部分是粒子。
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
generateParticle 會根據(jù)一定的算法隨機地生成一個粒子。這部分比較繁瑣,分析略去。
其中比較巧妙的還是它的 draw 方法:
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;
}
剛開始我還一直比較困惑,既然繪制粒子是在 ExplosionField 的 onDraw 方法中進(jìn)行,那肯定需要不停地刷新,結(jié)果作者并不是這么做的,實現(xiàn)方法又著實驚艷了一把。
首先,作者在 ExplosionAnimator 類中重載了 start() 方法,通過調(diào)用 mContainer.invalidate(mBound) 來刷新 將要炸裂的 View 所對應(yīng)的區(qū)塊。
@Override
public void start() {
super.start();
mContainer.invalidate(mBound);
}
而 mContainer 即是占滿了 activity 的 view - ExplosionField,它的 onDraw 方法中又會調(diào)用 ExplosionAnimator 的 draw 方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (ExplosionAnimator explosion : mExplosions) {
explosion.draw(canvas);
}
}
這樣便形成了一個遞歸,兩者相互調(diào)用,不停地刷新,直到所有粒子的 alpha 值變?yōu)?0,刷新就停下來了。
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;
}
總結(jié)
這個開源庫的代碼質(zhì)量相當(dāng)高,十分佩服作者。
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android視圖View技巧總結(jié)》、《Android操作XML數(shù)據(jù)技巧總結(jié)》、《Android編程之a(chǎn)ctivity操作技巧總結(jié)》、《Android資源操作技巧匯總》、《Android文件操作技巧匯總》、《Android操作SQLite數(shù)據(jù)庫技巧總結(jié)》、《Android操作json格式數(shù)據(jù)技巧總結(jié)》、《Android數(shù)據(jù)庫操作技巧總結(jié)》、《Android編程開發(fā)之SD卡操作方法匯總》、《Android開發(fā)入門與進(jìn)階教程》及《Android控件用法總結(jié)》
希望本文所述對大家Android程序設(shè)計有所幫助。
相關(guān)文章
Android自定義View實現(xiàn)餅狀圖帶動畫效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實現(xiàn)餅狀圖帶動畫效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
Android中PopuWindow實現(xiàn)下拉列表實例
本篇文章主要介紹了Android中PopuWindow實現(xiàn)下拉列表實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
Android ScrollView實現(xiàn)向上滑動控件頂部懸浮效果
這篇文章主要為大家詳細(xì)介紹了Android ScrollView實現(xiàn)向上滑動控件頂部懸浮效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05
android開發(fā)教程之使用listview顯示qq聯(lián)系人列表
這篇文章主要介紹了android使用listview顯示qq聯(lián)系人列表的示例,需要的朋友可以參考下2014-02-02
Android 中ScrollView與ListView沖突問題的解決辦法
這篇文章主要介紹了Android 中ScrollView與ListView沖突問題的解決辦法的相關(guān)資料,希望通過本文能幫助到大家,讓大家掌握解決問題的辦法,需要的朋友可以參考下2017-10-10

