(自定義view實現(xiàn))音量波形圖

標簽: 自定義view 音量波形 音波


本文目的:主要是記錄自己在實現(xiàn)自定義view的時候颜骤,一些思路和解決方案唧喉。

目標

音量波形圖

繪制兩個音量波形,并且能夠向右運動忍抽,上面的波形移動速度慢八孝,下面的波形移動速度快,并且振幅能夠根據音量的高低進行改變鸠项。

分解目標

先考慮靜止狀態(tài)干跛,上圖有兩個波形圖,現(xiàn)只考慮一個波形圖祟绊,每個波形圖類似于兩個正弦函數(shù)的閉合楼入。所以我們第一步要繪制一個正弦圖形

正弦函數(shù)圖像

繪制正弦函數(shù)

關于自定義view的圖形繪制久免,一般都需要onMeasure浅辙,onLayout,onDraw三個步驟阎姥。由于是自定義view记舆,而不是viewGroup,所以并不需要實現(xiàn)onLayout方法呼巴。
在繪制之前泽腮,要在onMeasure方法里御蒲,計算出畫布的高度、寬度诊赊、中心點等需要計算的變量厚满,這里就不詳細說明了。
為了便于繪制圖形正弦函數(shù)碧磅,要把畫布的坐標原點移動到繪制view的中間位置碘箍。
也就是下圖中標明的點,這樣坐標原點(0,0)鲸郊,就位于view的中間丰榴,便于函數(shù)計算。

正弦函數(shù)方法參考:

    private double sine(float x, int period, float drawWidth) {
        return Math.sin(2 * Math.PI * period * x / drawWidth);
    }

其中period為在畫布里有多少個周期秆撮,假設period為3四濒,就是在畫布里有三個周期。drawWidth為畫布寬度职辨。

在ondraw 方法里進行繪制盗蟆。
這里調用drawsine方法

private void drawSine(Canvas canvas, Path path, Paint paint, int period, float drawWidth, float amplitude) {
    float halfDrawWidth = drawWidth / 2f;
    path.reset();
    path.moveTo(-halfDrawWidth, 0);//將繪制的起點移動到最左邊
    float y;
    for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
        y = (float) sine(x, period, drawWidth) * amplitude; 
        path.lineTo(x, y);
    }   
    canvas.drawPath(path, paint);
    canvas.save();
    canvas.restore(); 
}

amplitude 為振幅的高度,也就是半個畫布的高度舒裤。繪制出的圖形如下(在手機里喳资,y軸正方形是向下的,x軸正方形是向右的)


正弦函數(shù)圖

繪制兩個關于y軸對稱正弦函數(shù)

繪制反方向正弦函數(shù)惭每,并且填充里面的內容骨饿。只是相當于將y值乘以-1亏栈,這里不詳細列出具體代碼


兩個正弦函數(shù)圖

進行內容填充
mPaint.setStyle(Style.FILL); 畫筆的樣式設置為填充台腥,填充后的效果如下


音波圖

這樣勉強能算作一個波形圖了。

縮放波形圖

觀察剛開始的效果圖绒北,發(fā)現(xiàn)每個波形的振幅并不相同黎侈,所以要考慮對波形圖進行縮放。
采用縮放函數(shù)闷游,就是按比例將振幅逐漸增大或者減小峻汉。

    double scaling;
    for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
        scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 對y進行縮放
        y = (float) (sine(x, period, drawWidth) * amplitude * (1) * Math
                .pow(scaling, 3));
        path.lineTo(x, y);
    }

為了更好的效果,我們縮放了三次脐往,Math.pow(scaling, 3)
現(xiàn)在感覺和圖一的效果差不多了休吠。基本滿足需求业簿,就是每個波形之間的間隙還是很小瘤礁。(后續(xù)會進行優(yōu)化)


音波縮放圖

讓波形圖動起來

在view里定義一個移動線程MoveThread,每隔一段時間就執(zhí)行一次刷新postInvalidate()梅尤,每次刷新圖像的時候柜思,都會改變該圖形的相位岩调。
所謂相位,查看下圖赡盘,一個函數(shù)是sin(x),另外一個函數(shù)是sin(x+0.5)号枕,兩個函數(shù)之間就相差了0.5個相位。


正弦函數(shù)相位 + 0.5 圖

相位變化了0.5陨享,看起來就會向左移動0.5的距離葱淳。(圖形右上角有標注函數(shù))
在線程中不斷更新相位的取值,這樣不斷的刷新圖形抛姑,就會看起來形成一種移動的效果蛙紫。(大家可以想象以前放電影時用的膠片,實現(xiàn)原理類似)這樣我們的圖形就能運動起來了途戒。
修改后的sine函數(shù)

private double sine(float x, int period, float drawWidth, double phase) {
    return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
}

定義一個MoveThread

private class MoveThread extends Thread {
    private static final int MOVE_STOP = 1;

    private static final int MOVE_START = 0;

    private int state;

    @Override
    public void run() {
        mPhase = 0;
        state = MOVE_START;
        while (true) {
            if (state == MOVE_STOP) {
                break;
            }
            try {
                sleep(30);
            } catch (InterruptedException e) {
            }
            mPhase -= MOVE_DISTACE;
            postInvalidate();
        }
    }
    public void stopRunning() {
        state = MOVE_STOP;
    }
}

這樣當線程開啟的時候坑傅,我們就能根據不斷的改變sine函數(shù)的相位,就會形成不斷右移動的效果喷斋。

繪制兩個波形唁毒,并且設置不同的移動速度

兩個波形的區(qū)別只是顏色不同,最大振幅不同星爪,以及移動速度不同浆西。
所謂移動速度不同,就是相位每次改變的值不同顽腾〗悖可以在計算sine函數(shù)的時候,對固定相位值乘以不同的比例抄肖,就會得到不同的移動速度久信。從下圖中的移動我們可以看到效果,已經很接近目標了漓摩。


音波移動圖形

(這里可以在圖中看到不同的實現(xiàn)效果裙士,為了便于有些同學學習和實踐,將整個view進行了解剖管毙,能更快的學習view的繪制過程)

根據音量改變波形圖的振幅

通過音量設置波形圖振幅腿椎,這樣能夠讓波形圖隨著聲音大小的變化而變化。
我們改變sin函數(shù)的振幅夭咬,圖形就會升高或者下降啃炸。也就是在相同的x位置處,y的取值會發(fā)生變化卓舵。


振幅不同南用,兩個正弦的高度也不同

但是,隨著音頻的變化,振幅的變動幅度變大训枢,這樣會造成一種圖形的閃動托修。

解決圖形閃動

當音量變化時,我們的振幅會發(fā)生變化恒界,也就是這個圖形睦刃,會隨著振幅的變化按比例變大或者變小。如下圖標記的兩個點十酣,如果我們刷新間隔為1s涩拙,就是1s之后,點1會突然變成點2的位置耸采。這樣就會造成閃動兴泥。


點在不同的振幅,所在的高度不同

我們的要求是圖形要平滑的變動虾宇,意思就是不能這么快的進行變化搓彻,要怎么解決呢?
首先我們規(guī)定上升的最大速度為為1px每秒嘱朽,現(xiàn)在的y值為1px旭贬,也就是當前1的位置。
現(xiàn)在只考慮點1的位置搪泳,假設我們每1s刷新一次稀轨,上升的最大速度為1px每秒,這樣我們就可以計算出下一次變化y的最高位置為 1px + 1px/秒 * 1秒 = 2岸军。

  • 如果當前音量發(fā)生變化奋刽,也就是振幅發(fā)生改變,得到的y值為3px艰赞,這個時候y值佣谐,3px >
    我們計算的2px,這個時候就要用我們的2px猖毫。也就保證了最大速度不能超過我們規(guī)定的速度台谍。
  • 如果當前音量發(fā)生變化,也就是振幅發(fā)生改變吁断,得到的y值為1.5px,這個時候y值坞生,1.5px <
    我們計算的2px仔役,這個時候就要用我們的1.5px。根據實際位置進行設定是己。

下降同理又兵,這樣我們就能保證上升或者下降的最大速度。

    // 計算當前時間下的振幅
    private float currentVolumeAmplitude(long curTime) {
        if (lastAmplitude == nextTargetAmplitude) {
            return nextTargetAmplitude;
        }

        if (curTime == amplitudeSetTime) {
            return lastAmplitude;
        }

        if (nextTargetAmplitude > lastAmplitude) {
            float target = lastAmplitude + mVerticalSpeed
                    * (curTime - amplitudeSetTime) / 1000;
            if (target >= nextTargetAmplitude) {
                target = nextTargetAmplitude;
                lastAmplitude = nextTargetAmplitude;
                amplitudeSetTime = curTime;
                nextTargetAmplitude = mMinAmplitude;
            }
            return target;
        }

        if (nextTargetAmplitude < lastAmplitude) {
            float target = lastAmplitude - mVerticalRestoreSpeed
                    * (curTime - amplitudeSetTime) / 1000;
            if (target <= nextTargetAmplitude) {
                target = nextTargetAmplitude;
                lastAmplitude = nextTargetAmplitude;
                amplitudeSetTime = curTime;
                nextTargetAmplitude = mMinAmplitude;
            }
            return target;
        }

        return mMinAmplitude;
    }

圖形優(yōu)化

因為中間的間隙過小,我們要把中間的間歇變大沛厨,類似于下圖捌肴。這樣效果可能會更好一點埃跷。


優(yōu)化后的波形圖

實施方案,將正弦函數(shù)上移,下面的正弦函數(shù)下移動瓣铣,這樣中間留有固定寬度的,通過縮放函數(shù)之后尼酿,效果如下:


優(yōu)化后的音量波形圖

實驗過程中存在的問題以及解決方案:

中間線條的問題

橫線的原因恭理,是因為縮放造成了這 兩個波形之間的點 x對應的值,y不等于0剿牺,會閉合不到中間的點企垦。造成這個的現(xiàn)象是因為我們只是針對半個正弦曲線就進行填充了


有瑕疵的波形圖

所以我們要將正反兩個曲線畫出來之后,把路徑閉合之后再進行填充晒来。這樣就不會出現(xiàn)上面中間有橫線的瑕疵


修復后的波形圖

閃動問題

參考上文解決方案

源代碼地址:https://github.com/duchao/VolumeView

可以直接使用的view

VolumeView.java
API: start() 開始
stop() 結束
setVolume(float volume) 設置音量

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末钞诡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子湃崩,更是在濱河造成了極大的恐慌臭增,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竹习,死亡現(xiàn)場離奇詭異誊抛,居然都是意外死亡,警方通過查閱死者的電腦和手機整陌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門拗窃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泌辫,你說我怎么就攤上這事随夸。” “怎么了震放?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵宾毒,是天一觀的道長。 經常有香客問我殿遂,道長诈铛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任墨礁,我火速辦了婚禮幢竹,結果婚禮上,老公的妹妹穿的比我還像新娘恩静。我一直安慰自己焕毫,他們只是感情好蹲坷,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邑飒,像睡著了一般循签。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疙咸,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天县匠,我揣著相機與錄音,去河邊找鬼罕扎。 笑死聚唐,一個胖子當著我的面吹牛,可吹牛的內容都是我干的腔召。 我是一名探鬼主播杆查,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼臀蛛!你這毒婦竟也來了亲桦?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤浊仆,失蹤者是張志新(化名)和其女友劉穎客峭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抡柿,經...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡舔琅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了洲劣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片备蚓。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖囱稽,靈堂內的尸體忽然破棺而出郊尝,到底是詐尸還是另有隱情,我是刑警寧澤战惊,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布流昏,位于F島的核電站,受9級特大地震影響吞获,放射性物質發(fā)生泄漏况凉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一衫哥、第九天 我趴在偏房一處隱蔽的房頂上張望茎刚。 院中可真熱鬧,春花似錦撤逢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽初狰。三九已至,卻和暖如春互例,著一層夾襖步出監(jiān)牢的瞬間奢入,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工媳叨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腥光,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓糊秆,卻偏偏與公主長得像武福,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痘番,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內容