亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android中View的炸裂特效實(shí)現(xiàn)方法詳解

 更新時(shí)間:2016年07月25日 15:43:33   作者:feelang  
這篇文章主要介紹了Android中View的炸裂特效實(shí)現(xiàn)方法,涉及Android組件ExplosionField的相關(guān)定義與使用技巧,需要的朋友可以參考下

本文實(shí)例講述了Android中View的炸裂特效實(shí)現(xiàn)方法。分享給大家供大家參考,具體如下:

前幾天微博上被一個(gè)很優(yōu)秀的 Android 開(kāi)源組件刷屏了 - ExplosionField,效果非??犰?,有點(diǎn)類(lèi)似 MIUI 卸載 APP 時(shí)的動(dòng)畫(huà),先來(lái)感受一下。

ExplosionField 不但效果很拉風(fēng),代碼寫(xiě)得也相當(dāng)好,讓人忍不住要拿來(lái)好好讀一下。

創(chuàng)建 ExplosionField

ExplosionField 繼承自 View,在 onDraw 方法中繪制動(dòng)畫(huà)特效,并且它提供了一個(gè) attach2Window 方法,可以把 ExplosionField 最為一個(gè)子 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,
這樣一來(lái),一個(gè) view 炸裂出來(lái)的粒子可以繪制在整個(gè) Activity 所在的區(qū)域。

知識(shí)點(diǎn):可以用 Window.ID_ANDROID_CONTENT 來(lái)替代 android.R.id.content

炸裂之前的震動(dòng)效果

在 View 的點(diǎn)擊事件中,調(diào)用 mExplosionField.explode(v)之后,View 首先會(huì)震動(dòng),然后再炸裂。

震動(dòng)效果比較簡(jiǎn)單,設(shè)定一個(gè) [0, 1] 區(qū)間 ValueAnimator,然后在 AnimatorUpdateListener 的 onAnimationUpdate 中隨機(jī)平移 x 和 y坐標(biāo),最后把 scale 和 alpha 值動(dòng)態(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)建一個(gè) bitmap

View 震動(dòng)完了就開(kāi)始進(jìn)行最難的炸裂,并且炸裂是跟隱藏同時(shí)進(jìn)行的,先來(lái)看一下炸裂的 API -

void explode(Bitmap bitmap, Rect bound, long startDelay, long duration)

前兩個(gè)參數(shù) bitmap 和 bound 是關(guān)鍵,通過(guò) View 來(lái)創(chuàng)建 bitmap 的代碼比較有意思。

如果 View 是一個(gè) ImageView,并且它的 Drawable 是一個(gè) BitmapDrawable 就可以直接獲取這個(gè) Bitmap。

if (view instanceof ImageView) {
 Drawable drawable = ((ImageView) view).getDrawable();
 if (drawable != null && drawable instanceof BitmapDrawable) {
  return ((BitmapDrawable) drawable).getBitmap();
 }
}

如果不是一個(gè) ImageView,可以按照如下步驟創(chuàng)建一個(gè) bitmap:

1. 新建一個(gè) Canvas

2. 根據(jù) View 的大小創(chuàng)建一個(gè)空的 bitmap

3. 把空的 bitmap 設(shè)置為 Canvas 的底布

4. 把 view 繪制在 canvas上

5. 把 canvas 的 bitmap 設(shè)置成 null

當(dāng)然,繪制之前要清掉 View 的焦點(diǎn),因?yàn)榻裹c(diǎn)可能會(huì)改變一個(gè) View 的 UI 狀態(tài)。

以下代碼中用到的 sCanvas 是一個(gè)靜態(tài)變量,這樣可以節(jié)省每次創(chuàng)建時(shí)產(chǎn)生的開(kāi)銷(xiāo)。

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 時(shí)產(chǎn)生了 OOM,可以主動(dòng)進(jìn)行一次 GC - System.gc(),然后再次嘗試創(chuàng)建。

這個(gè)函數(shù)的實(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,還有一個(gè)一個(gè)很重要的參數(shù) bound,它的創(chuàng)建相對(duì)比較簡(jiǎn)單:

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,然后通過(guò) getLocationOnScreen(location) 獲取 ExplosionField 在屏幕中的坐標(biāo),并根據(jù)這個(gè)坐標(biāo)把 炸裂View 的可視區(qū)域進(jìn)行平移,這樣炸裂效果才會(huì)顯示在 ExplosionField 中,最后根據(jù) mExpandInset 值(默認(rèn)為 0)擴(kuò)展一下。

那創(chuàng)建的 bitmap 和 bound 有什么用呢?我們繼續(xù)往下分析。

創(chuàng)建粒子

先來(lái)看一下炸裂成粒子這個(gè)方法的全貌:

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();
}

這里要解釋一下為什么用一個(gè)容器類(lèi)變量 - mExplosions 來(lái)保存一個(gè) ExplosionAnimator。因?yàn)?activity 中多個(gè) View 的炸裂效果可能要同時(shí)進(jìn)行,所以要把每個(gè) View 對(duì)應(yīng)的炸裂動(dòng)畫(huà)保存起來(lái),等動(dòng)畫(huà)結(jié)束的時(shí)候再刪掉。

作者自定義了一個(gè)繼承自 ValueAnimator 的類(lèi) - ExplosionAnimator,它主要做了兩件事情,一個(gè)是創(chuàng)建粒子 - generateParticle,另一個(gè)是繪制粒子 - draw(Canvas canvas)。

先來(lái)看一下構(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 分成了一個(gè) 17 x 17 的矩陣,每個(gè)元素的寬度和高度分別是 w 和 h。

int w = bitmap.getWidth() / (partLen + 2);
int h = bitmap.getHeight() / (partLen + 2);

所有的粒子是一個(gè) 15 x 15 的矩陣,元素色值是位圖對(duì)應(yīng)的像素值。

bitmap.getPixel((j + 1) * w, (i + 1) * h)

結(jié)構(gòu)如下圖所示,其中空心部分是粒子。

 ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ●
 ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

generateParticle 會(huì)根據(jù)一定的算法隨機(jī)地生成一個(gè)粒子。這部分比較繁瑣,分析略去。

其中比較巧妙的還是它的 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;
}

剛開(kāi)始我還一直比較困惑,既然繪制粒子是在 ExplosionField 的 onDraw 方法中進(jìn)行,那肯定需要不停地刷新,結(jié)果作者并不是這么做的,實(shí)現(xiàn)方法又著實(shí)驚艷了一把。

首先,作者在 ExplosionAnimator 類(lèi)中重載了 start() 方法,通過(guò)調(diào)用 mContainer.invalidate(mBound) 來(lái)刷新 將要炸裂的 View 所對(duì)應(yīng)的區(qū)塊。

@Override
public void start() {
 super.start();
 mContainer.invalidate(mBound);
}

而 mContainer 即是占滿了 activity 的 view - ExplosionField,它的 onDraw 方法中又會(huì)調(diào)用 ExplosionAnimator 的 draw 方法。

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 for (ExplosionAnimator explosion : mExplosions) {
  explosion.draw(canvas);
 }
}

這樣便形成了一個(gè)遞歸,兩者相互調(diào)用,不停地刷新,直到所有粒子的 alpha 值變?yōu)?0,刷新就停下來(lái)了。

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é)

這個(gè)開(kāi)源庫(kù)的代碼質(zhì)量相當(dāng)高,十分佩服作者。

更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專(zhuān)題:《Android視圖View技巧總結(jié)》、《Android操作XML數(shù)據(jù)技巧總結(jié)》、《Android編程之a(chǎn)ctivity操作技巧總結(jié)》、《Android資源操作技巧匯總》、《Android文件操作技巧匯總》、《Android操作SQLite數(shù)據(jù)庫(kù)技巧總結(jié)》、《Android操作json格式數(shù)據(jù)技巧總結(jié)》、《Android數(shù)據(jù)庫(kù)操作技巧總結(jié)》、《Android編程開(kāi)發(fā)之SD卡操作方法匯總》、《Android開(kāi)發(fā)入門(mén)與進(jìn)階教程》及《Android控件用法總結(jié)

希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。

相關(guān)文章

最新評(píng)論