繪制聲音頻率的波紋動畫效果

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

Music

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

Android

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

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

本文源碼的GitHub下載地址


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

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

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

    <!--波紋視圖-->
    <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. 首頁邏輯

添加動態(tài)權(quán)限管理, 在啟動頁面時, 獲取應(yīng)用所需的音頻權(quán)限.
RendererFactory工廠類創(chuàng)建波紋的繪制類SimpleWaveformRender.
startVisualiser方法獲取當(dāng)前播放音樂的音頻信息.
注意頁面關(guān)閉, 在onPause時, 釋放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, 啟動監(jiān)聽;
setDataCaptureListener, 第一個參數(shù)是回調(diào), 使用WaveFormData或FftData; 第二個是更新率; 第三個是判斷使用WaveFormData; 第四個是判斷使用FftData, 第三\四個均與回調(diào)的返回值有關(guān).


3. 波紋視圖

頁面框架, 分離顯示和邏輯, 使用接口渲染, 輸入畫布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è)置波紋后重繪頁面invalidate().


4. 波紋邏輯

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

/**
 * 波紋渲染邏輯
 * <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();

        // 沒有數(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);
    }
}

繪制移動moveTo, 繪制直線lineTo.


效果


動畫

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

參考

That's all! Enjoy it!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容