本文的合集已經(jīng)編著成書理肺,高級(jí)Android開發(fā)強(qiáng)化實(shí)戰(zhàn)洽糟,歡迎各位讀友的建議和指導(dǎo)讯泣。在京東即可購(gòu)買:https://item.jd.com/12385680.html
在一些音樂(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.
效果
通過(guò)繪制波紋, 可以類似地繪制一些連續(xù)數(shù)據(jù), 更加直觀地展示, 提升用戶體驗(yàn).
That's all! Enjoy it!