ANDROID類比火花粒子的滑動噴射效果,android火花
轉載請註明本文出自大苞米的部落格(http://blog.csdn.net/a396901990),謝謝支援!
開篇廢話:
年前換了一個手機,SONY的Z3C。這個手機在解鎖螢幕時有一個滑動動畫,類似火花的粒子噴射,效果很炫。。。
於是嘗試著類比了一下,完成後效果如(還有很多細節沒有實現):
SurfaceView:
因為surfaceview是使用的雙緩衝機制,所以很適合繪製這種需要不停變換的畫面。
下面我從網上copy了幾條關於SurfaceView的一些特性(已經表明了出處),因為寫這個Demo的一個主要目的就是熟悉瞭解Android的繪圖機制。
SurfaceView和View最本質的區別:
摘錄自http://www.cnblogs.com/lipeil/archive/2012/08/31/2666187.html
surfaceView是在一個新起的單獨線程中可以重新繪製畫面,而View必須在UI的主線程中更新畫面。
那麼在UI的主線程中更新畫面 可能會引發問題,比如你更新畫面的時間過長,那麼你的主UI線程會被你正在畫的函數阻塞。那麼將無法響應按鍵,觸屏等訊息。
當使用surfaceView 由於是在新的線程中更新畫面所以不會阻塞你的UI主線程。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要surfaceView中 thread處理,一般就需要有一個event queue的設計來儲存touch event,這會稍稍複雜一點,因為涉及到線程同步。
所以基於以上,根據遊戲特點,一般分成兩類:
1 被動更新畫面的。比如棋類,這種用view就好了。因為畫面的更新是依賴於 onTouch 來更新,可以直接使用 invalidate。 因為這種情況下,這一次Touch和下一次的Touch需要的時間比較長些,不會產生影響。
2 主動更新。比如一個人在一直跑動。這就需要一個單獨的thread不停的重繪人的狀態,避免阻塞main UI thread。所以顯然view不合適,需要surfaceView來控制。
雙緩衝:
摘錄自http://blog.csdn.net/tobacco5648/article/details/8261749
Android中的SurfaceView在更新視圖時,為了提高更新效率,加強使用者體驗,採用了雙緩衝機制。
Android的官方說明:
Note: On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. For example, you can clear the previous state of the Canvas by filling in a color with drawColor() or setting a background image with drawBitmap(). Otherwise, you will see traces of the drawings you previously performed.
簡單理解:
在運用時可以理解為:SurfaceView在更新視圖時用到了兩張Canvas,一張frontCanvas和一張backCanvas,每次實際顯示的是frontCanvas,backCanvas儲存的是上一次更改前的視圖,當使用lockCanvas()擷取畫布時,得到的實際上是backCanvas而不是正在顯示的frontCanvas,之後你在擷取到的backCanvas上繪製新視圖,再unlockCanvasAndPost(canvas)此視圖,那麼上傳的這張canvas將替換原來的frontCanvas作為新的frontCanvas,原來的frontCanvas將切換到後台作為backCanvas。例如,如果你已經先後兩次繪製了視圖A和B,那麼你再調用lockCanvas()擷取視圖,獲得的將是A而不是正在顯示的B,之後你講重繪的C視圖上傳,那麼C將取代B作為新的frontCanvas顯示在SurfaceView上,原來的B則轉換為backCanvas。
Surface使用方法:
(摘錄自http://www.cnblogs.com/devinzhang/archive/2012/02/03/2337559.html)
1)實現步驟
a.繼承SurfaceView
b.實現SurfaceHolder.Callback介面
2)需要重寫的方法
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){} //在surface的大小發生改變時激發(2)public void surfaceCreated(SurfaceHolder holder){} //在建立時激發,一般在這裡調用畫圖的線程。(3)public void surfaceDestroyed(SurfaceHolder holder) {} //銷毀時激發,一般在這裡將畫圖的線程停止、釋放。</span>
3)SurfaceHolder
SurfaceHolder,surface的控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控製表面,大小,像素等。幾個需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);// 給SurfaceView當前的持有人一個回調對象。(2)、abstract Canvas lockCanvas();// 鎖定畫布,一般在鎖定後就可以通過其返回的畫布對象Canvas,在其上面畫圖等操作了。(3)、abstract Canvas lockCanvas(Rect dirty);// 鎖定畫布的某個地區進行畫圖等..因為畫完圖後,會調用下面的unlockCanvasAndPost來改變顯示內容。// 相對部分記憶體要求比較高的遊戲來說,可以不用重畫dirty外的其它地區的像素,可以提高速度。(4)、abstract void unlockCanvasAndPost(Canvas canvas);// 結束鎖定畫圖,並提交改變。</span>
4)總結整個過程
繼承SurfaceView並實現SurfaceHolder.Callback介面 ---->
SurfaceView.getHolder()獲得SurfaceHolder對象 ---->
SurfaceHolder.addCallback(callback)添加回呼函數---->
SurfaceHolder.lockCanvas()獲得Canvas對象並鎖定畫布---->
Canvas繪畫 ---->
SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定畫圖,並提交改變,將圖形顯示。
代碼實現:
介紹代碼前先簡單整理下思路:
1.在手指點擊處會不停的噴射火花粒子
3.粒子噴射長度和密度不同,離觸摸點越近越多,越遠越少
3.粒子會從小變大,再從大到消失
4.粒子產生後會沿著某個方向隨機運動
5.粒子會有淡淡的光暈效果並且會變換顏色
下面來逐一進行講解:
1.在手指點擊處會不停的噴射火花粒子:
要處理手指按下首先需要設定觸摸點擊監聽,解決方案就是在自訂的surfaceview中重寫onTouchEvent方法,從而擷取到手指的點擊位置。
知道了觸摸點後就需要產生火花粒子,這時候就需要使用畫筆(Paint)和畫板(Canvas)來畫出他們。
設定畫筆方法:
private void setSparkPaint() { this.mSparkPaint = new Paint(); // 開啟消除鋸齒 this.mSparkPaint.setAntiAlias(true); /* * 設定畫筆樣式為填充 Paint.Style.STROKE:描邊 Paint.Style.FILL_AND_STROKE:描邊並填充 * Paint.Style.FILL:填充 */ this.mSparkPaint.setDither(true); this.mSparkPaint.setStyle(Paint.Style.FILL); // 設定外圍模糊效果 this.mSparkPaint.setMaskFilter(new BlurMaskFilter(BLUR_SIZE, BlurMaskFilter.Blur.SOLID)); }
畫筆設定好以後以後就用這個畫筆在畫布上畫出這些粒子,這裡為了簡單都將粒子看作一個個小圓點。如下方法的作用就是在觸摸點迴圈畫出這些小圓
// 迴圈繪製所有火花 for (int[] n : sparks) { n = sparkManager.drawSpark(mCanvas, (int) X, (int) Y, n); }
經過一些列計算後調用canvas畫圓的方法來畫出粒子:
// 畫花火 canvas.drawCircle(bezierPoint.x, bezierPoint.y, radius, mSparkPaint);
2.粒子噴射長度和密度不同,離觸摸點越近越多,越遠越少
長度,和密度這兩個可以通過隨即函數來解決。
mDistance = getRandom(SparkView.WIDTH / 4, mRandom.nextInt(15)) + 1;
3.粒子會從小變大,再從大到消失:
之前嘗試過根據粒子存在時間改變透明度的方法,但效果不好。
所以直接調整粒子的大小來更好。但需要兩個階段:
第一階段,粒子小圓的半徑從0到最大。
第二階段,粒子小圓的半徑從最大到0。
只需要動態計算出半徑,最後將半徑傳遞到canvas.drawCircle中就可以完成這個效果。
因為半徑的值需要均勻的增加,我的思路是通過路徑長度和當前走過長度的比值再乘以一個速率得出:
/** * 更新火花路徑 */ private void updateSparkPath() { mCurDistance += PER_SPEED_SEC; // 前半段 if (mCurDistance < (mDistance / 2) && (mCurDistance != 0)) { radius = SPARK_RADIUS * (mCurDistance / (mDistance / 2)); } // 後半段 else if (mCurDistance > (mDistance / 2) && (mCurDistance < mDistance)) { radius = SPARK_RADIUS - SPARK_RADIUS * ((mCurDistance / (mDistance / 2)) - 1); } // 完成 else if (mCurDistance >= mDistance) { mCurDistance = 0; mDistance = 0; radius = 0; } }
4.粒子產生後會沿著某個方向隨機運動
這個就簡單了,相信大家肯定能想到就是——賽貝爾曲線(關於賽貝爾曲線推薦參考維基百科)。
粒子路徑可以通過一條4點的賽貝爾曲線類比,如:
我在上網找了一個函數可以求出賽貝爾曲線在某時間比下的點:
/** * 計算塞貝兒曲線 * * @param t 時間,範圍0-1 * @param s 起始點 * @param c1 拐點1 * @param c2 拐點2 * @param e 終點 * @return 塞貝兒曲線在目前時間下的點 */ private Point CalculateBezierPoint( float t, Point s, Point c1, Point c2, Point e ) { float u = 1 - t; float tt = t * t; float uu = u * u; float uuu = uu * u; float ttt = tt * t; Point p = new Point((int) (s.x * uuu), (int) (s.y * uuu)); p.x += 3 * uu * t * c1.x; p.y += 3 * uu * t * c1.y; p.x += 3 * u * tt * c2.x; p.y += 3 * u * tt * c2.y; p.x += ttt * e.x; p.y += ttt * e.y; return p; }將計算後的點傳入畫圓函數中:
// 計算塞貝兒曲線的當前點 Point bezierPoint = CalculateBezierPoint(mCurDistance / mDistance, start, c1, c2, end); // 畫花火 canvas.drawCircle(bezierPoint.x, bezierPoint.y, radius, mSparkPaint);
5.粒子會有淡淡的光暈效果並且會變換顏色
淡淡的發光和變換顏色都是通過畫筆設定的:
淡淡發光:
this.mSparkPaint.setMaskFilter(new BlurMaskFilter(BLUR_SIZE, BlurMaskFilter.Blur.SOLID));
但需要關閉硬體加速:
// 關閉硬體加速 setLayerType(LAYER_TYPE_SOFTWARE, null);
設定隨機變換顏色:
// 設定隨機顏色 mSparkPaint.setColor(Color.argb(255, mRandom.nextInt(128) + 128, mRandom.nextInt(128) + 128, mRandom.nextInt(128) + 128));
總結:
大致邏輯已經介紹完畢,還有很多細節沒有實現,並且發現有輕微的閃爍現象,但沒有找到很好的解決辦法(求大神指點)。。。
Github下載串連:
https://github.com/a396901990/SparkScreen/tree/master
最後推薦兩篇關於SurfaceView和畫圖的文章:
如果通過SurfaceVIew做遊戲Create a SurfaceView Game step-by-step
愛哥的Android自訂控制項其實很簡單系列