天天看點

詳解ExplosionField的使用,實作View的粉碎效果1.獲得一個ExplosionField2.ExplosionField的使用3.源碼解讀

小米平闆解除安裝軟體的時候,會有一個粉碎的效果,看起來很拉風,GitHub上有一個開源控件可以實作這個效果,我們一起來看看。先來看看效果圖:

詳解ExplosionField的使用,實作View的粉碎效果1.獲得一個ExplosionField2.ExplosionField的使用3.源碼解讀

看起來不錯吧,那我們今天就來詳細說說ExplosionField的使用和它内部的實作原理吧。

1.獲得一個ExplosionField

ExplosionField是GitHub上的一個開源項目,我們直接在GitHub上下載下傳就可以了。

下載下傳位址https://github.com/tyrantgit/ExplosionField

下載下傳好之後我們直接将tyrantgit檔案夾拷貝到我們的項目中就可以使用了,該檔案夾的位置在:ExplosionField-master\ExplosionField-master\explosionfield\src\main\java中。

注意:使用ExplosionField要求我們的jre版本在1.7以上。

2.ExplosionField的使用

布局檔案很簡單,就是四個ImageView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="horizontal" >

        <ImageView
            android:id="@+id/iv1"
            android:layout_width="72dp"
            android:layout_height="72dp"
            android:layout_weight="1"
            android:onClick="onClick"
            android:src="@drawable/p1" />

        <ImageView
            android:id="@+id/iv2"
            android:layout_width="72dp"
            android:layout_height="72dp"
            android:layout_weight="1"
            android:onClick="onClick"
            android:src="@drawable/p2" />
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal" >

        <ImageView
            android:id="@+id/iv3"
            android:layout_width="72dp"
            android:layout_height="72dp"
            android:layout_weight="1"
            android:onClick="onClick"
            android:src="@drawable/p3" />

        <ImageView
            android:id="@+id/iv4"
            android:layout_width="72dp"
            android:layout_height="72dp"
            android:layout_weight="1"
            android:onClick="onClick"
            android:src="@drawable/p4" />
    </LinearLayout>

</LinearLayout>
           

在MainActivity中,我們拿到一個ExplosionField的執行個體,然後通過執行它的explode方法就可以實作View的粉碎效果了。

public class MainActivity extends Activity {

	private ExplosionField explosionField;
	private ImageView iv1, iv2, iv3, iv4;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		iv1 = (ImageView) this.findViewById(R.id.iv1);
		iv2 = (ImageView) this.findViewById(R.id.iv2);
		iv3 = (ImageView) this.findViewById(R.id.iv3);
		iv4 = (ImageView) this.findViewById(R.id.iv4);
		explosionField = ExplosionField.attach2Window(this);
	}

	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.iv1:
			explosionField.explode(iv1);
			iv1.setVisibility(View.INVISIBLE);
			break;
		case R.id.iv2:
			explosionField.explode(iv2);
			iv2.setVisibility(View.INVISIBLE);
			break;
		case R.id.iv3:
			explosionField.explode(iv3);
			iv3.setVisibility(View.INVISIBLE);
			break;
		case R.id.iv4:
			explosionField.explode(iv4);
			iv4.setVisibility(View.INVISIBLE);
			break;
		}
	}
}
           

使用起來是如此的友善,這裡我要特别說一句,為什麼粉碎完成之後還要将控件隐藏,這是因為View粉碎之後,我們就看不到了,但是實際上它還在頁面上,如果這個View本身有點選事件,那麼這個時候你點選它粉碎前的位置,點選事件還是能夠響應,将控件隐藏之後就可以避免這個問題了。說了這麼多,我們來看看這個效果究竟是怎麼實作的。

3.源碼解讀

通過前面兩步,我們已經知道,跟這個效果有關的就是三個類,那麼我們這裡就從explosionField = ExplosionField.attach2Window(this);這行代碼所執行的attach2Window(this);方法看起:

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繼承自View,它本身就是一個View,那麼在attach2Window方法中,我們先拿到一個rootView,這個rootView其實就是我們setContentView(R.layout.activity_main);所設定的View,然後我們new一個ExplosionField并把它添加到rootView當中,并把它的寬高都設定為MATCH_PARENT,這樣就可以保證View爆炸産生的碎片可以被繪制在整個Activity區域。

好了,拿到一個ExplosionField對象之後,我們就可以粉碎一個View了,粉碎View執行的方法是explode,這個方法需要我們把要粉碎的View作為一個參數傳入進去,我們看看這個方法:

public void explode(final View view) {
		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]);
		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();
		explode(Utils.createBitmapFromView(view), r, startDelay,
				ExplosionAnimator.DEFAULT_DURATION);
	}
           

這裡的代碼我們可以分為4部分來了解:

第一部分:2-7行

這一段代碼我們主要是用來擷取要粉碎View的一個可視區域,拿到這個Rect之後,我們再拿到ExplosionField在目前頁面中的坐标,然後根據這個坐标對Rect進行平移,平移之後就可以讓粉碎的效果在ExplosionField中顯示,最後我們還要将Rect的區域擴大一下,預設情況下,橫向擴大64dp,縱向擴大64dp,mExpandInset的初始化在ExplosionField的構造方法中調用,最終拿到的Rect我們會在第四部分使用。

第二部分:8-23行

這一段代碼主要是是完成粉碎前的振動效果,一個View要被粉碎了,吓得它先打個哆嗦。這裡的實作還是比較簡單的,先是生成一個随機數,根據這個随機數算出View在X軸和Y軸平移的距離,反複的平移就形成了振動效果。

第三部分:24-25行

這一段代碼是将View隐藏,隐藏的方式就是讓scaleX和scaleY、alpha最終都變為0f。

第四部分:26-27行

這一段代碼是粉碎View的核心代碼,我們下面分析。

好了,這一段代碼算是一個準備工作,下面我們看看上面第四部分提到的這個explode的重載方法,這裡一共需要四個參數,第一個參數是一個Bitmap,這個Bitmap根據我們傳入的View建立,第二個參數就是我們上面第一部分提到的那個Rect,第三個參數是一個啟動延遲時間,第四個參數是動畫預設的執行時間,四個參數中,後面兩個都是固定值,第二個參數我們上文已經說過,這裡我們主要看看第一個參數的獲得:

public static Bitmap createBitmapFromView(View view) {
        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);
                view.draw(canvas);
                canvas.setBitmap(null);
            }
        }
        return bitmap;
    }

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

根據我們傳入的View,我們得到一個Bitmap,如果這個View本身就是一個ImageView,那麼我們會比較容易獲得一個Bitmap,如果這個View不是ImageView,那麼我們根據這個View的大小建立一個空的Bitmap,在建立的過程中,如果發生了OOM,我們會嘗試執行一次System.gc,然後重新建立,如果還建立失敗,那麼就會傳回一個null。拿到Bitmap之後,我們把這個Bitmap設定為一個Canvas的底布,然後把View繪制在Canvas上,最後再把Canvas的底布設定為null,同時将Bitmap傳回。

經過上面的步驟,我們就可以拿到一個根據我們要粉碎的View建立的bitmap,然後我們就來看看explode的重載方法:

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

到了這裡,我們先來看看ExplosionAnimator這個類,這個類繼承自ValueAnimator,我們來看看ExplosionField的構造方法:

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

在這裡我們看到了View被粉碎的方式,一個View一共被分為15×15個Particle,通過generateParticle方法可以獲得這一個一個的Particle,每一個Particle的顔色就是bitmap上對應的顔色,這些Particle存在一個mParticles數組中,container就是我們傳進來的ExplosionField,它賦給了mContainer。我們注意到,ExplosionField重寫了start方法:

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

在start方法中調用了ExplosionField的draw方法,那我們看看ExplosionField方法中的draw方法:

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

好啊,這裡竟然又調用了ExplosionAnimator的draw方法,那我們再看看ExplosionAnimator中的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;
	}
           

在ExplosionAnimator的draw方法中,我們看到,在for循環中會将我們的15×15個Parcicle都畫出來,然後又回去調用ExplosionField中的draw方法,如此循環往複互相之間不斷調用,直到Particle的alpha屬性為0時停止調用。

看到這裡,我們對ExplosionField有了一個基本的了解,這個時候我們再回過頭看ExplosionField中的explode方法,我們注意到有一個List集合專門用來存放ExplosionAnimator,這是由于我們的一個頁面上可能同時有多個View被粉碎,每一個被粉碎的View對應一個單獨的ExplosionAnimator,當該動畫執行完了之後,就把它從List集合中移除。

關于粉碎View的事就說這麼多。

Demo下載下傳https://github.com/lenve/ExplosionFieldTest