由于最近项目开发需要用到自定义SeekBar,于是又对android下的各种类型drawable进行了一个全面系统的认识,只能感慨drawable的功能还是很强大的。通过自定义SeekBar有感而发,尝试用layer-list实现音频录制过程的一个麦克风录制效果,具体效果图如下:
在实现开发之前先让我们一起认识两种类型的Drawable:
LayerDrawable
LayerDrawable可以包含一个drawable数组,而且系统会根据drawable数组的前后顺序来绘制所有的drawable,索引最大的drawable也就相应的会被绘制在最上面。使用过PhotoShop的朋友应该会比较容易理解,LayerDrawable和PhotoShop中图层的概念很相似,这里drawable数组中的每一个drawable就相当于PhotoShop中的一个图层,上一个图层会遮住之后所有图层与之重叠的部分。
定义LayerDrawable对象的XML文件的根元素为
ClipDrawable
ClipDrawable,顾名思义这就是一个可以进行裁切的drawable,在XML文件中定义ClipDrawable对象使用的根元素是< clip … />元素,该元素包含以下几个重要的属性:
- android:drawable:指定将要被截取的Drawable对象。
- android:clipOrientation:指定Drawable对象的截取方向可以是水平和竖直方向。
- android:gravity:表示Drawable对象的对齐方式,例如:left 可以理解为左边部分为保留部分,右边部分为剪切部分,则我们可以看到的就是截取的左边部分。
注意,使用ClipDrawable对象时可以调用setLevel(int level)方法来控制截取区域的大小,而level的取值区间在0~10000之间,则level为0时,表示图片截取部分为空,当了level为10000时,截取整张图片。
了解完毕,下面我们就要用这两种Drawable结合使用开发我们今天的麦克风说话效果:
首先,准备两张位图
然后,在XML中新建一个拥有两个Drawable的LayerDrawable文件layer-microphone.xml,在顶层显示的是可以裁切的ClipDrawable,设置剪切方向为竖直方向,设置对其方式为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用于音频录制过程显示麦克风动画效果,关于自定义popupWindow有疑问的朋友可以参考我的上一篇文章“2016-05-10 浅谈PopupWindow在Android开发中的使用”。
窗口布局如下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的资源设为layer-microphone.xml,我们获取ImageView的Drawable对象并设置Level值就能实现想要的效果:
imageView.getDrawable().setLevel(5400);
从这里我们已经可以看到裁切效果。最后一步,我们只要能够实时获取音频录制过程中的分贝值的变化,再将分贝值变化转换到相应的Level值,就能实现音频录制说话效果啦,于是百度,在网上看到一篇文章“Android中实时获取音量分贝值详解” ,首先,感谢作者的分享,于是我也就照着方法写了一个AudioRecoderUtil.java类,稍加改进,添加了一个监听事件,代码如下:
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);// 设置麦克风
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
/* ③准备 */
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();
}
};
/**
* 更新话筒状态
*/
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);
}
}
进行到这一步,已经基本完成了这个效果,最后只需要在自定义PopupWindow的时候提供方法setLevel(int level)就可以轻松实现PopupWindow实时刷新分贝值啦!!
public void setLevel(int level) {
imageView.getDrawable().setLevel(3000 + 6000 * level / 100);
}
至于为什么设置level的时候要3000 + 6000 level / 100*以及计时效果,就都留给聪明的你去探索咯!!