多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > [置頂] 【騰訊bugly干貨分享】Android自繪動畫實現與優化實戰——以Tencent OS錄音機波形動

[置頂] 【騰訊bugly干貨分享】Android自繪動畫實現與優化實戰——以Tencent OS錄音機波形動

來源:程序員人生   發布時間:2016-08-26 09:25:30 閱讀次數:2982次

本文來自于騰訊bugly社區,原文地址為:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1180

前言

我們所熟知的,Android 的圖形繪制主要是基于 View 這個類實現。 每一個 View 的繪制都需要經過 onMeasure、onLayout、onDraw 3步曲,分別對應到丈量大小、布局、繪制。

Android 系統為了簡化線程開發,下降利用開發的難度,將這3個進程都放在利用的主線程(UI 線程)中履行,以保證繪制系統的線程安全。

這3個進程通過1個叫 Choreographer 的定時器來驅動調用更新, Choreographer 每16ms被 vsync 這個信號喚醒調用1次,這有點類似初期的電視機刷新的機制。在 Choreographer 的 doFrame 方法中,通過樹狀結構存儲的 ViewGroup,順次遞歸的調用到每一個 View 的 onMeasure、onLayout、onDraw 方法,從而最后將每一個 View 都繪制出來(固然最后還會經過 SurfaceFlinger 的類來將 View 合成起來顯示,實際進程很復雜)。

同時每一個 View 都保存了很多標記值 flag,用來判斷是不是該 View 需要重新被 Measure、Layout、Draw。 這樣對那些沒有變化,不需要重繪的 View,則不再調用它們的方法,從而能夠提高繪制效力。

Android 為了方便開發者進行動畫開發,提供了好幾種動畫實現的方式。 其中比較經常使用的是屬性動畫類(ObjectAnimator),它通過定時以1定的曲線速率來改變 View 的1系列屬性,最后產生 View 的動畫的效果。比較常見的屬性動畫能夠動態的改變 View 的大小、色彩、透明度、位置等值,此種方式實現的效力比較高,也是官方推薦的動畫情勢。

為了進1步的提升動畫的效力,避免每次都需要屢次調用 onMeasure、onLayout、onDraw,重新繪制 View 本身。 Android 還提出了1個層 Layer 的概念。

通過將 View 保存在圖層中,對平移、旋轉、伸縮等動畫,只需要對該層進行整體變化,而不再需要重新繪制 View 本身。 層 Layer 又分為軟繪層(Software Layer)和硬繪層(Harderware Layer) 。它們可以通過 View 類的 setLayerType(layerType, paint);方法進行設置。軟繪層將 View 存儲成 bitmap,它會占用普通內存;而硬繪層則將 View 存儲成紋理(Texture),占用 GPU 中的存儲。 需要注意的是,由于將 View 保存在圖層中,都會占用相應的內存,因此在動畫結束以后需要重新設置成LAYER_ TYPE_ NONE,釋放內存。

由于普通的 View 都處于主線程中,Android 除繪制以外,在主線程中還需要處理用戶的各種點擊事件。很多情況,在主線程中還需要運行額外的用戶處理邏輯、輪詢消息事件等。 如果主線程過于繁忙,不能及時的處理和響利用戶的輸入,會讓用戶的體驗急劇下降。如果更嚴重的情況,當主線程延遲時間到達5s的時候,還會觸發 ANR(Application Not Responding)。 這樣當界面的繪制和動畫比較復雜,計算量比較大的情況,就不再合適使用 View 這類方式來繪制了。

Android 斟酌到這類場景,提出了 SurfaceView 的機制。SurfaceView 能夠在非 UI 線程中進行圖形繪制,釋放了 UI 線程的壓力。SurfaceView 的使用方法1般是復寫1下3種方法:

   public void surfaceCreated(SurfaceHolder holder);
   public void surfaceChanged(SurfaceHolder holder, int format, int width,
                              int height);
   public void surfaceDestroyed(SurfaceHolder holder);

surfaceCreated 在 SurfaceView 被創建的時候調用, 1般在該方法中創建繪制線程,并啟動這個線程。

surfaceDestroyed 在 SurfaceView 被燒毀的時候調用,在該方法中設置標記位,讓繪制線程停止運行。

繪制子線程中,1般是1個 while 循環,通過判斷標記位來決定是不是退出該子線程。 使用 sleep 函數來定時的調起繪制邏輯。 通過 mHolder.lockCanvas()來取得 canvas,繪制終了以后調用 mHolder.unlockCanvasAndPost(canvas);來上屏。 這里特別要注意繪制線程和 surfaceDestroyed 中需要加鎖。否則會有 SurfaceView 被燒毀了,但是繪制子線程中還是持有對 Canvas 的援用,而致使 crash。下面是1個經常使用的框架:

private final Object mSurfaceLock = new Object();
private DrawThread mThread;
@Override
public void surfaceCreated(SurfaceHolder holder) {
    mThread = new DrawThread(holder);
    mThread.setRun(true);  
    mThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
                           int height) {
    //這里可以獲得SurfaceView的寬高等信息
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    synchronized (mSurfaceLock) {  //這里需要加鎖,否則doDraw中有可能會crash
        mThread.setRun(false);
    }
}

private class DrawThread extends Thread {
    private SurfaceHolder mHolder;
    private boolean mIsRun = false;

    public DrawThread(SurfaceHolder holder) {
        super(TAG);
        mHolder = holder;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (mSurfaceLock) {
                if (!mIsRun) {
                    return;
                }
                Canvas canvas = mHolder.lockCanvas();
                if (canvas != null) {
                    doDraw(canvas);  //這里做真正繪制的事情
                    mHolder.unlockCanvasAndPost(canvas);
                }
            }
            Thread.sleep(SLEEP_TIME);
        }
    }

    public void setRun(boolean isRun) {
        this.mIsRun = isRun;
    }
}

Android 為繪制圖形提供了 Canvas 類,可以理解這個類是1塊畫布,它提供了在畫布上畫不同圖形的方法。它提供了1系列的繪制各種圖形的 API, 比如繪制矩形、圓形、橢圓等。對應的 API 都是 drawXXX的情勢。

不規則的圖形的繪制比較特殊,它同于規則圖形已有繪制公式的情況,它有多是任意的線條組成。Canvas 為畫不規則形狀,提供了 Path 這個類。通過 Path 能夠記錄各種軌跡,它可以是點、線、各種形狀的組合。通過 drawPath 這個方法便可繪制出任意圖形。

有了畫布 Canvas 類,提供了繪制各種圖形的工具以后,還需要指定畫筆的色彩,樣式等屬性,才能有效的繪圖。Android 提供了 Paint 這個類,來抽象畫筆。 通過 Paint 可以指定繪制的色彩,是不是填充,如果處理交集等屬性。

動畫實現

既然是實戰,固然要有1個例子啦。 這里以 TOS 里面的錄音機的波形動效實現為例。 首先看1下設計獅童鞋給的視覺設計圖:

下面是動起來的效果圖:

看到這么高大上的動效圖,不能不贊嘆1下設計獅童鞋,但同時也深深的捏了把汗——這個動畫要咋實現捏。

粗略的看1下上面的視覺圖。 感覺像是多個正弦曲線組成。 每條正弦線好像中間高,兩邊低,應當有1個對稱的衰減系數。 同時有兩組上下對稱的正弦線,在對稱的正弦線中間采取漸變色彩來進行填充。然后看動效的效果圖,好像這個不規則的正弦曲線有1個固定的速率向前在運動。

看來為了實現這個動效圖,還得把都已還給老師的那點可憐的數學知識撿起來。下面是正弦曲線的公式:

y=Asin(ωx+φ)+k

A 代表的是振幅,對應的波峰和波谷的高度,即 y 軸上的距離;ω 是角速度,換成頻率是 2πf,能夠控制波形的寬度;φ 是初始相位,能夠決定正弦曲線的初始 x 軸位置;k 是偏距,能夠控制在 y 軸上的偏移量

為了能夠更加直觀,將公式圖形化的顯示出來,這里強烈推薦1個網站:https://www.desmos.com/calculator ,它能將輸入的公式轉換成坐標圖。這正是我們需要的。比如 sin(0.75πx - 0.5π) 對應的圖形是下圖:

與上面設計圖中的相比,還需要乘上1個對稱的衰減函數。 我們挑選了以下的衰減函數425/(4+x4):

將sin(0.75πx - 0.5π) 乘以這個衰減函數 425/(4+x4),然后乘以0.5。 最后得出了下圖:

看起來這個曲線與視覺圖中的曲線已很像了,不過就是多加幾個算法類似,但是相位不同的曲線罷了。 以下圖:

看看,用了我們足(quan)夠(bu)強(wang)大(ji)的數學知識以后, 我們好像也創造出來了類似視覺稿中的波形了。

接下來,我們只需要在 SurfaceView 中使用 Path,通過上面的公式計算出1個個的點,然后畫直線連接起來就行啦! 因而我們得出了下面的實際效果(為了方便顯示,已將背景調成白色):

曲線畫出來了,然后要做的就是漸變色的填充了。 這也是視覺還原比較難實現的地方。

對漸變填充,Android 提供了 LinearGradient 這個類。它需要提供起始點和終結點的坐標,和起始點和終結點的色彩值:

public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
             TileMode tile);

TileMode 包括了 CLAMP、REPEAT、MIRROR 3種模式。 它指定了,如果填充的區域超過了起始點和終結點的距離,色彩重復的模式。CLAMP 指使用終點邊沿的色彩,REPEAT 指重復的漸變,而MIRROR則指的是鏡像重復。

從 LinearGradient 的構造函數就能夠預知,漸變填充的時候,1定要指定精確的起始點和終結點。否則如果漸變距離大于填充區域,會出現漸變不完全,而漸變距離小于填充區域則會出現多個漸變或填不滿的情況。以下圖所示:

圖中左側是精確設置漸變出發點和終點為矩形的頂部和底部; 圖中中間為設置的漸變出發點為頂部,終點為矩形的中間; 右側的則設置的漸變出發點和終點都大于矩形的頂部和底部。代碼以下:

LinearGradient gradient = new LinearGradient(100, mHeight_2 - 200, 100, mHeight_2 + 200,
        line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(100, mHeight_2 - 200, 300, mHeight_2 + 200, mPaint);

gradient = new LinearGradient(400, mHeight_2 - 200, 400, mHeight_2,
         line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(400, mHeight_2 - 200, 600, mHeight_2 + 200, mPaint);

gradient = new LinearGradient(700, mHeight_2 - 400, 700, mHeight_2 + 400,
        line_1_start_color, region_1_end_color, Shader.TileMode.REPEAT);
mPaint.setShader(gradient);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(700, mHeight_2 - 200, 900, mHeight_2 + 200, mPaint);

對矩形這類規則圖形進行漸變填充,能夠很容易設置漸變色彩的出發點和終點。 但是對上圖中的正弦曲線如果做到呢? 難道需要將1組正弦曲線的每一個點上下連接,使用漸變進行繪制? 那樣計算量將會是非常巨大的!那又有其他甚么好的方法呢?

Paint 中提供了 Xfermode 圖象混合模式的機制。 它能夠控制繪制圖形與之前已存在圖形的混合交疊模式。其中比較有用的是 PorterDuffXfermode 這個類。它有多種混合模式,以下圖所示:

這里 canvas 原本的圖片可以理解為背景,就是 dst; 新畫上去的圖片可以理解為前景,就是 src。有了這類圖形混合技術,能夠完成各種圖形交集的顯示。

那我們是不是可以腦洞大開1下,將上圖已繪制好的波形圖,與漸變的矩形進行交集,將它們相交的地方畫出來呢。 它們相交的地方好像恰好就是我們需要的效果呢。

這樣,我們只需要先填充波形,然后在每組正弦線相交的封閉區域畫1個以波峰和波谷為高的矩形,然后將這個矩形染色成漸變色。以這個矩形與波形做出交集,選擇 SrcIn 模式,即能只顯示相交部份矩形的這1塊的色彩。 這個方案看起來可行,先試試。下面圖是沒有履行 Xfermode 的疊加圖, 從圖中可以看出,兩個正弦線中間的區域正是我們需要的!

下面是履行 SrcIn 模式混合以后的圖象:

奇異的事情出現了, 視覺圖中的效果被還原了。

我們再依葫蘆畫瓢,再繪制另外1組正弦曲線。 這里需要注意的是,由于 Xfermode 中的 Dst 指的原本的背景,因此這里兩組正弦線的混合會相互產生影響。 即第2組在調用 SrcIn 模式進行混合的時候,會將第1組的圖形進行剪切。以下圖所示:

因此在繪制的時候,必須將兩組正弦曲線分開單獨繪制在不同 Canvas 層上。 好在 Android 系統為我們提供了這個功能,Android 提供了不同 Canvas 層,以用于進行離屏緩存的繪制。我們可以先繪制1組圖形,然后調用 canvas.saveLayer 方法將它存在離屏緩存中,然后再繪制另外1組曲線。最后調用 canvas.restoreToCount(sc);方法恢復 Canvas,將兩屏混合顯示。最后的效果圖以下所示:

這里總結1下繪制的順序:
1、計算出曲線需要繪制的點
2、填充出正弦線
3、在每組正弦線相交的地方,根據波峰波谷繪制出1個漸變線填充的矩形。并且設置圖形混合模式為 SrcIn

 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 

4、對正弦線進行描邊
5、離屏存儲 Canvas,再進行下1組曲線的繪制

靜態的繪制已完成了。接下來就是讓它動起來了。 根據上面給出來的框架,在繪制線程中會定時履行 doDraw 方法。我們只需要在 doDraw 方法中每次將波形往前移動1個距離,便可到達讓波形往前移動的效果。具體對應到正弦公式中的 φ 值,每次只需要在原有值的基礎上修改這個值即能改變波形在 X 軸的位置。每次履行 doDraw 都會根據下面的計算方法重新計算圖形的初相值:

this.mPhase = (float) ((this.mPhase + Math.PI * mSpeed) % (2 * Math.PI));

在計算波形高度的時候,還可以乘以音量大小。即正弦公式中的 A 的值可以為 volume * 繪制的最大高度 * 425/(4+x4)。 這樣波形的振幅即能與音量正相干。波形可以隨著音量跳動大小。

動畫的優化

雖然上面已實現了波形的動畫。但是如果以為工作已結束了,那就真是太 sample,naive了。

現在手機的分辨率變的愈來愈大,1般都是1080p的分辨率。隨著分辨率的增加,圖形繪制所需要的計算量也愈來愈大(像素點多了)。這樣致使在某些低端手機中,或某些偽高端手機(比如某星S4)中,CPU 的計算能力不足,從而致使動畫的卡頓。 因此對自繪動畫,可能還需要不斷的進行代碼和算法的優化,提高繪制的效力,盡可能減少計算量。

自繪動畫優化的終究目的是減少計算量,下降 CPU 的負擔。為了到達這個目的,筆者總結歸納了以下幾種方法,如果大家有更多更好的方法,歡迎分享:

1、下降分辨率

在實際動畫繪制的進程中,如果對每一個像素點的去計算(x,y)值,會致使大量的計算。但是這類密集的計算常常都是不需要的。 對動畫,人的肉眼是有1定的容忍度的,在1定范圍內的圖形失真是沒法發覺的,特別是那種1閃而過的東西更是如此。 這樣在實現的時候,可以都自己擬定1個比實際分辨率小很多的圖形密度,這個圖形密度上來計算 Y 值。然后將我們自己定義的圖形密度成比例的映照到真實的分辨率上。 比如上面繪制正弦曲線的時候,我們完全可以只計算100個點。然后將這60個點成比例的放在1024個點的X軸上。 這樣我們1下子便減少了接近10倍的計算量。這有點類似柵格化1副圖片。

由于采取了低密度的繪制,將這些低密度的點用直線連接起來,會產生鋸齒的現象,這樣一樣會對體驗產生影響。但是別怕,Android 已為我們提供了抗鋸齒的功能。在 Paint 類中便可進行設置:

mPaint.setAntiAlias(true);

使用 Android 優化過了的抗鋸齒功能,1定會比我們每一個點的去繪制效力更高。

通過動態調理自定義的繪制密度,在繪制密度與終究實現效果中找到1個平衡點(即不影響最后的視覺效果,同時還能最大限度的減少計算量),這個是最直接,也最簡單的優化方法。

2、減少實時計算量

我們知道在過去嵌入式裝備中計算資源都是相當有限的,運行的代碼常常需要優化,乃至有時候需要在匯編級別進行。雖然現在手機中的處理器已愈來愈強大,但是在處理動畫這類短時間間隔的大量運算,還是需要仔細的編寫代碼。 1般的動畫刷新周期是16ms,這也意味著動畫的計算需要盡量的少做運算。

只要能夠減少實時計算量的事情,都應當是我們應當做的。那末如何才能做到盡可能少做實時運算呢? 1個比較重要的思惟和方法是利用用空間來換取時間。1般我們在做自繪動畫的時候,會需要做大量的中間運算。而這些運算有可能在每次繪制定時到來的時候,產生的結果都是1樣的。這也意味著有可能我們重復的做出了需要冗余的計算。 我們可以將這些中間運算的結果,存儲在內存中。這樣下次需要的時候,便不再需要重新計算,只需要取出來直接使用便可。 比較經常使用的查表法即便利用這類空間換時間的方法來提高速度的。

具體針對本例而言, 在計算 425/(4+x4) 這個衰減系數的時候,對每一個 X 軸上固定點來講,它的計算結果都是相同的。 因此我們只需要將每一個點對應的 y 值存儲在1個數組中,每次直接從這個數組中獲得便可。這樣能夠節省出很多 CPU 在計算乘方和除法運算的計算量。 一樣道理,由于 sin 函數具有周期性,因此我們只需要將這個周期中的固定 N 個點計算出值,然后存儲在數組中。每次需要計算 sin 值的時候,直接從之前已計算好的結果中找出近似的那個就能夠了。 固然其實這里計算 sin 不需要我們做這樣的優化,由于 Android 系統提供的 Math 方法庫中計算 sin 的方法肯定已應用類似的原理優化過了。

CPU 1般都有1個特點,它在快速的處理加減乘運算,但是在處理浮點型的除法的時候,則會變的特別的慢,多要多個指令周期才能完成。因此我們還應當努力減少運算量,特別是浮點型的除法運算。 1般比較通用的做法是講浮點型的運算轉換成整型的運算,這樣對速度的提升也會比較明顯。 但是整型運算同時也意味著會丟失數據的精確度,這樣常常會致使繪制出來的圖形有鋸齒感。 之前有同事便遇到即便采取了 Android 系統提供的抗鋸齒方法,但是繪制出來的圖形鋸齒感還是很強烈,有可能就是數值計算中的精確度的問題,比如采取了不正確的整型計算,或毛病的4舍5入。 為了保證精確度,同時還能使用整型來進行運算,常??梢詫⑿枰嬎愕膮担y1乘上1個精確度(比如乘以100或1000,視需要的精確范圍而定)取整計算,最后再將結果除以這個精確度。 這里還需要注意整型溢出的問題。

3、減少內存分配次數

Android 在內存分配和釋放方面,采取了 JAVA 的垃圾回收 GC 模式。 當分配的內存不再使用的時候,系統會定時幫我們自動清算。這給我們利用開發帶來了極大的便利,我們從此不再需要過量的關注內存的分配與回收,也因此減少很多內存使用的風險。但是內存的自動回收,也意味著會消耗系統額外的資源。1般的 GC 進程會消耗系統ms級別的計算時間。在普通的場景中,開發者無需過量的關心內存的細節。但是在自繪動畫開發中,卻不能疏忽內存的分配。

由于動畫1般由1個16ms的定時器來進行驅動,這意味著動畫的邏輯代碼會在短時間內被循環往復的調用。 這樣如果在邏輯代碼中在堆上創建過量的臨時變量,會致使內存的使用量在短時間穩步上升,從而頻繁的引發系統的GC行動。這樣無疑會拖累動畫的效力,讓動畫變得卡頓。

處理分析內存分配,減少沒必要要的分配呢, 首先我們需要先分析內存的分配行動。 對Android內存的使用情況,Android Studio提供了很好用,直觀的分析工具。 為了更加直觀的表現內存分配的影響,在程序中故意創建了1些比較大的臨時變量。然后使用Memory Monitor工具得到了下面的圖:

并且在log中看到有頻繁的打印D/dalvikvm: GC_FOR_ALLOC freed 3777K, 18% free 30426K/36952K, paused 33ms, total 34ms

圖中每次漲跌的鋸齒意味著產生了1次GC,然后又分配了多個內存,這個進程不斷的往復。 從log中可以看到系統在頻繁的發起GC,并且每次GC都會將系統暫停33ms,這固然會對動畫造成影響。 固然這個是測試的比較極真個情況,1般來講,如果內存被更加穩定的使用的話,觸發GC的幾率也會大大的下降,上面圖中的顛簸鋸齒出現到幾率也會越低。

上面內存使用的情況,也被稱為內存抖動,它除在周期性的調用進程中出現,另外1個多發場景是在for循環中分配、釋放內存。它影響的不單單是自繪動畫中,其他場景下也需要盡可能避免。

從上圖中可以直觀的看到內存在1定時間段內分配和釋放的情況,得出是不是內存的使用是不是安穩。但是當出現問題以后,我們還需要借助 Allocation Tracker 這個工具來追蹤問題產生的緣由,并最后解決它。Allocation Tracker 這個工具能夠幫助我們追蹤內存對象的分配和釋放情況,能夠獲得內存對象的來源。比如上面的例子,我們在1段時間內進行追蹤,可以得到以下圖:

從圖中我們可以看到大部份的內存分配都來自線程18 Thread 18,這也是我們的動畫的繪制線程。 從圖中可以看到主要的內存分配有以下幾個地方:
1、我們故意創建的臨時大數組
2、來自 getColor 函數, 它來自對 getResources().getColor()的調用,需要獲得從系統資源中獲得色彩資源。這個方法中會創建多個 StringBuilder 的變量
3、創建 Xfermode 的臨時變量,來自 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 這個調用。

4、創建漸變值的 LinearGradient gradient = new LinearGradient(getXPos(startX), startY, getXPos(startX), endY,
gradientStartColor, gradientEndColor, Shader.TileMode.REPEAT);

對第2、3,這些變量完全不需要每次循環履行的時候,重復創建變量。 由于每次他們的使用都是固定的??梢哉遄脤⑺鼈儚呐R時變量轉為成員變量,在動畫初始化的同時也將這些成員變量初始化好。需要的時候直接調用便可。

而對第4類這樣的內存分配,由于每次動畫中的波形形狀都不1樣,因此漸變色必現得重新創建并設值。因此這里其實不能將它作為成員變量使用。這里是屬于必須要分配的。好在這個對象也不大,影響很小。

對那些沒法避免,每次又必須分配的大量對象,我們還能夠采取對象池模型的方式來分配對象。對象池來解決頻繁創建與燒毀的問題,但是這里需要注意結束使用以后,需要手動釋放對象池中的對象。

經過優化的內存分配,會變得平緩很多。比如對上面的例子。 去除上面故意創建的大量數組,和優化了2、3兩個點以后的內存分配以下圖所示:

可以看出短時間內,內存并沒有甚么明顯的變化。并且在很長1段時間內都沒有觸發1次 GC

4、減少 Path 的創建次數

這里觸及到對特殊規則圖形的繪制的優化。 Path 的創建也觸及到內存的分配和釋放,這些都是需要消耗資源的。并且對越復雜的 Path,Canvas 在繪制的時候,也會更加的耗時。因此我們需要做的就是盡可能優化 Path 的創建進程,簡化運算量。這1塊并沒有很多統1的標準方法,更多的是依托經驗,并且將上面提到到的3點優化方法靈活應用。

首先 Path 類中本身即提供了數據結構重用的接口。它除提供 reset 復位方法以外,還提供了 rewind 的方法。這樣每次動畫循環調用的時候,能夠做到不釋放之前已分配的內存就可以夠重用。這樣避免的內存的反復釋放和分配。特別是對本例中,每次繪制的 Path 中的點都是1樣多的情況更加適用。

采取方法1種低密度的繪圖方法,一樣還能夠減少 Path 中線段的數量,這樣下降了 Path 構造的次數,同能 Canvas 在繪制 Path 的時候,由于 Path 變的簡單了,一樣能夠加快繪制速度。

特別的,對本文中的波形例子。 視覺圖中給出來的效果圖,除要用漸變色填充正弦線中間的區域以外。還需要對正弦線本身進行描邊。 同時1組正弦線中的上下兩根正弦線的色彩還不1樣。 這樣對1組完全的正弦線的繪制其實需要3個步驟:
1、填充正弦線
2、描正弦線上邊沿
3、描正弦線下邊沿

如何很好的將這3個步驟組合起來,盡可能減少 Path 的創建也很有講求。比如,如果我們直接依照上面列出來的步驟來繪制的話,首先需要創建1個同時包括上下正弦線的 Path,需要計算1遍上下正弦線的點,然后對這個 Path 使用填充的方式來繪制。 然后再計算1遍上弦線的點,創建只有上弦線的 Path,然后使用 Stroke 的模式來繪制,接著下弦線。 這樣我們將會重復創建兩邊 Path,并且還會重復1倍點坐標的計算量。

如果我們能采取上面步驟2中提到的,利用空間換取時間的方法。 首先把所有點位置都記在1個數組中,然后利用這些點來計算并繪制上弦線的 Path,然后保存下來;再計算和繪制下弦線的 Path 并保存。最后創建1個專門記錄填充區的 Path,利用 mPath.addPath();的功能,將之前的兩個 path 填充到該 Path 中。 這樣便能夠減少 Path 的計算量。同時將3個 Path 分別用不同的變量來記錄,這樣在下次循環到來的時候,還能利用 rewind 方法來進行內存重用。

這里需要注意的是,Path 提供了 close的方法,來將1段線封閉。 這個函數能夠提供1定的方便。但是其實不是每一個時候都好用。有的時候,還是需要我們手動的去添加線段來閉合1個區域。比以下面圖中的情形,采取 close,就會致使中間有1段空白的區域:

5、優化繪制的步驟

甚么? 經過上面幾個步驟的優化,動畫還是卡頓?不要慌,這里再提供1個精確分析卡頓的工具。 Android 還為我們提供了能夠追蹤監控每一個方法履行時間的工具 TraceView。 它在 Android Device Monitor 中打開。比如筆者在開發進程中發現動畫有卡頓,然后用上面 TraceView 工具查看得到下圖:

發現 clapGradientRectAndDrawStroke 這個方法占用了72.1%的 CPU 時間,而這個方法中實際占用時間的是 drawPath。這說明此處的繪制存在明顯的缺點與不公道,大部份的時間都用在繪制 clapGradientRectAndDrawStroke 上面了。那末我們再看1下之前繪制的原理,為了能夠從矩形和正弦線之間剪切出交集,并顯示漸變區域。筆者做出了以下圖的嘗試:

首先繪制出漸變填充的矩形; 然后再將正弦線包裹的區域用透明色彩進行反向填充(白色區域),這樣它們交集的地方利用 SrcIn 模式進行剪切,這時候候顯示出來便是白色覆蓋了矩形的區域(實際是透明色)加上它們未交集的地方(正弦框內)。這樣一樣能夠到達設計圖中給出的效果。代碼以下:

    mPath.rewind();
    mPath.addPath(mPathLine1);
    mPath.lineTo(getXPos(mDensity - 1), -mLineCacheY[mDensity - 1] + mHeight_2 * 2);
    mPath.addPath(mPathLine2);
    mPath.lineTo(getXPos(0), mLineCacheY[0]);

    mPath.setFillType(Path.FillType.INVERSE_WINDING);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setShader(null);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    mPaint.setColor(getResources().getColor(android.R.color.transparent));
    canvas.drawPath(mPath, mPaint);
    mPaint.setXfermode(null);

雖然上面的代碼一樣也實現了效果,但是由于使用的反向填充,致使填充區域急劇變大。最后致使 canvas.drawPath(mPath, mPaint);調用占據了70%以上的計算量。

找到瓶頸點并知道緣由以后,我們就可以做出針對性的改進。 我們只需要調劑繪制的順序,先將正弦線區域內做正向填充,然后再以 SrcIn 模式繪制漸變色填充的矩形。 這樣減少了需要繪制的區域,同時也到達預期的效果。

下面是改進以后 TraceView 的結果截圖:

從截圖中可以看到計算量被均分到不同的繪制方法中,已沒有瓶頸點了,并且實測動畫也變得流暢了。 1般卡頓都能通過此種方法比較精確的找到真實的瓶頸點。

總結

本文主要簡單介紹了1下 Android 普通 View 和 SurfaceView 的繪制與動畫原理,然后介紹了1下錄音機波形動畫的具體實現和優化的方法。但是限于筆者的水平和經驗有限,肯定有很多紕漏和毛病的地方。大家有更多更好的建議,歡迎1起分享討論,共同進步。

更多精彩內容歡迎關注騰訊bugly微信公眾賬號:

騰訊bugly

騰訊bugly

騰訊 Bugly是1款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上利用崩潰的情況和解決方案。智能合并功能幫助開發同學把每天上報的數千條 Crash 根據根因合并分類,逐日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在發布后快速的了解利用的質量情況,適配最新的 iOS, Android 官方操作系統,鵝廠的工程師都在使用,快來加入我們吧!

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 欧美一区二区在线播放 | 最近的中文字幕 | 青青草原国产在线观看 | 日韩欧美精品有码在线观看 | 欧美久久久久久 | 国产福利一区二区三区视频在线 | 波多野结衣手机视频一区 | 女人18一级特级毛片免费看 | 亚洲国产天堂久久综合2261144 | 最新午夜宅男 | 国产精品所毛片视频 | www.激情| 亚洲性xx | xxxx 欧美| 亚洲欧洲在线观看 | 日本在线视频网 | 午夜视频入口 | free性日韩| 亚洲欧美久久婷婷爱综合一区天堂 | 亚洲精品精品一区 | 三级国产在线观看 | 国产aⅴ精品一区二区三区久久 | 91啪国自产在线高清观看 | 欧美精品v欧洲精品 | 久久久久日韩精品无 | 性欧美videos另类视频 | 偷自拍第一页 | 亚洲国产成人综合 | 亚洲最大色网站 | 日本中文字幕免费 | 国产中文字幕视频在线观看 | 国产精品欧美亚洲韩国日本99 | 成人不卡在线 | 天天做天天爱天天大综合 | 羞羞网址 | www.亚洲成人 | 99久久老司机免费精品免费 | 精品国产综合成人亚洲区 | 亚洲精品视频区 | 日本v在线 | 欧美高清在线不卡免费观看 |