這是前幾日在朋友圈傳瘋了的開源項目 如果沒記錯的話 小米手機卸載應用的時候就是使用的這個效果 於是我去github fork 了這個項目 地址如下:
效果圖:
我使用的IDE 是 android studio
我把源碼 和範例程序簡單的移植到了android studio 然後隨便拿了幾個圖(其實是QQ空間apk裏的)
工程目錄如下
其實很簡單啦 就是個activity 點擊其中每個 view 就會產生爆炸特效 首先view會顫抖下 然後爆炸
所有的源碼都有註釋 如果有錯 歡迎指出
下載:
點擊打開鏈接
MainActivity.java
package com.chan.explosionfield;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
public class MainActivity extends AppCompatActivity {
//爆炸區域
private ExplosionField mExplosionField;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExplosionField = ExplosionField.attach2Window(this);
addListener(findViewById(R.id.root));
}
//給需要爆炸的視圖添加到爆炸區域中
private void addListener(View root) {
//如果是view group 類型 就把它的子視圖添加到區域中
if (root instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) root;
for (int i = 0; i < parent.getChildCount(); i++) {
addListener(parent.getChildAt(i));
}
}
//這裏是View 類型的視圖
else {
//設置它爲可點擊的
root.setClickable(true);
//添加監聽器
root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//爆炸該視圖
mExplosionField.explode(v);
//取消註冊其點擊事件
v.setOnClickListener(null);
}
});
}
}
}
到這裏不得不看explision field的源碼 爆炸特效從explode那個函數開始
ExplisionField.java
/*
* Copyright (C) 2015 tyrantgit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chan.explosionfield;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* 爆炸區域
*/
public class ExplosionField extends View {
////////////////////////////////////////////////////////////////////////////////////////////////
//爆炸的動畫
private List<ExplosionAnimator> mExplosions = new ArrayList<>();
private int[] mExpandInset = new int[2];
////////////////////////////////////////////////////////////////////////////////////////////////
//ctor
public ExplosionField(Context context) {
super(context);
init();
}
public ExplosionField(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExplosionField(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
Arrays.fill(mExpandInset, Utils.dp2Px(32));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//這裏配合Explosion Animator的draw互相調用 知道用完動畫的播放時間
for (ExplosionAnimator explosion : mExplosions) {
explosion.draw(canvas);
}
}
public void expandExplosionBound(int dx, int dy) {
mExpandInset[0] = dx;
mExpandInset[1] = dy;
}
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();
}
/**
* 引爆view
* @param view 即將被引爆的view
*/
private int i = 0;
public void explode(final View view) {
//獲得它被可見的區域
Rect r = new Rect();
view.getGlobalVisibleRect(r);
//獲得當前視圖在屏幕中的位置
int[] location = new int[2];
getLocationOnScreen(location);
//偏移rect 但是我沒能理解這個意思
r.offset(-location[0], -location[1]);
r.inset(-mExpandInset[0], -mExpandInset[1]);
int startDelay = 100;
//這個動畫使得view “振動”
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();
//爆炸相關的視圖
explode(Utils.createBitmapFromView(view),
r,
startDelay,
ExplosionAnimator.DEFAULT_DURATION
);
}
public void clear() {
mExplosions.clear();
invalidate();
}
//獲得爆炸區域
public static ExplosionField attach2Window(Activity activity) {
//獲得MainActivity layout的 根佈局的父佈局
//在activity中 setContentView 會在當前佈局文件外再套一個父佈局
ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
ExplosionField explosionField = new ExplosionField(activity);
//將爆炸區域添加到其中
//ExplosionField extents View
rootView.addView(explosionField, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
//返回爆炸區域
return explosionField;
}
}
剛剛在MainActivity看到ExplosionField是由attach2Window這個方法產生 這裏的註釋是很完整的
爆炸時會產生當前view 的快照 然後根據快照 取其中的像素 作爲爆炸煙火的顏色 這個均由Utils.java生成
Utils.java
/*
* Copyright (C) 2015 tyrantgit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chan.explosionfield;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
public class Utils {
private Utils() {
}
/**
* 像素密度
*/
private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;
/**
* 用來繪圖
*/
private static final Canvas sCanvas = new Canvas();
/**
* 將dp 轉爲 像素
* @param dp
* @return
*/
public static int dp2Px(int dp) {
return Math.round(dp * DENSITY);
}
/**
* 從視圖獲得它的圖像
* @param view 要爆炸的view
* @return 它的圖像
*/
public static Bitmap createBitmapFromView(View view) {
//如果當前的是ImageView 類型
//那麼最方便了 它的Drawable 是 BitmapDrawable的
//可以直接獲得其中的圖
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
}
//如果不是
//那麼首先 就要使他失去焦點
//因爲獲得了焦點的視圖可能會隨時就改變
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);
//將視圖繪製在canvas中
view.draw(canvas);
//然後一處空白的快照
//以此來獲得真正的視圖快照
canvas.setBitmap(null);
}
}
//現在空白的快照已經有了view的樣子
//是真正的快照了
return bitmap;
}
/**
* 創建一個和指定尺寸大小一樣的bitmap
* @param width 寬
* @param height 高
* @param config 快照配置 詳見 {@link android.graphics.Bitmap.Config}
* @param retryCount 當生成空白bitmap發生oom時 我們會嘗試再試試生成bitmap 這個爲嘗試的次數
* @return 一個和指定尺寸大小一樣的bitmap
*/
public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
try {
//創建空白的bitmap
return Bitmap.createBitmap(width, height, config);
//如果發生了oom
} catch (OutOfMemoryError e) {
e.printStackTrace();
if (retryCount > 0) {
//主動gc 然後再次試試
System.gc();
return createBitmapSafely(width, height, config, retryCount - 1);
}
//直到次數用光
return null;
}
}
}
最後的特效都是在動畫裏面產生的
/*
* Copyright (C) 2015 tyrantgit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.chan.explosionfield;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import java.util.Random;
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;
//要爆炸的view
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++) {
//要取顏色的位置
final int x = (j + 1) * w;
final int y = (i + 1) * h;
//獲取顏色
final int color = bitmap.getPixel(x, y);
//生成爆炸粒子
mParticles[(i * partLen) + j] = generateParticle(color, random);
}
}
//保存當前的視圖
mContainer = container;
//設置值
setFloatValues(0f, END_VALUE);
//設置補間器
setInterpolator(DEFAULT_INTERPOLATOR);
//設置動畫時長
setDuration(DEFAULT_DURATION);
}
/**
* 生成爆炸粒子
* @param color 爆炸粒子的顏色
* @param random
* @return 爆炸粒子
*/
private Particle generateParticle(int color, Random random) {
//生成煙花點
Particle particle = new Particle();
particle.color = color;
//設置半徑
particle.radius = V;
//產生隨機大小的base radius
if (random.nextFloat() < 0.2f) {
particle.baseRadius = V + ((X - V) * random.nextFloat());
} else {
particle.baseRadius = W + ((V - W) * random.nextFloat());
}
float nextFloat = random.nextFloat();
particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());
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);
}
}
//這裏配合view 的 draw 互相調用
mContainer.invalidate();
return true;
}
@Override
public void start() {
super.start();
//這裏配合view 的 draw 互相調用
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;
public void advance(float factor) {
float f = 0f;
//這代表一個煙花點消逝的條件
float normalization = factor / END_VALUE;
if (normalization < life || normalization > 1f - overflow) {
alpha = 0f;
return;
}
//然後計算出煙花點的半徑 座標 透明度參數
//純數學計算
normalization = (normalization - life) / (1f - life - overflow);
float f2 = normalization * END_VALUE;
if (normalization >= 0.7f) {
f = (normalization - 0.7f) / 0.3f;
}
alpha = 1f - f;
f = bottom * f2;
cx = baseCx + f;
cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
radius = V + (baseRadius - V) * f2;
}
}
}