鴻蒙開源第三方組件—粒子破碎效果組件Azexplosion_ohos
前言
基于安卓平臺的粒子破碎效果組件Azexplosion(https://github.com/Xieyupeng520/AZExplosion), 實現了鴻蒙的功能化遷移和重構。代碼已經開源到(https://gitee.com/isrc_ohos/azexplosion_ohos),歡迎各位開發者下載使用并提出寶貴意見!
背景
Azexplosion_ohos是一個實現粒子破碎動畫效果的組件,用戶可以通過點擊手機屏幕上的破碎對象(一般是指手機屏幕上顯示的圖片或文字),來達到將該對象破碎的效果。該組件可以設置手機屏幕上的對象是否具有破碎效果,同時也可以更換破碎對象和破碎對象的背景。Azexplosion_ohos組件視覺效果突出、使用方便、可擴展性強,與小米手機刪除APP時的動態效果類似。
組件效果展示
組件應用僅包含一個主界面,在界面中存在圖片和文字兩種破碎對象。當手指觸碰圖片(或文字)時,該圖片(或文字)在視覺上呈現破碎效果,且破碎粒子的顏色與原圖片(或文字)的顏色相對應,如圖1所示,效果看起來很解壓,非常好用~

圖1 破碎效果展示
Sample解析
Azexplosion_ohos組件的核心功能主要被封裝在Library中,Sample的功能實現很簡潔,只需要構建整體的布局,并調用Library提供的監聽接口為整體顯示布局設置監聽,即可實現效果執行的對象的破碎效果,具體的實現步驟如下:
步驟1. 創建布局。
步驟2. 設置整體顯示布局。
步驟3. 導入相關類并實例化對象。
步驟4. 為整體顯示布局設置監聽。
接下來我們來看一下每一個步驟涉及的詳細操作。
(1)創建布局
首先在XML文件中創建一個DirectionalLayout的布局,寬度和高度都跟隨父控件變化而調整。后在DirectionalLayout加入需要的破碎對象,可以是圖片或文字,如圖1所示,代碼如下所示。
- <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:id="$+id:root"
- ohos:width="match_parent"
- ohos:height="match_parent"
- ohos:orientation="vertical">
- <Text
- ohos:width="match_content" //文字破碎對象
- ohos:text="破碎效果"
- ohos:text_size="60vp"
- ohos:top_margin="10vp"
- ohos:left_margin="20vp"
- ohos:bottom_margin="15vp"
- ohos:right_padding="10vp"
- ohos:left_padding="40vp"
- ohos:height="match_content"/>
- <DirectionalLayout ohos:id="$+id:group1"
- ohos:width="match_parent"
- ohos:height="100vp"
- ohos:top_margin="10vp"
- ohos:orientation="horizontal">
- <Image //圖片破碎對象
- ohos:id="$+id:qq"
- ohos:width="match_content"
- ohos:height="match_content"
- ohos:image_src="$media:qq"
- ohos:left_margin="25vp"
- ohos:right_margin="25vp"
- ohos:top_margin="15vp"/>
- ......
(2)設置整體顯示布局
在MainAbility文件的onStart()方法中,通過setUIContent為應用設置整體顯示布局,將步驟(1)中的布局設置為應用的主界面布局。為了顯示的美觀性,可以通過setBackground設置主界面的背景顏色。
- //directionalLayout 指向步驟(1)中的布局
- DirectionalLayout directionalLayout = (DirectionalLayout) LayoutScatter.getInstance(this).parse(ResourceTable.Layout_mian_activity, null, false);
- ShapeElement element = new ShapeElement();
- element.setRgbColor(new RgbColor(255,239,213));
- directionalLayout.setBackground(element); //背景顏色設置
- super.setUIContent(directionalLayout); //設置顯示布局
(3) 導入相關類并實例化對象
在MainAbility中,通過import關鍵字導入Library中的ExplosionField 類,并在onStart()方法中實例化ExplosionField類對象。
- ExplosionField explosionField = new ExplosionField(this);
(4)為整體顯示布局設置監聽
調用ExplosionField類對象的內部方法addListener,用于為整體顯示布局設置監聽。整體顯示布局設置監聽后,用戶點擊布局內某個組件,組件就會出現破碎現象。
- explosionField.addListener((ComponentContainer)findComponentById(ResourceTable.Id_root));
Library解析
在Sample中介紹了為整體顯示布局設置監聽后,點擊布局內的組件就會出現破碎現象。本節,我們來講組件破碎現象形成的詳細原理。
先來看看Azexplosion_ohos組件的Library組成結構,如圖2所示,該部分主要由三個類組成,分別是ExplosionAnimator、ExplosionField、Particle。ExplosionAnimator主要用于生成粒子并執行粒子破碎動畫,改變不同時刻的粒子狀態;ExplosionField主要功能負責粒子集的畫布顯示;Particle類主要用于描述粒子的顏色、透明度等屬性。

圖2 Library項目結構
下面我們介紹Library內部邏輯的執行步驟。當用戶點擊破碎對象后,Library負責生成破碎對象對應的矩陣圖像(PixelMap),然后把矩陣圖像分解成若干個粒子,最后再讓粒子動起來形成破碎的動畫,具體流程如圖3所示 。

圖3 Library內部邏輯的執行步驟
1、圖像或文字轉換成PixelMap
為整體顯示布局添加listener后,會通過for循環的方式,為每一個布局內的組件添加點擊監聽。在getOnClickListener()方法中,會繼續調用ExplosionField類createBitmapFromView()方法,而createBitmapFromView()方法就是完成圖像或文字轉換成PixelMap(位圖)的關鍵方法。
在createBitmapFromView()方法中首先會創建一個100*100的空的PixelMap,若破碎對象為圖片,則通過調用ExplosionField類的getPixelMap()方法獲取Image的PixelMap;若破碎對象為文字,則直接返回空的PixelMap。此時,圖片的PixelMap自帶圖片原本的像素信息,而由于文字得到的PixelMap是空的,所以默認顯示黑色的破碎效果。
- //為每一個布局內的組件添加點擊監聽
- public void addListener(Component view) {
- if (view instanceof ComponentContainer) {
- ComponentContainer viewGroup = (ComponentContainer) view;
- int count = viewGroup.getChildCount();
- // 逐個取出布局內的破碎對象
- for (int i = 0 ; i < count; i++) {
- addListener(viewGroup.getComponentAt(i));
- }
- } else {
- //為每一個破碎對象設置點擊監聽
- view.setClickable(true);
- view.setClickedListener(getOnClickListener());
- }
- }
- //將每一個破碎對象轉換為PixelMap
- private PixelMap createBitmapFromView(Component view) {
- //PixelMap參數初始化操作
- PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
- options.size = new Size(100,100);
- //創建位圖對象
- PixelMap = PixelMap.create(options);
- if(view.getName().equals("Id_qq")){
- bitmap =getPixelMap(ResourceTable.Media_qq); //qq的PixelMap
- }
- if(view.getName().equals("Id_qzone"))
- bitmap =getPixelMap(ResourceTable.Media_qzone); //qzone的PixelMap
- if(view.getName().equals("Id_vx"))
- bitmap =getPixelMap(ResourceTable.Media_vx); //微信的PixelMap
- ......
- return bitmap; //將獲取的PixelMap返回
- }
2、生成破碎粒子
生成破碎粒子是ExplosionAnimator的功能之一,主要是對來自ExplosionField類的PixelMap進行處理。首先根據PixelMap的寬高,算出橫豎粒子的個數。然后計算出粒子所在位置的顏色。接著調用Particle類的generateParticle()方法生成粒子,生成的破碎粒子如圖4所示。
- //生成粒子
- private Particle[][] generateParticles(PixelMap bitmap, Rect bound) {
- int w = bound.getWidth(); //PixelMap的寬
- int h = bound.getHeight(); // PixelMap的高
- int partW_Count = w / Particle.PART_WH; //橫向粒子個數
- int partH_Count = h / Particle.PART_WH; //豎向粒子個數
- //粒子的寬
- int bitmap_part_w = bitmap.getImageInfo().size.width / partW_Count;
- //粒子的高
- int bitmap_part_h = bitmap.getImageInfo().size.height / partH_Count;
- //粒子矩陣
- Particle[][] particles = new Particle[partH_Count][partW_Count];
- Point point = null;
- for (int row = 0; row < partH_Count; row ++) { //行
- for (int column = 0; column < partW_Count; column ++) { //列
- //取得當前粒子所在位置的顏色
- int color = bitmap.readPixel(new Position(column* bitmap_part_w, row * bitmap_part_h));
- point = new Point(column, row); //x是列,y是行
- particles[row][column] = Particle.generateParticle(color, bound, point);
- }
- }
- return particles; //返回粒子矩陣
- }

圖4 破碎粒子效果圖
3、執行破碎動畫
接下來我們需要為粒子加上動畫,讓它們動起來,實現一個完整的動態效果。動畫效果的實現需要依賴ExplosionAnimator 類,ExplosionAnimator 類繼承自AnimatorValue類,可用于繪制動畫。下面我們具體分析動畫效果是如何實現的。
(1)創建ExplosionAnimator 類對象
在創建ExplosionAnimator 類對象的過程中,將被點擊的破碎對象的PixelMap作為參數傳入,得到ExplosionAnimator 類對象的成員變量包含上述PixelMap生成的粒子集。
- //創建元素為列表ExplosionAnimator類對象的數組列表
- private ArrayList<ExplosionAnimator> explosionAnimators;
- explosionAnimators = new ArrayList<ExplosionAnimator>();
- //創建ExplosionAnimator 類對象
- final ExplosionAnimator animator = new ExplosionAnimator(this, createBitmapFromView(view), rect);
- //ExplosionAnimator 類對象添加到列表中
- explosionAnimators.add(animator);
(2)start()
當監聽器監聽到屏幕被觸碰時,通過(1)中創建的ExplosionAnimator 類對象調用start() 方法,通過invalidate()來刷新將要破碎的圖片所對應的區塊,invalidate()方法會調用onDraw()方法進行動畫繪制。
- public void start() {
- super.start();
- mContainer.invalidate();
- }
(3)onDraw()
在onDraw()方法里,首先保存畫布的繪制狀態并修正因為狀態欄導致的錯位,然后循環調用ExplosionAnimator 的draw()方法。
- public void onDraw(Component component, Canvas canvas) {
- canvas.save(); // 保存畫布的繪制狀態
- canvas.translate(0,positions[1]); //修正因為狀態欄導致的錯位
- for (ExplosionAnimator animator : explosionAnimators) {
- animator.draw(canvas);
- }
- canvas.restore();
- }
(4)draw()
在draw方法中,每次繪制都調用一次advance()方法讓粒子“前進一步”(逐漸向下擴散),然后設置畫筆的新屬性并重新繪制。
- public void draw(Canvas canvas) {
- //動畫結束時停止
- if(!isRunning()) {
- return;
- }
- for (Particle[] particle : mParticles) {
- for (Particle p : particle) {
- p.advance(myvalue);
- mPaint.setColor(new Color(p.color));
- //只是這樣設置,透明色會顯示為黑色
- mPaint.setAlpha((int) (p.alpha));
- canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);
- }
- }
- mContainer.invalidate();
- }
最后,我們總結一下整體的動畫繪制的過程是如何實現的。首先在ExplosionField中調用ExplosionAnimator的start()方法開啟動畫,start()方法中會調用invalidate()方法來使ExplosionField重繪(調用onDraw方法)。
onDraw方法調用draw方法,draw方法中也使用invalidate強制ExplosionField重繪(調用onDraw方法),每一次循環完成一次重繪。 這樣兩者相互調用,不停地刷新,直到所有粒子都繪制完成,刷新停止,動畫繪制流程如圖5所示。

圖5 動畫繪制流程