android n对讲功能,Android仿微信语音对讲录音功能

自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的编码时我们也就不能再使用MediaRecorder,而只能使用AudioRecord进行处理)。

效果图:

e3d5c5470c21b2545d342f40ed4f2bac.gif

实现思路:

1.在微信中我们可以看到实现语音对讲的是通过点按按钮来完成的,因此在这里我选择重新自己的控件使其继承自Button并重写onTouchEvent方法,来实现对录音的判断。

2.在onTouchEvent方法中,

当我们按下按钮时,首先显示录音的对话框,然后调用录音准备方法并开始录音,接着开启一个计时线程,每隔0.1秒的时间获取一次录音音量的大小,并通过Handler根据音量大小更新Dialog中的显示图片;

当我们移动手指时,若手指向上移动距离大于50,在Dialog中显示松开手指取消录音的提示,并将isCanceled变量(表示我们最后是否取消了录音)置为true,上移动距离小于20时,我们恢复Dialog的图片,并将isCanceled置为false;

当抬起手指时,我们首先关闭录音对话框,接着调用录音停止方法并关闭计时线程,然后我们判断是否取消录音,若是的话则删除录音文件,否则判断计时时间是否太短,最后调用回调接口中的recordEnd方法。

3.在这里为了适应不同的录音需求,我使用了策略模式来进行处理,将每一个不同的录音方法视为一种不同的策略,根据自己的需要去改写。

注意问题

1.在onTouchEvent的返回值中应该返回true,这样才能屏蔽之后其他的触摸事件,否则当手指滑动离开Button之后将不能在响应我们的触摸方法。

2.不要忘记为自己的App添加权限:

代码参考

RecordButton 类,我们的自定义控件,重新复写了onTouchEvent方法

package com.example.recordtest;

import android.annotation.SuppressLint;

import android.app.Dialog;

import android.content.Context;

import android.os.Handler;

import android.os.Message;

import android.util.AttributeSet;

import android.view.Gravity;

import android.view.LayoutInflater;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Button;

import android.widget.ImageView;

import android.widget.TextView;

import android.widget.Toast;

public class RecordButton extends Button {

private static final int MIN_RECORD_TIME = 1; // 最短录音时间,单位秒

private static final int RECORD_OFF = 0; // 不在录音

private static final int RECORD_ON = 1; // 正在录音

private Dialog mRecordDialog;

private RecordStrategy mAudioRecorder;

private Thread mRecordThread;

private RecordListener listener;

private int recordState = 0; // 录音状态

private float recodeTime = 0.0f; // 录音时长,如果录音时间太短则录音失败

private double voiceValue = 0.0; // 录音的音量值

private boolean isCanceled = false; // 是否取消录音

private float downY;

private TextView dialogTextView;

private ImageView dialogImg;

private Context mContext;

public RecordButton(Context context) {

super(context);

// TODO Auto-generated constructor stub

init(context);

}

public RecordButton(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

// TODO Auto-generated constructor stub

init(context);

}

public RecordButton(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

init(context);

}

private void init(Context context) {

mContext = context;

this.setText("按住 说话");

}

public void setAudioRecord(RecordStrategy record) {

this.mAudioRecorder = record;

}

public void setRecordListener(RecordListener listener) {

this.listener = listener;

}

// 录音时显示Dialog

private void showVoiceDialog(int flag) {

if (mRecordDialog == null) {

mRecordDialog = new Dialog(mContext, R.style.Dialogstyle);

mRecordDialog.setContentView(R.layout.dialog_record);

dialogImg = (ImageView) mRecordDialog

.findViewById(R.id.record_dialog_img);

dialogTextView = (TextView) mRecordDialog

.findViewById(R.id.record_dialog_txt);

}

switch (flag) {

case 1:

dialogImg.setImageResource(R.drawable.record_cancel);

dialogTextView.setText("松开手指可取消录音");

this.setText("松开手指 取消录音");

break;

default:

dialogImg.setImageResource(R.drawable.record_animate_01);

dialogTextView.setText("向上滑动可取消录音");

this.setText("松开手指 完成录音");

break;

}

dialogTextView.setTextSize(14);

mRecordDialog.show();

}

// 录音时间太短时Toast显示

private void showWarnToast(String toastText) {

Toast toast = new Toast(mContext);

View warnView = LayoutInflater.from(mContext).inflate(

R.layout.toast_warn, null);

toast.setView(warnView);

toast.setGravity(Gravity.CENTER, 0, 0);// 起点位置为中间

toast.show();

}

// 开启录音计时线程

private void callRecordTimeThread() {

mRecordThread = new Thread(recordThread);

mRecordThread.start();

}

// 录音Dialog图片随录音音量大小切换

private void setDialogImage() {

if (voiceValue < 600.0) {

dialogImg.setImageResource(R.drawable.record_animate_01);

} else if (voiceValue > 600.0 && voiceValue < 1000.0) {

dialogImg.setImageResource(R.drawable.record_animate_02);

} else if (voiceValue > 1000.0 && voiceValue < 1200.0) {

dialogImg.setImageResource(R.drawable.record_animate_03);

} else if (voiceValue > 1200.0 && voiceValue < 1400.0) {

dialogImg.setImageResource(R.drawable.record_animate_04);

} else if (voiceValue > 1400.0 && voiceValue < 1600.0) {

dialogImg.setImageResource(R.drawable.record_animate_05);

} else if (voiceValue > 1600.0 && voiceValue < 1800.0) {

dialogImg.setImageResource(R.drawable.record_animate_06);

} else if (voiceValue > 1800.0 && voiceValue < 2000.0) {

dialogImg.setImageResource(R.drawable.record_animate_07);

} else if (voiceValue > 2000.0 && voiceValue < 3000.0) {

dialogImg.setImageResource(R.drawable.record_animate_08);

} else if (voiceValue > 3000.0 && voiceValue < 4000.0) {

dialogImg.setImageResource(R.drawable.record_animate_09);

} else if (voiceValue > 4000.0 && voiceValue < 6000.0) {

dialogImg.setImageResource(R.drawable.record_animate_10);

} else if (voiceValue > 6000.0 && voiceValue < 8000.0) {

dialogImg.setImageResource(R.drawable.record_animate_11);

} else if (voiceValue > 8000.0 && voiceValue < 10000.0) {

dialogImg.setImageResource(R.drawable.record_animate_12);

} else if (voiceValue > 10000.0 && voiceValue < 12000.0) {

dialogImg.setImageResource(R.drawable.record_animate_13);

} else if (voiceValue > 12000.0) {

dialogImg.setImageResource(R.drawable.record_animate_14);

}

}

// 录音线程

private Runnable recordThread = new Runnable() {

@Override

public void run() {

recodeTime = 0.0f;

while (recordState == RECORD_ON) {

{

try {

Thread.sleep(100);

recodeTime += 0.1;

// 获取音量,更新dialog

if (!isCanceled) {

voiceValue = mAudioRecorder.getAmplitude();

recordHandler.sendEmptyMessage(1);

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

};

@SuppressLint("HandlerLeak")

private Handler recordHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

setDialogImage();

}

};

@Override

public boolean onTouchEvent(MotionEvent event) {

// TODO Auto-generated method stub

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN: // 按下按钮

if (recordState != RECORD_ON) {

showVoiceDialog(0);

downY = event.getY();

if (mAudioRecorder != null) {

mAudioRecorder.ready();

recordState = RECORD_ON;

mAudioRecorder.start();

callRecordTimeThread();

}

}

break;

case MotionEvent.ACTION_MOVE: // 滑动手指

float moveY = event.getY();

if (downY - moveY > 50) {

isCanceled = true;

showVoiceDialog(1);

}

if (downY - moveY < 20) {

isCanceled = false;

showVoiceDialog(0);

}

break;

case MotionEvent.ACTION_UP: // 松开手指

if (recordState == RECORD_ON) {

recordState = RECORD_OFF;

if (mRecordDialog.isShowing()) {

mRecordDialog.dismiss();

}

mAudioRecorder.stop();

mRecordThread.interrupt();

voiceValue = 0.0;

if (isCanceled) {

mAudioRecorder.deleteOldFile();

} else {

if (recodeTime < MIN_RECORD_TIME) {

showWarnToast("时间太短 录音失败");

mAudioRecorder.deleteOldFile();

} else {

if (listener != null) {

listener.recordEnd(mAudioRecorder.getFilePath());

}

}

}

isCanceled = false;

this.setText("按住 说话");

}

break;

}

return true;

}

public interface RecordListener {

public void recordEnd(String filePath);

}

}

Dialog布局:

android:orientation="vertical"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:gravity="center"

android:background="@drawable/record_bg"

android:padding="20dp" >

android:id="@+id/record_dialog_img"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

android:id="@+id/record_dialog_txt"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textColor="@android:color/white"

android:layout_marginTop="5dp" />

录音时间太短的Toast布局:

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="@drawable/record_bg"

android:padding="20dp"

android:gravity="center"

android:orientation="vertical" >

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/voice_to_short" />

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textColor="@android:color/white"

android:textSize="15sp"

android:text="时间太短 录音失败" />

自定义的Dialogstyle,对话框样式

@android:color/transparent

@null

true

true

true

@android:style/Animation.Dialog

false

RecordStrategy 录音策略接口

package com.example.recordtest;

/**

* RecordStrategy 录音策略接口

* @author acer

*/

public interface RecordStrategy {

/**

* 在这里进行录音准备工作,重置录音文件名等

*/

public void ready();

/**

* 开始录音

*/

public void start();

/**

* 录音结束

*/

public void stop();

/**

* 录音失败时删除原来的旧文件

*/

public void deleteOldFile();

/**

* 获取录音音量的大小

* @return

*/

public double getAmplitude();

/**

* 返回录音文件完整路径

* @return

*/

public String getFilePath();

}

个人写的一个录音实践策略

package com.example.recordtest;

import java.io.File;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

import android.media.MediaRecorder;

import android.os.Environment;

public class AudioRecorder implements RecordStrategy {

private MediaRecorder recorder;

private String fileName;

private String fileFolder = Environment.getExternalStorageDirectory()

.getPath() + "/TestRecord";

private boolean isRecording = false;

@Override

public void ready() {

// TODO Auto-generated method stub

File file = new File(fileFolder);

if (!file.exists()) {

file.mkdir();

}

fileName = getCurrentDate();

recorder = new MediaRecorder();

recorder.setOutputFile(fileFolder + "/" + fileName + ".amr");

recorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风

recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置MediaRecorder录制的音频格式

recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置MediaRecorder录制音频的编码为amr

}

// 以当前时间作为文件名

private String getCurrentDate() {

SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HHmmss");

Date curDate = new Date(System.currentTimeMillis());// 获取当前时间

String str = formatter.format(curDate);

return str;

}

@Override

public void start() {

// TODO Auto-generated method stub

if (!isRecording) {

try {

recorder.prepare();

recorder.start();

} catch (IllegalStateException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

isRecording = true;

}

}

@Override

public void stop() {

// TODO Auto-generated method stub

if (isRecording) {

recorder.stop();

recorder.release();

isRecording = false;

}

}

@Override

public void deleteOldFile() {

// TODO Auto-generated method stub

File file = new File(fileFolder + "/" + fileName + ".amr");

file.deleteOnExit();

}

@Override

public double getAmplitude() {

// TODO Auto-generated method stub

if (!isRecording) {

return 0;

}

return recorder.getMaxAmplitude();

}

@Override

public String getFilePath() {

// TODO Auto-generated method stub

return fileFolder + "/" + fileName + ".amr";

}

}

MainActivity

package com.example.recordtest;

import android.os.Bundle;

import android.app.Activity;

import android.view.Menu;

public class MainActivity extends Activity {

RecordButton button;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

button = (RecordButton) findViewById(R.id.btn_record);

button.setAudioRecord(new AudioRecorder());

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.menu.main, menu);

return true;

}

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值