這個破碎動畫,是一種類似小米系統(tǒng)刪除應(yīng)用時的爆炸破碎效果的動畫。
雙陽網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,雙陽網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為雙陽成百上千提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站建設(shè)公司要多少錢,請找那個售后服務(wù)好的雙陽做網(wǎng)站的公司定做!
效果圖展示
先來看下是怎樣的動效,要是感覺不是理想的學習目標,就跳過,避免浪費大家的時間。��
源碼在這里:point_right: https://github.com/ReadyShowShow/explosion
一行代碼即可調(diào)用該動畫
new ExplosionField(this).explode(view, null))
下面開始我們酷炫的Android動畫特效正式講解:point_down:
先來個整體結(jié)構(gòu)的把握
整體結(jié)構(gòu)非常簡單明了,新老從業(yè)者都可快速看懂,容易把握學習。
./ |-- explosion | |-- MainActivity.java (測試爆炸破碎動效的主界面) | |-- animation(爆炸破碎動效有關(guān)的類均在這里) | | |-- ExplosionAnimator.java(爆炸動畫) | | |-- ExplosionField.java(爆炸破碎動畫所依賴的View) | | `-- ParticleModel.java(每個破碎后的粒子的model,顏色、位置、大小等) | `-- utils | `-- UIUtils.java(計算狀態(tài)欄高度的工具類) `-- tree.txt
庖丁解牛
下面開始每個類的詳細分析
本著從簡到繁、由表及里的原則,詳細講解每個類
MainActivity.java
MainActivity.java是測試動效的界面,該Activity內(nèi)部有7個測試按鈕。該類做的事情非常單純,就是給每個View分別綁定click點擊事件,讓View在點擊時能觸發(fā)爆炸破碎動畫。
/** * 說明:測試的界面 * 作者:Jian * 時間:2017/12/26. */ public class MainActivity extends AppCompatActivity { /** * 加載布局文件,添加點擊事件 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViewsClick(); } /** * 添加點擊事件的實現(xiàn) */ private void initViewsClick() { // 為單個View添加點擊事件 final View title = findViewById(R.id.title_tv); title.setOnClickListener(v -> new ExplosionField(MainActivity.this).explode(title, null)); // 為中間3個View添加點擊事件 setSelfAndChildDisappearOnClick(findViewById(R.id.title_disappear_ll)); // 為下面3個View添加點擊事件 setSelfAndChildDisappearAndAppearOnClick(findViewById(R.id.title_disappear_and_appear_ll)); // 跳轉(zhuǎn)到github網(wǎng)頁的點擊事件 findViewById(R.id.github_tv).setOnClickListener((view) -> { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); Uri content_url = Uri.parse(getString(R.string.github)); intent.setData(content_url); startActivity(intent); }); } /** * 為自己以及子View添加破碎動畫,動畫結(jié)束后,把View消失掉 * @param view 可能是ViewGroup的view */ private void setSelfAndChildDisappearOnClick(final View view) { if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { setSelfAndChildDisappearOnClick(viewGroup.getChildAt(i)); } } else { view.setOnClickListener(v -> new ExplosionField(MainActivity.this).explode(view, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); view.setVisibility(View.GONE); } })); } } /** * 為自己以及子View添加破碎動畫,動畫結(jié)束后,View自動出現(xiàn) * @param view 可能是ViewGroup的view */ private void setSelfAndChildDisappearAndAppearOnClick(final View view) { if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { setSelfAndChildDisappearAndAppearOnClick(viewGroup.getChildAt(i)); } } else { view.setOnClickListener(v -> new ExplosionField(MainActivity.this).explode(view, null)); } } }
ParticleModel.java
ParticleModel.java是包含一個粒子的所有信息的model。advance方法根據(jù)值動畫返回的進度計算出粒子的位置和顏色等信息
/** * 說明:爆破粒子,每個移動與漸變的小塊 * 作者:Jian * 時間:2017/12/26. */ class ParticleModel { // 默認小球?qū)捀? static final int PART_WH = 8; // 隨機數(shù),隨機出位置和大小 static Random random = new Random(); //center x of circle float cx; //center y of circle float cy; // 半徑 float radius; // 顏色 int color; // 透明度 float alpha; // 整體邊界 Rect mBound; ParticleModel(int color, Rect bound, Point point) { int row = point.y; //行是高 int column = point.x; //列是寬 this.mBound = bound; this.color = color; this.alpha = 1f; this.radius = PART_WH; this.cx = bound.left + PART_WH * column; this.cy = bound.top + PART_WH * row; } // 每一步動畫都得重新計算出自己的狀態(tài)值 void advance(float factor) { cx = cx + factor * random.nextInt(mBound.width()) * (random.nextFloat() - 0.5f); cy = cy + factor * random.nextInt(mBound.height() / 2); radius = radius - factor * random.nextInt(2); alpha = (1f - factor) * (1 + random.nextFloat()); } }
ExplosionAnimation.java
ExlosionAnimation.java是動畫類,是一個值動畫,在值動畫每次產(chǎn)生一個值的時候,就計算出整個爆炸破碎動效內(nèi)的全部粒子的狀態(tài)。這些狀態(tài)交由使用的View在渲染時進行顯示。
/** * 說明:爆炸動畫類,讓離子移動和控制離子透明度 * 作者:Jian * 時間:2017/12/26. */ class ExplosionAnimator extends ValueAnimator { private static final int DEFAULT_DURATION = 1500; private ParticleModel[][] mParticles; private Paint mPaint; private View mContainer; public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) { setFloatValues(0.0f, 1.0f); setDuration(DEFAULT_DURATION); mPaint = new Paint(); mContainer = view; mParticles = generateParticles(bitmap, bound); } // 生成粒子,按行按列生成全部粒子 private ParticleModel[][] generateParticles(Bitmap bitmap, Rect bound) { int w = bound.width(); int h = bound.height(); // 橫向粒子的個數(shù) int horizontalCount = w / ParticleModel.PART_WH; // 豎向粒子的個數(shù) int verticalCount = h / ParticleModel.PART_WH; // 粒子寬度 int bitmapPartWidth = bitmap.getWidth() / horizontalCount; // 粒子高度 int bitmapPartHeight = bitmap.getHeight() / verticalCount; ParticleModel[][] particles = new ParticleModel[verticalCount][horizontalCount]; for (int row = 0; row < verticalCount; row++) { for (int column = 0; column < horizontalCount; column++) { //取得當前粒子所在位置的顏色 int color = bitmap.getPixel(column * bitmapPartWidth, row * bitmapPartHeight); Point point = new Point(column, row); particles[row][column] = new ParticleModel(color, bound, point); } } return particles; } // 由view調(diào)用,在View上繪制全部的粒子 void draw(Canvas canvas) { // 動畫結(jié)束時停止 if (!isStarted()) { return; } // 遍歷粒子,并繪制在View上 for (ParticleModel[] particle : mParticles) { for (ParticleModel p : particle) { p.advance((Float) getAnimatedValue()); mPaint.setColor(p.color); // 錯誤的設(shè)置方式只是這樣設(shè)置,透明色會顯示為黑色 // mPaint.setAlpha((int) (255 * p.alpha)); // 正確的設(shè)置方式,這樣透明顏色就不是黑色了 mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha)); canvas.drawCircle(p.cx, p.cy, p.radius, mPaint); } } mContainer.invalidate(); } @Override public void start() { super.start(); mContainer.invalidate(); } }
ExplosionField.java
ExplosionField.java是真實執(zhí)行上面ExplosionAnimator。ExplosionField會創(chuàng)建一個View并依附在Activity的根View上。
/** * 說明:每次爆炸時,創(chuàng)建一個覆蓋全屏的View,這樣的話,不管要爆炸的View在任何位置都能顯示爆炸效果 * 作者:Jian * 時間:2017/12/26. */ public class ExplosionField extends View { private static final String TAG = "ExplosionField"; private static final Canvas mCanvas = new Canvas(); private ExplosionAnimator animator; public ExplosionField(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); animator.draw(canvas); } /** * 執(zhí)行爆破破碎動畫 */ public void explode(final View view, final AnimatorListenerAdapter listener) { Rect rect = new Rect(); view.getGlobalVisibleRect(rect); //得到view相對于整個屏幕的坐標 rect.offset(0, -UIUtils.statusBarHeignth()); //去掉狀態(tài)欄高度 animator = new ExplosionAnimator(this, createBitmapFromView(view), rect); // 接口回調(diào) animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { if (listener != null) listener.onAnimationStart(animation); // 延時添加到界面上 attach3Activity((Activity) getContext()); // 讓被爆炸的View消失(爆炸的View是新創(chuàng)建的View,原View本身不會發(fā)生任何變化) view.animate().alpha(0f).setDuration(150).start(); } @Override public void onAnimationEnd(Animator animation) { if (listener != null) listener.onAnimationEnd(animation); // 從界面中移除 removeFromActivity((Activity) getContext()); // 讓被爆炸的View顯示(爆炸的View是新創(chuàng)建的View,原View本身不會發(fā)生任何變化) view.animate().alpha(1f).setDuration(150).start(); } @Override public void onAnimationCancel(Animator animation) { if (listener != null) listener.onAnimationCancel(animation); } @Override public void onAnimationRepeat(Animator animation) { if (listener != null) listener.onAnimationRepeat(animation); } }); animator.start(); } private Bitmap createBitmapFromView(View view) { // 為什么屏蔽以下代碼段? // 如果ImageView直接得到位圖,那么當它設(shè)置背景(backgroud)時,不會讀取到背景顏色 // if (view instanceof ImageView) { // Drawable drawable = ((ImageView)view).getDrawable(); // if (drawable != null && drawable instanceof BitmapDrawable) { // return ((BitmapDrawable) drawable).getBitmap(); // } // } //view.clearFocus(); //不同焦點狀態(tài)顯示的可能不同——(azz:不同就不同有什么關(guān)系?) Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); if (bitmap != null) { synchronized (mCanvas) { mCanvas.setBitmap(bitmap); view.draw(mCanvas); // 清除引用 mCanvas.setBitmap(null); } } return bitmap; } /** * 將創(chuàng)建的ExplosionField添加到Activity上 */ private void attach3Activity(Activity activity) { ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); rootView.addView(this, lp); } /** * 將ExplosionField從Activity上移除 */ private void removeFromActivity(Activity activity) { ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT); rootView.removeView(this); } }
動畫執(zhí)行時為什么要創(chuàng)建一個新View(ExplosionField)
其實上面的動畫類ExplosionAnimator已經(jīng)實現(xiàn)了核心功能,直接在原View上使用該動畫應(yīng)該是沒問題的。為什么還要引入一個ExplosionField類呢?動畫的執(zhí)行為什么不能直接在原本的View上執(zhí)行呢?偏偏要在一個看似多余的ExplosionField對象上執(zhí)行呢。
這里就得從Android下View繪制原理來解釋了:Android下的View都有一個Bound,在View進行measure和layout的時候,已經(jīng)確定了View的大小和位置,如果要在這個View上進行動畫的話,就會出現(xiàn)動畫只能在view大小范圍內(nèi)進行展現(xiàn)。當然了,也不是說在原來View上一定不能實現(xiàn)這一動效,就是相當復雜,要在動畫執(zhí)行過程中,不斷改變原View的大小和View的屬性等信息,相當復雜。
在性能還行的前提下,要優(yōu)先代碼的整潔度,盡量避免為了優(yōu)化的性能,而舍棄整潔清爽的代碼。一般來說,過度的優(yōu)化,并沒有給用戶帶來太多體驗上的提升,反而給項目帶來了巨大的維護難度。
UIUtils.java
UIUtils是關(guān)于UI的工具類,沒啥可說的
public class UIUtils { public static int dp2px(double dpi) { return (int) (Resources.getSystem().getDisplayMetrics().density * dpi + 0.5f); } public static int statusBarHeignth() { return dp2px(25); } }
結(jié)束
源碼:point_right: https://github.com/ReadyShowShow/explosion
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。