今晚完成了模仿魔镜demo的最后一个模块就是使用话筒实例来吹气,让画面形成雾,再用手势擦除,相当于总结一下话筒类的使用。
在工作之前先声明权限
要先构造一个AudioRecordManger类
首先声明变量以及构造方法
private static final String TAG = "AudioRecord";//标记
public static final int SAMPLE_RATE_IN_HZ = 8000;//通道配置
public static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ,
AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_PCM_16BIT);//用于写入声音的缓存
private AudioRecord mAudioRecord;//话筒类
public boolean isGetVoiceRun;//是否录音运行
private Handler mHandler;//句柄
private int mWhat;//动作
public Object mLock;//对象
记下来是构造方法,通过传入handler和what来赋值,mLock是一个同步锁,做延时用
public AudioRecordManger(Handler handler,int what) {
mLock = new Object();//同步锁
this.mHandler = handler;//获得句柄
this.mWhat = what;//动作ID
}
实现监听吹气功能,用线程的方式开启话筒录音后,一秒钟10次接收话筒音量,然后将接收的音量转化为float的分贝值,并作为message发送。
public void getNoiseLevel() {
if (isGetVoiceRun) {
Log.e(TAG, "还在录着呢");
return;
}
//创建录音对象,并初始化对象属性
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
SAMPLE_RATE_IN_HZ, AudioFormat.CHANNEL_IN_DEFAULT,
AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE);
//判断话筒对象是否为空
if (mAudioRecord == null) {
Log.e("sound", "mAudioRecord初始化失败");
}
isGetVoiceRun = true;//开启录音
//使用新线程
new Thread(new Runnable() {
@Override
public void run() {
mAudioRecord.startRecording();//录音启动
short[] buffer = new short[BUFFER_SIZE];//设置缓存数组
while (isGetVoiceRun) {
int r = mAudioRecord.read(buffer, 0, BUFFER_SIZE);//r是实际读取的数据长度,一般而言r会小于buffersize
long v = 0;
// 将 buffer 内容取出,进行平方和运算
for (int i = 0; i < buffer.length; i++) {
v += buffer[i] * buffer[i];
}
double mean = v / (double) r; // 平方和除以数据总长度,得到音量大小。
double volume = 10 * Math.log10(mean);
// 大概一秒十次,锁
synchronized (mLock) {
try {
mLock.wait(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//声明消息类,句柄发送消息到主窗体函数
Message message = Message.obtain();
message.what = mWhat;
message.obj = volume;
mHandler.sendMessage(message);
}
//话筒对象释放
if (null !=mAudioRecord) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
}).start();//启动线程
}
mainactivity中接收message,先声明刚刚创建好的类
private AudioRecordManger audioRecordManger; //调用话筒实现类
private static final int RECORD = 2; //监听话筒
再在onCreate方法中调用实例的方法
audioRecordManger = new AudioRecordManger(handler,RECORD); //实例化话筒实现类
audioRecordManger.getNoiseLevel(); //打开话筒监听声音
我们在xml文件中放置drawview控件用来作为生成雾的画板,并在这上面去擦除雾,这是drawview的类
public class DrawView extends View {
private Canvas mCanvas;// 画布
private Path mPath;// 路径
private Paint mPaint;// 画笔
private float moveX, moveY;//移动坐标
private Bitmap mBitmap;//图片变量
private Bitmap bitmap;//图片变量
private volatile boolean mComplete = false;// 判断遮盖层区域是否消除达到阈值
/**
* 构造函数
*/
public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();//初始化
}
public DrawView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawView(Context context) {
this(context, null);
}
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.glasses).copy(Bitmap.Config.ARGB_8888,true);//初始化图片加载
mPaint = new Paint(); //新建画笔
mPaint.setColor(Color.RED); //设置画笔颜色
mPaint.setStyle(Paint.Style.STROKE);//设置画笔样式
mPaint.setStrokeJoin(Paint.Join.ROUND);//设置结合处样子
mPaint.setStrokeCap(Paint.Cap.ROUND);//设置画笔笔触风格
mPaint.setDither(true);// 设置递色
mPaint.setAntiAlias(true);//设置抗锯齿
mPaint.setStrokeWidth(100);//设置空心线宽
mPath = new Path();//创建新路径
}
//画布绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);
if (!mComplete) //如果还未擦除完成
{
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); //设定目标图模式
canvas.drawBitmap(mBitmap, 0, 0, null);//画图
mCanvas.drawPath(mPath, mPaint); //画布 路径
canvas.drawBitmap(mBitmap, 0, 0, null); //画布图片
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);//宽度
int height = MeasureSpec.getSize(heightMeasureSpec);//高度
mBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);//图片
bitmap = Bitmap.createScaledBitmap(bitmap,width,height,true);//基于原bitmap,创建一个新的宽、高的bitmap
mCanvas = new Canvas(mBitmap); //画布实例化
mCanvas.drawColor(Color.TRANSPARENT);//设置颜色
mCanvas.drawBitmap(bitmap,0,0,null);//画布重绘bitmap
}
}
接下来是mainactivity开始通过有aduiorecordmanger发送过来的message做处理,如果音量分贝大于50,则产生动画效果形成雾
private void getSoundValues(double values){
//话筒分贝大于50,屏幕起雾
if (values >50){
hideView();//隐藏无关控件
drawView.setVisibility(View.VISIBLE); //显示控件
Animation animation = AnimationUtils.loadAnimation(this, R.anim.in_window);//设置透明度
drawView.setAnimation(animation);
audioRecordManger.isGetVoiceRun = false;//设置话筒停止运行
Log.e("玻璃显示","执行");
}
}
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case RECORD://监测话筒
double soundValues = (double) msg.obj;
getSoundValues(soundValues);//获得话筒声音后,屏幕重绘起雾
break;
default:
break;
}
return false;
}
});
这里的anim中的in_window是在res下建立的动画xml文件,用Alpha的值来表示灰的程度,0是完全没有,1是全灰
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="3000"/>
</set>
这个时候当向手机话筒吹气的时候,就会产生雾气。
这时候加入手势擦除功能。
在drawView中加入写一个内部接口让mainactivity来实现
public interface OnCaYiCaCompleteListener{
void complete();
}
private OnCaYiCaCompleteListener mListener;
public void setOnCaYiCaCompleteListener(OnCaYiCaCompleteListener mListener){
this.mListener = mListener;
}
//变量重置
public void setEndValues(){
moveX = 0;
moveY = 0;
mPath.reset(); //路径重置
mComplete =false; //恢复未擦除状态
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
int w = getWidth();
int h = getHeight();
float wipeArea = 0;
float totalArea = w * h;
Bitmap bitmap = mBitmap;
int[] mPixels = new int[w * h];
// 获得Bitmap上所有的像素信息
bitmap.getPixels(mPixels, 0, w, 0, 0, w, h);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
int index = i + j * w;
if (mPixels[index] == 0)
{
wipeArea++;
}
}
}
if (wipeArea > 0 && totalArea > 0)
{
int percent = (int) (wipeArea * 100 / totalArea);
Log.e("TAG", percent + "");
if (percent > 50)
{
// 清除掉图层区域
mComplete = true;
postInvalidate();
}
}
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
moveX = x;
moveY = y;
mPath.moveTo(moveX,moveY);
break;
case MotionEvent.ACTION_MOVE:
int dx = (int)Math.abs(moveX - x);
int dy = (int)Math.abs(moveY - y);
if(dx > 1 || dy > 1){
mPath.quadTo(x,y,(moveX + x) / 2,(moveY + y) / 2);
}
moveX = x;
moveY = y;
break;
case MotionEvent.ACTION_UP:
if(!mComplete){ //如果擦除未完成,则新线程开始
new Thread(mRunnable).start();
}
break;
default:
break;
}
if(!mComplete){
invalidate();
}
return true;
}
再在ondraw中加入,当还未擦除干净的时候则进行释放操作
if(mComplete){
if (mListener != null){
mListener.complete(); //监听结束
setEndValues(); //变量重置
}
}
最后在mainactivity中实现接口
@Override
public void complete() {
showView();
audioRecordManger.getNoiseLevel();
drawView.setVisibility(View.GONE);
}
drawView.setOnCaYiCaCompleteListener(this);
到这里完成吹气起雾和手势擦除功能