
由于最近項目開發(fā)需要用到自定義SeekBar,于是又對android下的各種類型drawable進行了一個全面系統(tǒng)的認識,只能感慨drawable的功能還是很強大的。通過自定義SeekBar有感而發(fā),嘗試用ClipDrawable實現(xiàn)音頻錄制過程的一個麥克風(fēng)錄制效果:
在實現(xiàn)開發(fā)之前先讓我們一起認識兩種類型的Drawable:
LayerDrawable
LayerDrawable可以包含一個drawable數(shù)組,而且系統(tǒng)會根據(jù)drawable數(shù)組的前后順序來繪制所有的drawable,索引最大的drawable也就相應(yīng)的會被繪制在最上面。使用過PhotoShop的朋友應(yīng)該會比較容易理解,LayerDrawable和PhotoShop中圖層的概念很相似,這里drawable數(shù)組中的每一個drawable就相當(dāng)于PhotoShop中的一個圖層,上一個圖層會遮住之后所有圖層與之重疊的部分。
定義LayerDrawable對象的XML文件的根元素為<layer-list… />,該元素可以包含對個<item… />元素也就是一個drawable對象。
ClipDrawable
ClipDrawable,顧名思義這就是一個可以進行裁切的drawable,在XML文件中定義ClipDrawable對象使用的根元素是<clip… />元素,該元素包含以下幾個重要的屬性:
- android:drawable:指定將要被截取的Drawable對象。
- android:clipOrientation:指定Drawable對象的截取方向可以是水平和豎直方向。
- android:gravity:表示Drawable對象的對齊方式,例如:left 可以理解為左邊部分為保留部分,右邊部分為剪切部分,則我們可以看到的就是截取的左邊部分。
注意,使用ClipDrawable對象時可以調(diào)用setLevel(int level)方法來控制截取區(qū)域的大小,而level的取值區(qū)間在0~10000之間,則level為0時,表示圖片截取部分為空,當(dāng)了level為10000時,截取整張圖片。
了解完畢,下面我們就要用這兩種Drawable結(jié)合使用開發(fā)我們今天的麥克風(fēng)說話效果:
首先,準(zhǔn)備兩張位圖


然后,在XML中新建一個擁有兩個Drawable的LayerDrawable文件layer-microphone.xml,在頂層顯示的是可以裁切的ClipDrawable,設(shè)置剪切方向為豎直方向,設(shè)置對其方式為bottom,底部的則是不通的Drawable作為背景。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@android:id/background" android:drawable="@drawable/icon_microphone_normal" />
<item android:id="@android:id/progress" >
<clip android:drawable="@drawable/icon_microphone_recoding" android:gravity="bottom" android:clipOrientation="vertical" />
</item>
</layer-list>
然后,再自定義一個PopupWindow用于音頻錄制過程顯示麥克風(fēng)動畫效果,關(guān)于自定義popupWindow有疑問的朋友可以參考我的上一篇文章"2016-05-10 淺談PopupWindow在Android開發(fā)中的使用"。
窗口布局如下layout_microphone.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:background="@drawable/shape_window_background"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp" >
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:id="@android:id/progress"
android:src="@drawable/layer-microphone" />
<TextView
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:id="@android:id/text1"
android:layout_width="114dp"
android:textColor="#FFFFFF"
android:gravity="center"
android:textSize="16sp"
android:text="00:00" />
</LinearLayout>
</LinearLayout>
從代碼中可以看出,我把ImageVie的資源設(shè)為layer-microphone.xml,我們獲取ImageView的Drawable對象并設(shè)置Level值就能實現(xiàn)想要的效果:
imageView.getDrawable().setLevel(5400);

從這里我們已經(jīng)可以看到裁切效果。最后一步,我們只要能夠?qū)崟r獲取音頻錄制過程中的分貝值的變化,再將分貝值變化轉(zhuǎn)換到相應(yīng)的Level值,就能實現(xiàn)音頻錄制說話效果啦,于是百度,在網(wǎng)上看到一篇文章"Android中實時獲取音量分貝值詳解" ,首先,感謝作者的分享,于是我也就照著方法寫了一個AudioRecoderUtil.java類,稍加改進,添加了一個監(jiān)聽事件,代碼如下:
package com.mariostudio.audiorecoder;
import java.io.File;
import java.io.IOException;
import android.media.MediaRecorder;
import android.os.Handler;
import android.util.Log;
/**
* Created by MarioStudio on 2016/5/12.
*/
public class AudioRecoderUtils {
private String filePath;
private MediaRecorder mMediaRecorder;
private final String TAG = "MediaRecord";
public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大錄音時長1000*60*10;
private OnAudioStatusUpdateListener audioStatusUpdateListener;
public AudioRecoderUtils(){
this.filePath = "/dev/null";
}
public AudioRecoderUtils(File file) {
this.filePath = file.getAbsolutePath();
}
private long startTime;
private long endTime;
/**
* 開始錄音 使用amr格式
* 錄音文件
* @return
*/
public void startRecord() {
// 開始錄音
/* ①Initial:實例化MediaRecorder對象 */
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設(shè)置麥克風(fēng)
/* ②設(shè)置音頻文件的編碼:AAC/AMR_NB/AMR_MB/Default 聲音的(波形)的采樣 */
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*
* ②設(shè)置輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263視頻/ARM音頻編碼)、MPEG-4、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB)
*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
/* ③準(zhǔn)備 */
mMediaRecorder.setOutputFile(filePath);
mMediaRecorder.setMaxDuration(MAX_LENGTH);
mMediaRecorder.prepare();
/* ④開始 */
mMediaRecorder.start();
// AudioRecord audioRecord.
/* 獲取開始時間* */
startTime = System.currentTimeMillis();
updateMicStatus();
Log.i("ACTION_START", "startTime" + startTime);
} catch (IllegalStateException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
/**
* 停止錄音
*/
public long stopRecord() {
if (mMediaRecorder == null)
return 0L;
endTime = System.currentTimeMillis();
Log.i("ACTION_END", "endTime" + endTime);
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
Log.i("ACTION_LENGTH", "Time" + (endTime - startTime));
return endTime - startTime;
}
private final Handler mHandler = new Handler();
private Runnable mUpdateMicStatusTimer = new Runnable() {
public void run() {
updateMicStatus();
}
};
/**
* 更新話筒狀態(tài)
*/
private int BASE = 1;
private int SPACE = 100;// 間隔取樣時間
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
private void updateMicStatus() {
if (mMediaRecorder != null) {
double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;
double db = 0;// 分貝
if (ratio > 1) {
db = 20 * Math.log10(ratio);
if(null != audioStatusUpdateListener) {
audioStatusUpdateListener.onUpdate(db);
}
}
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
}
}
public interface OnAudioStatusUpdateListener {
public void onUpdate(double db);
}
}
進行到這一步,已經(jīng)基本完成了這個效果,最后只需要在自定義PopupWindow的時候提供方法setLevel(int level)就可以輕松實現(xiàn)PopupWindow實時刷新分貝值啦??!
public void setLevel(int level) {
imageView.getDrawable().setLevel(3000 + 6000 * level / 100);
}

至于為什么設(shè)置level的時候要3000 + 6000 * level / 100以及計時效果,就都留給聰明的你去探索咯!!
作者申明:如果文中有表述不當(dāng)或闡述錯誤的地方,還望正在看文章的您可以幫忙指出,有疑惑也可以在評論區(qū)提問或者私信,期待您的意見和建議,歡迎關(guān)注交流。