繪制聲音頻率的波紋動(dòng)畫效果

歡迎Follow我的GitHub, 關(guān)注我的簡(jiǎn)書. 其余參考Android目錄.

Music

本文的合集已經(jīng)編著成書理肺,高級(jí)Android開發(fā)強(qiáng)化實(shí)戰(zhàn)洽糟,歡迎各位讀友的建議和指導(dǎo)讯泣。在京東即可購(gòu)買:https://item.jd.com/12385680.html

Android

在一些音樂(lè)類應(yīng)用中, 經(jīng)常會(huì)展示隨著節(jié)奏上下起伏的波紋信息, 這些波紋形象地傳達(dá)了聲音信息, 可以提升用戶體驗(yàn), 那么是如何實(shí)現(xiàn)的呢? 可以使用Visualizer類獲取當(dāng)前播放的聲音信息, 并繪制在畫布上, 使用波紋展示即可. 我來(lái)講解一下使用方法.

主要
(1) Visualizer類提取波紋信息的方式.
(2) 應(yīng)用動(dòng)態(tài)權(quán)限管理的方法.
(3) 分離自定義視圖的展示和邏輯.

本文源碼的GitHub下載地址


1. 基礎(chǔ)準(zhǔn)備

Android 6.0引入動(dòng)態(tài)權(quán)限管理, 在這個(gè)項(xiàng)目中, 會(huì)使用系統(tǒng)的音頻信息, 因此把權(quán)限管理引入這個(gè)項(xiàng)目, 參考. Gradle配置引入了Lambda表達(dá)式, 參考.

頁(yè)面布局, 使用自定義的波紋視圖控件.

    <!--波紋視圖-->
    <me.chunyu.spike.wcl_visualizer_demo.visualizers.WaveformView
        android:id="@+id/main_wv_waveform"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

效果


波紋

2. 首頁(yè)邏輯

添加動(dòng)態(tài)權(quán)限管理, 在啟動(dòng)頁(yè)面時(shí), 獲取應(yīng)用所需的音頻權(quán)限.
RendererFactory工廠類創(chuàng)建波紋的繪制類SimpleWaveformRender.
startVisualiser方法獲取當(dāng)前播放音樂(lè)的音頻信息.
注意頁(yè)面關(guān)閉, 在onPause時(shí), 釋放Visualiser類.

public class MainActivity extends AppCompatActivity {

    private static final int CAPTURE_SIZE = 256; // 獲取這些數(shù)據(jù), 用于顯示
    private static final int REQUEST_CODE = 0;

    // 權(quán)限
    private static final String[] PERMISSIONS = new String[]{
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.MODIFY_AUDIO_SETTINGS
    };

    @Bind(R.id.main_wv_waveform) WaveformView mWvWaveform; // 波紋視圖

    private Visualizer mVisualizer; // 音頻可視化類

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        RendererFactory rendererFactory = new RendererFactory();
        mWvWaveform.setRenderer(rendererFactory.createSimpleWaveformRender(ContextCompat.getColor(this, R.color.colorPrimary), Color.WHITE));
    }

    @Override protected void onResume() {
        super.onResume();

        PermissionsChecker checker = new PermissionsChecker(this);

        if (checker.lakesPermissions(PERMISSIONS)) {
            PermissionsActivity.startActivityForResult(this, REQUEST_CODE, PERMISSIONS);
        } else {
            startVisualiser();
        }
    }

    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE && resultCode == PermissionsActivity.PERMISSIONS_DENIED) {
            finish();
        }
    }

    // 設(shè)置音頻線
    private void startVisualiser() {
        mVisualizer = new Visualizer(0); // 初始化
        mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
            @Override
            public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
                if (mWvWaveform != null) {
                    mWvWaveform.setWaveform(waveform);
                }
            }

            @Override
            public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {

            }
        }, Visualizer.getMaxCaptureRate(), true, false);
        mVisualizer.setCaptureSize(CAPTURE_SIZE);
        mVisualizer.setEnabled(true);
    }

    // 釋放
    @Override protected void onPause() {
        if (mVisualizer != null) {
            mVisualizer.setEnabled(false);
            mVisualizer.release();
        }
        super.onPause();
    }
}

Visualizer類
new Visualizer(0), 初始化; setCaptureSize, 獲取波紋數(shù)量; setEnabled, 啟動(dòng)監(jiān)聽(tīng);
setDataCaptureListener, 第一個(gè)參數(shù)是回調(diào), 使用WaveFormData或FftData; 第二個(gè)是更新率; 第三個(gè)是判斷使用WaveFormData; 第四個(gè)是判斷使用FftData, 第三\四個(gè)均與回調(diào)的返回值有關(guān).


3. 波紋視圖

頁(yè)面框架, 分離顯示和邏輯, 使用接口渲染, 輸入畫布Canvas和波紋Waveform.

/**
 * 音頻波紋視圖
 * <p>
 * Created by wangchenlong on 16/2/11.
 */
public class WaveformView extends View {

    private WaveformRenderer mRenderer; // 繪制類
    private byte[] mWaveform; // 波紋形狀

    public WaveformView(Context context) {
        super(context);
    }

    public WaveformView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WaveformView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(21)
    public WaveformView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setRenderer(WaveformRenderer renderer) {
        mRenderer = renderer;
    }

    public void setWaveform(byte[] waveform) {
        mWaveform = Arrays.copyOf(waveform, waveform.length); // 數(shù)組復(fù)制
        invalidate(); // 設(shè)置波紋之后, 需要重繪
    }

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mRenderer != null) {
            mRenderer.render(canvas, mWaveform);
        }
    }
}

數(shù)組復(fù)制Arrays.copyOf(), 在設(shè)置波紋后重繪頁(yè)面invalidate().


4. 波紋邏輯

核心部分renderWaveform, 渲染波紋.
把頁(yè)面分為網(wǎng)格樣式, 根據(jù)波紋值, 繪制曲線; 沒(méi)有波紋, 繪制居中水平直線.

/**
 * 波紋渲染邏輯
 * <p>
 * Created by wangchenlong on 16/2/12.
 */
public class SimpleWaveformRenderer implements WaveformRenderer {

    private static final int Y_FACTOR = 0xFF; // 2的8次方 = 256
    private static final float HALF_FACTOR = 0.5f;

    @ColorInt private final int mBackgroundColor;
    private final Paint mForegroundPaint;
    private final Path mWaveformPath;

    private SimpleWaveformRenderer(@ColorInt int backgroundColor, Paint foregroundPaint, Path waveformPath) {
        mBackgroundColor = backgroundColor;
        mForegroundPaint = foregroundPaint;
        mWaveformPath = waveformPath;
    }

    public static SimpleWaveformRenderer newInstance(@ColorInt int backgroundColor, @ColorInt int foregroundColour) {
        Paint paint = new Paint();
        paint.setColor(foregroundColour);
        paint.setAntiAlias(true); // 抗鋸齒
        paint.setStrokeWidth(8.0f); // 設(shè)置寬度
        paint.setStyle(Paint.Style.STROKE); // 填充

        Path waveformPath = new Path();

        return new SimpleWaveformRenderer(backgroundColor, paint, waveformPath);
    }

    @Override public void render(Canvas canvas, byte[] waveform) {
        canvas.drawColor(mBackgroundColor);
        float width = canvas.getWidth();
        float height = canvas.getHeight();

        mWaveformPath.reset();

        // 沒(méi)有數(shù)據(jù)
        if (waveform != null) {
            // 繪制波形
            renderWaveform(waveform, width, height);
        } else {
            // 繪制直線
            renderBlank(width, height);
        }

        canvas.drawPath(mWaveformPath, mForegroundPaint);
    }

    private void renderWaveform(byte[] waveform, float width, float height) {
        float xIncrement = width / (float) (waveform.length); // 水平塊數(shù)
        float yIncrement = height / Y_FACTOR; // 豎直塊數(shù)
        int halfHeight = (int) (height * HALF_FACTOR); // 居中位置
        mWaveformPath.moveTo(0, halfHeight);
        for (int i = 1; i < waveform.length; ++i) {
            float yPosition = waveform[i] > 0 ?
                    height - (yIncrement * waveform[i]) : -(yIncrement * waveform[i]);
            mWaveformPath.lineTo(xIncrement * i, yPosition);
        }
        mWaveformPath.lineTo(width, halfHeight); // 最后的點(diǎn), 水平居中
    }

    // 居中畫一條直線
    private void renderBlank(float width, float height) {
        int y = (int) (height * HALF_FACTOR);
        mWaveformPath.moveTo(0, y);
        mWaveformPath.lineTo(width, y);
    }
}

繪制移動(dòng)moveTo, 繪制直線lineTo.


效果


動(dòng)畫

通過(guò)繪制波紋, 可以類似地繪制一些連續(xù)數(shù)據(jù), 更加直觀地展示, 提升用戶體驗(yàn).

參考

That's all! Enjoy it!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驻民,隨后出現(xiàn)的幾起案子绽左,更是在濱河造成了極大的恐慌悼嫉,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拼窥,死亡現(xiàn)場(chǎng)離奇詭異戏蔑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鲁纠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門总棵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人房交,你說(shuō)我怎么就攤上這事彻舰》ジ睿” “怎么了候味?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)隔心。 經(jīng)常有香客問(wèn)我白群,道長(zhǎng),這世上最難降的妖魔是什么硬霍? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任帜慢,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粱玲。我一直安慰自己躬柬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布抽减。 她就那樣靜靜地躺著允青,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卵沉。 梳的紋絲不亂的頭發(fā)上颠锉,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音史汗,去河邊找鬼琼掠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛停撞,可吹牛的內(nèi)容都是我干的瓷蛙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼戈毒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼速挑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起副硅,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤姥宝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后恐疲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊满,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年培己,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碳蛋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡省咨,死狀恐怖肃弟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情零蓉,我是刑警寧澤笤受,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站敌蜂,受9級(jí)特大地震影響箩兽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜章喉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一汗贫、第九天 我趴在偏房一處隱蔽的房頂上張望身坐。 院中可真熱鬧,春花似錦落包、人聲如沸部蛇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搪花。三九已至,卻和暖如春嘹害,著一層夾襖步出監(jiān)牢的瞬間撮竿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工笔呀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幢踏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓许师,卻偏偏與公主長(zhǎng)得像房蝉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子微渠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,183評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)搭幻、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,107評(píng)論 4 62
  • 多年以后逞盆,面對(duì)著一張女孩的個(gè)人照檀蹋,男孩將會(huì)回想起高中快要畢業(yè)時(shí)的一個(gè)晚上。那時(shí)臨近畢業(yè)云芦,班上所有的人并不是緊張于高...
    偽悔到來(lái)閱讀 226評(píng)論 0 0
  • 啟迪樂(lè)高學(xué)員:冉喆 課程階段:小小探索家 課程主題:托馬斯小火車 任課老師:小白老師 課程日期:2017.10.2...
    愿你安好啊閱讀 1,160評(píng)論 0 0
  • 不知不覺(jué)離開老家已經(jīng)快五年了俯逾,經(jīng)常做夢(mèng)還是能夠回憶起在老家的點(diǎn)點(diǎn)滴滴。偶爾發(fā)呆也能憶起些趣事舅逸。一直都想用很爛的文筆...
    學(xué)習(xí)者_(dá)閱讀 230評(píng)論 0 1