中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android 中 View 炸裂特效的實現分析 <IT藍豹>

發布時間:2020-06-16 12:23:18 來源:網絡 閱讀:356 作者:楊光成 欄目:移動開發




前幾天微博上被一個很優秀的 Android 開源組件刷屏了 - ExplosionField,效果非常酷炫,有點類似 MIUI 卸載 APP 時的動畫,先來感受一下。
 Android 中 View 炸裂特效的實現分析 <IT藍豹>

ExplosionField 不但效果很拉風,代碼寫得也相當好,讓人忍不住要拿來好好讀一下。


創建 ExplosionField

ExplosionField 繼承自 View,在 onDraw 方法中繪制動畫特效,并且它提供了一個 attach3Window 方法,可以把 ExplosionField 最為一個子 View 添加到 Activity 上的 root view 中。

public static ExplosionField attach3Window(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;
}1234567

explosionFieldLayoutParams 屬性都被設置為 MATCH_PARENT
這樣一來,一個 view 炸裂出來的粒子可以繪制在整個 Activity 所在的區域。

知識點:可以用 Window.ID_ANDROID_CONTENT 來替代 android.R.id.content

炸裂之前的震動效果

在 View 的點擊事件中,調用 mExplosionField.explode(v)之后,View 首先會震動,然后再炸裂。

震動效果比較簡單,設定一個 [0, 1] 區間 ValueAnimator,然后在 AnimatorUpdateListeneronAnimationUpdate 中隨機平移 x 和 y坐標,最后把 scale 和 alpha 值動態減為 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();123456789101112131415

根據 View 創建一個 bitmap

View 震動完了就開始進行最難的炸裂,并且炸裂是跟隱藏同時進行的,先來看一下炸裂的 API - void explode(Bitmap bitmap, Rect bound, long startDelay, long duration)

前兩個參數 bitmap 和 bound 是關鍵,通過 View 來創建 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();
    }
}123456

如果不是一個 ImageView,可以按照如下步驟創建一個 bitmap:

  1. 新建一個 Canvas

  2. 根據 View 的大小創建一個空的 bitmap

  3. 把空的 bitmap 設置為 Canvas 的底布

  4. 把 view 繪制在 canvas上

  5. 把 canvas 的 bitmap 設置成 null

當然,繪制之前要清掉 View 的焦點,因為焦點可能會改變一個 View 的 UI 狀態。
一下代碼中用到的 sCanvas 是一個靜態變量,這樣可以節省每次創建時產生的開銷。

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

作者創建位圖的辦法非常巧妙,如果新建 Bitmap 時產生了 OOM,可以主動進行一次 GC - System.gc(),然后再次嘗試創建。

這個函數的實現方式讓人佩服作者的功力。

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;
    }
}123456789101112

出了 bitmap,還有一個一個很重要的參數 bound,它的創建相對比較簡單:

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]);123456

首先獲取 需要炸裂的View 的全局可視區域 - Rect r,然后通過 getLocationOnScreen(location) 獲取 ExplosionField 在屏幕中的坐標,并根據這個坐標把 炸裂View 的可視區域進行平移,這樣炸裂效果才會顯示在 ExplosionField 中,最后根據 mExpandInset 值(默認為 0)擴展一下。

那創建的 bitmap 和 bound 有什么用呢?我們繼續往下分析。

創建粒子

先來看一下炸裂成粒子這個方法的全貌:

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

這里要解釋一下為什么用一個容器類變量 - mExplosions 來保存一個 ExplosionAnimator。因為 activity 中多個 View 的炸裂效果可能要同時進行,所以要把每個 View 對應的炸裂動畫保存起來,等動畫結束的時候再刪掉。

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

先來看一下構造函數:

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

根據構造函數可以知道作者把 bitmap 分成了一個 17 x 17 的矩陣,每個元素的寬度和高度分別是 wh

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

所有的粒子是一個 15 x 15 的矩陣,元素色值是位圖對應的像素值。

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

結構如下圖所示,其中空心部分是粒子。

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

generateParticle 會根據一定的算法隨機地生成一個粒子。這部分比較繁瑣,分析略去。

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

剛開始我還一直比較困惑,既然繪制粒子是在 ExplosionFieldonDraw 方法中進行,那肯定需要不停地刷新,結果作者并不是這么做的,實現方法又著實驚艷了一把。

首先,作者在 ExplosionAnimator 類中重載了 start() 方法,通過調用 mContainer.invalidate(mBound) 來刷新 將要炸裂的 View 所對應的區塊。

@Overridepublic void start() {    super.start();
    mContainer.invalidate(mBound);
}12345

而 mContainer 即是占滿了 activity 的 view - ExplosionField,它的 onDraw 方法中又會調用 ExplosionAnimatordraw 方法。

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

這樣便形成了一個遞歸,兩者相互調用,不停地刷新,直到所有粒子的 alpha 值變為 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;
}123456789101112131415

總結

這個開源庫的代碼質量相當高,十分佩服作者。



更多特效。。《IT藍豹》



向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

抚顺市| 彭泽县| 河东区| 砀山县| 卓尼县| 嘉鱼县| 鄂州市| 牡丹江市| 潜江市| 偏关县| 兴安县| 锡林郭勒盟| 宜城市| 扶绥县| 嘉善县| 额尔古纳市| 右玉县| 行唐县| 同心县| 汉源县| 哈密市| 大关县| 潍坊市| 余姚市| 贵南县| 军事| 前郭尔| 太白县| 汉中市| 淳化县| 图木舒克市| 叶城县| 郑州市| 讷河市| 绥江县| 梁山县| 大同市| 孝昌县| 汪清县| 江口县| 电白县|