本文是仿照张鸿洋在慕课网的教学视频《Android-仿微信语音聊天》而作,从某种意义上来说并不能算作纯粹的原创,在此首先向这位大神致敬~

首先展示一下效果。1、当用户按下“按住说话”按钮时,弹出对话框,此时开始录音,并且右边的音量随声音大小而波动。2、如果这时手指向上滑动,则显示取消发送语音的提示。3、当录音结束时,发送语音。4、如果录音时间过短,则对话框给出提示,此次录音失效。

实现此功能的关键在于三个部分:提示对话框,声音录制和录音按钮。

首先讨论录音对话框,共分4种情况。

- 1、默认(不显示对话框)

- 2、正在录音(显示麦克风和音量)

- 3、试图取消(显示箭头)

- 4、时间过短(显示叹号)

根据上面分析,先写出对话框的布局。对话框上排为两张图片,下面为一行提示文字

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="20dp"android:gravity="center"android:background="@drawable/audiorec_dialog_loading_bg"android:orientation="vertical"><LinearLayout
        android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"><ImageView
            android:id="@+id/img_recdlg_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/audiorec_recorder"android:visibility="visible"/><ImageView
            android:id="@+id/img_recdlg_voice"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/audiorec_v1"android:visibility="visible"/></LinearLayout><TextView
        android:id="@+id/txt_recdlg_label"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="@string/str_audiorecdlg_label_recording"android:textColor="@color/white"/></LinearLayout>

并且在styles.xml文件中,加上对话框的样式

<style name="Theme_AudioDialog" parent="@android:style/Theme.Dialog"><item name="android:windowBackground">@android:color/transparent</item><item name="android:windowFrame">@null</item><item name="android:windowNoTitle">true</item><item name="android:windowIsFloating">true</item><item name="android:windowIsTranslucent">true</item>    <!--半透明--><item name="android:backgroundDimEnabled">false</item>  <!--背景变暗-->
</style>

接下来创建一个用于管理对话框的类——RecordDialogManager,并且在类中提供外部调用的方法,使其能够转换成上面说的4中情况。默认状态下,直接把dialog给dismiss掉即可。对于正在录音这种情况,首先我们要创建显示对话框,然后将图片设为对应样式。

    public void showRecordingDialog(){mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.layout_dialog_rec,null);mDialog.setContentView(view);mIcon = (ImageView) mDialog.findViewById(R.id.img_recdlg_icon);mVoice = (ImageView) mDialog.findViewById(R.id.img_recdlg_voice);mLabel = (TextView) mDialog.findViewById(R.id.txt_recdlg_label);mDialog.show();}public void recording(){if (mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.audiorec_recorder);mLabel.setText(R.string.str_audiorecdlg_label_recording);}}

录音过程中,需要动态改变显示音量的大小,因此还需要提供一个调用方法,以改变音量值。这里通过音量值,组成资源引用的名称,然后加载对应的图片。

    /*** 更新声音级别的图片* @param level must be 1-7*/public void updateVoiceLevel(int level){if (mDialog != null && mDialog.isShowing()){//通过level获取resIdint resId = mContext.getResources().getIdentifier("audiorec_v"+level,"drawable",mContext.getPackageName());mVoice.setImageResource(resId);}}

试图取消录音时,需要换掉图片,并且只显示一张图。录音过短与之类似。

    public void wangToCancel(){if (mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.audiorec_cancel);mLabel.setText(R.string.str_audiorecbtn_want_cancel);}}public void tooShort(){if (mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.audiorec_voice_too_short);mLabel.setText(R.string.str_audiorecdlg_label_too_short);}}

当然,文字也要换成对应的。

    <string name="str_audiorecbtn_want_cancel">松开手指,取消发送</string><string name="str_audiorecdlg_label_recording">手指上滑,取消发送</string><string name="str_audiorecdlg_label_too_short">录音时间过短</string>

接下来是声音录制模块,使用MediaRecorder这个类实现录音,并且向外部提供几个方法,用于录音过程的控制。由于我们不希望出现多个录音的实例,因此这个类设为单例模式。

首先是准备录音,这里做一些初始化的操作,并且在完成之后要告知界面准备完毕,以便在界面上显示正在录音的对话框。因此,要提供一个接口,并在准备完成后调用。

    public void prepareAudio()      //准备{String strPath = MediaManager.getInstance().getStoragePath(MediaManager.MediaType.AUDIO_UPLOAD);String fileName = "voice_"+System.currentTimeMillis()+".amr";curFile = new File(strPath,fileName);isPrepared = false;recorder = new MediaRecorder();recorder.setOutputFile(curFile.getAbsolutePath());recorder.setAudioSource(MediaRecorder.AudioSource.MIC);         //音频源为麦克风recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);    //输出文件格式recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);    //音频编码格式try {recorder.prepare();recorder.start();       //已经准备好isPrepared = true;if( null != listener ){listener.onPrepared();}} catch (IOException e) {e.printStackTrace();}}//接口private AudioStateListener listener;public interface AudioStateListener{void onPrepared();      //回调 准备完毕}public void setOnAudioStateListener( AudioStateListener listener){this.listener = listener;}

在录音开始之后,需要不断的获取当前的音量,因此需要提供获取音量的方法

    public int getVoiceVolume( int maxLevel )     //音量等级{if( isPrepared ){try {//振幅范围是 1-32767return maxLevel * recorder.getMaxAmplitude() / 32768 + 1;} catch (Exception e) {}}return 1;}

录音可能被用户取消,也可能是正常的录制结束,因此还需要提供这两个方法。它们的差别在于正常录制结束时需要保留下录音文件,而取消录音时不用。

    public void release()       //释放{recorder.stop();recorder.release();recorder = null;}public void cancel()        //取消{release();if( null != curFile ){curFile.delete();curFile = null;}}

最后讨论录音按钮,这个按钮总共有三种状态:未录音时的状态(STATE_NORMAL)、正在录音时的状态(STATE_RECORDING)和试图取消录音(STATE_WANT_TO_CANCEL)。

由于用户的按下、移动和抬起是操作于这个Button的,因此我们需要记录用户的MotionEvent,并以此改变按钮状态。

此外,在一次录制完成之后,需要给Button所在的Activity提供一个回调的方法,让Activity执行后续的操作(比如上传语音到服务器)。

首先我们来定义按钮的状态和一些记录状态的变量

    //Y方向按住移动此距离后更改状态为试图取消private static final int DISTANCE_Y_CANCEL = 50;//最大声音级别private static final int MAX_VOLUME_LEVEL = 7;//最短录音时长private static final float LEAST_REC_TIME = 1.0f;private static final int STATE_NORMAL = 1;private static final int STATE_RECORDING = 2;private static final int STATE_WANT_TO_CANCEL = 3;private int mCurState = STATE_NORMAL;private boolean isRecording;                //录音准备是否已经完成private boolean mReady;                     //是否已经进入录音状态private float mTime;                        //计时

由于我们要接收录音器准备完成的事件,因此我们需要实现对应的接口,并且在接口回调中显示对话框(这里只写了定义,还需要给VoiceRecorder设置上这个接口)。

对话框的显示,这里用了消息投递的方法,因此还需要创建一个Handler并处理所有可能的信息。除了对话框的显示之外,更新当前音量和关闭对话框也是通过投递消息来实现的。

    VoiceRecorder.AudioStateListener asListener = new VoiceRecorder.AudioStateListener(){@Overridepublic void onPrepared(){mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}};private static final int MSG_AUDIO_PREPARED = 0x100;private static final int MSG_VOICE_CHANGED = 0x101;private static final int MSG_DIALOG_DISMISS = 0x102;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg){switch (msg.what){case MSG_AUDIO_PREPARED:mDialogManager.showRecordingDialog();isRecording = true;new Thread(mGetVolume).start();    //开启新线程,记录录音时间,并不断获取音量break;case MSG_VOICE_CHANGED:mDialogManager.updateVoiceLevel(VoiceRecorder.getInstance().getVoiceVolume(MAX_VOLUME_LEVEL));break;case MSG_DIALOG_DISMISS:mDialogManager.dismissDialog();break;}}};//获取音量大小private Runnable mGetVolume = new Runnable(){@Overridepublic void run(){while ( isRecording ){try{Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);}catch (InterruptedException e){e.printStackTrace();}}}};

接下来我们定义在不同状态下,按钮和对话框的更新。

    private void changeState(int state){if (mCurState != state){mCurState = state;switch (state){case STATE_NORMAL:setBackgroundResource(R.drawable.im_controlbar_inputbox_n);setText(R.string.str_audiorecbtn_normal);break;case STATE_RECORDING:if(mReady == false){mReady = true;VoiceRecorder.getInstance().prepareAudio();}setBackgroundResource(R.drawable.im_controlbar_inputbox_p);setText(R.string.str_audiorecbtn_recording);if (isRecording){mDialogManager.recording();}break;case STATE_WANT_TO_CANCEL:setBackgroundResource(R.drawable.im_controlbar_inputbox_p);setText(R.string.str_audiorecbtn_want_cancel);mDialogManager.wangToCancel();break;default:break;}}}

然后是最关键的部分,通过MotionEvent来更改按钮的当前状态,因此要覆写onTouchEvent方法。由于在按下之后,可能最终要取消录音,所以需要在按下后,用户移动手指时,获得当前的坐标。

    @Overridepublic boolean onTouchEvent(MotionEvent event){int action = event.getAction();int x = (int) event.getX();int y = (int) event.getY();switch (action){case MotionEvent.ACTION_DOWN:        break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:break;}return super.onTouchEvent(event);}
当按下时,一次录音开始,更改状态为STATE_RECORDING
            case MotionEvent.ACTION_DOWN:reset();changeState(STATE_RECORDING);break;
当移动手指时,需要检测是否已经进入或越出了试图取消录音的范围,并以此来更新状态
            case MotionEvent.ACTION_MOVE:if (isRecording){//根据坐标判断是否想要取消if (wantToCancel(x, y)){changeState(STATE_WANT_TO_CANCEL);}else{changeState(STATE_RECORDING);}}break;
接下来是难点,当松开手指后,需要分以下几种情况讨论
-1、如果按下之后立刻抬起手指,状态还没有切换到STATE_RECORDING(虽然几乎不可能)
-2、状态切换到STATE_RECORDING,但是AudioRecorder还没准备完成
-3、AudioRecorder准备完成,但是录音时间太短
-4、正常录音结束
-5、用户取消录音

据此写出对于ACTION_UP的处理

            case MotionEvent.ACTION_UP:if(!mReady)     //状态还没切换{reset();mDialogManager.showRecordingDialog();mDialogManager.tooShort();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);return super.onTouchEvent(event);}if( !isRecording || mTime < LEAST_REC_TIME )    //prepare还没完成 或 录音时间太短{VoiceRecorder.getInstance().cancel();if(STATE_RECORDING == mCurState){mDialogManager.tooShort();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS,1300);}else{mDialogManager.dismissDialog();}}else if (STATE_RECORDING == mCurState)      //正常录制结束{mDialogManager.dismissDialog();VoiceRecorder.getInstance().release();if( listener != null){listener.onRecordFinish(mTime,VoiceRecorder.getInstance().getFilePath());}}else if (STATE_WANT_TO_CANCEL == mCurState)     //取消录音{mDialogManager.dismissDialog();VoiceRecorder.getInstance().cancel();}reset();break;

前文说过,在一次录制完成之后,需要给按钮所在的Activity提供一个回调的方法,因此定义一个接口

    //录音完成回调接口public interface OnRecordFinishListener{void onRecordFinish(float seconds, String fileName);}private OnRecordFinishListener listener;public void setOnRecordFinishListener( OnRecordFinishListener listener ){this.listener = listener;}

至此,录音按钮这个类基本上就完成了。

当然,声音录下来了最终是为了播放,所以我们还需要写一个类用于播放声音,这个用MediaPlayer实现就可以,没什么过多强调的,直接上代码了。

public class MediaManager
{private static MediaManager mInstance;private static final String AUDIO_DIR = "/im/audio";private static final String AUDIO_UPLOAD_DIR = "/im/audio/upload";private MediaManager() {}public static MediaManager getInstance() {if (null == mInstance) {synchronized (MediaManager.class) {if (null == mInstance) {mInstance = new MediaManager();}}}return mInstance;}private MediaPlayer mMediaPlayer;private boolean isPause;     //当前是否暂停public String getStoragePath(MediaType type){if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();File dir = null;switch (type) {case AUDIO:dir = new File(sdcardPath + AUDIO_DIR);break;case AUDIO_UPLOAD:dir = new File(sdcardPath + AUDIO_UPLOAD_DIR);break;}if (!dir.exists()) {dir.mkdirs();}return dir.getAbsolutePath();} else {return null;}}public void playSound(String filePath, MediaPlayer.OnCompletionListener listener) {if (null == mMediaPlayer) {mMediaPlayer = new MediaPlayer();mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {mMediaPlayer.reset();return false;}});} else {mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(listener);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}public void pause() {if (null != mMediaPlayer && mMediaPlayer.isPlaying()) {mMediaPlayer.pause();isPause = true;}}public void resume() {if (null != mMediaPlayer && isPause) {mMediaPlayer.start();isPause = false;}}public void release() {if (null != mMediaPlayer) {mMediaPlayer.release();mMediaPlayer = null;}}public enum MediaType {AUDIO,AUDIO_UPLOAD,}
}

我们在录音按钮那个类里面给Activity提供了一个回调方法,Activity中只需实现这个接口,并完成后续操作即可。

    AudioRecorderButton.OnRecordFinishListener orfListener = new AudioRecorderButton.OnRecordFinishListener() {@Overridepublic void onRecordFinish(float seconds, String fileName)  {uploadAudio(new File(fileName), Math.round(seconds), new Callback() {@Overridepublic void onFailure(Exception e)  {}@Overridepublic void onSuccess()  {//其他操作,添加到listview等等}});}};

源码下载链接:http://download.csdn.net/detail/liusiqian0209/9265237

Android仿微信语音聊天功能相关推荐

  1. android仿微信语音聊天功能,Android仿微信发送语音消息的功能及示例代码

    微信的发送语音是有一个向上取消的,我们使用ontouchlistener来监听手势,然后做出相应的操作就行了. 直接上代码: //语音操作对象 private mediaplayer mplayer ...

  2. java实现仿微信app聊天功能_Android仿微信语音聊天功能

    本文实例讲述了Android仿微信语音聊天功能代码.分享给大家供大家参考.具体如下: 项目效果如下: 具体代码如下: AudioManager.java package com.xuliugen.we ...

  3. android 仿微信语音聊天

    android 仿微信语音聊天 跟着imooc老师学习 代码地址: https://github.com/tingsky9985/Weixin_Recorder

  4. Android仿微信语音聊天界面设计

    这篇文章主要为大家详细介绍了Android仿微信语音聊天界面设计代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间 ...

  5. Android 仿微信语音聊天音量大小显示控件

    某日用微信语音功能聊天,发现当我使用语音功能时,会弹出一个窗口,窗口中间有一个控件会实时的显示我说话声音的大小(即分贝).当时觉得挺好玩,决定也仿制一个,效果如下 分析控件显示效果,可判断左边是一个i ...

  6. Android仿微信语音聊天

    1.项目界面展示: 2.项目代码整体结构简析: (1)AudioRecorderButton类(录音按钮):State:STATE_NORMAL[正常状态].STATE_RECORDERING[正在录 ...

  7. Android 仿微信语音聊天,正式加入字节跳动

    /** 恢复标志位 */ private void resetState() { isRecording = false; isReady = false; changeState(STATE_NOR ...

  8. Android模仿微信语音聊天功能

    项目效果如下: 项目目录结构如下: 代码如下: AudioManager.java package com.xuliugen.weichat;import java.io.File; import j ...

  9. Android 仿微信语音聊天,flutter项目结构

    4.录音类里有两个成员:录音长度,录音路径. 下面贴一下代码: 自定义Button package com.zms.wechatrecorder.view; import com.zms.wechat ...

  10. Android仿微信语音聊天demo

    其实我接触android时间也不是很久,但是发现android远远比我们想象的要有趣并且复杂很多,所以还是要多花点时间来写一写这些demo例子,这个程序是我从慕课网上学来的,因为毕竟要自己手写,才能体 ...

最新文章

  1. 06.SQLServer性能优化之---数据库级日记监控
  2. php 中session与cookies的区别,php中session和cookie的区别
  3. 内存的静态分配和动态分配的区别【转】
  4. 最常见的读入数据方法集锦
  5. macOS的控制台Console.app
  6. 关于 JQuery 的克隆
  7. guava 缓存查询_阿里Java二面难点:Redis缓存穿透、击穿、缓存雪崩方案
  8. Linux下eclipse编译C/C++程序遇到 undefined reference to `pthread_create'的异常解决办法
  9. 语料库与python应用_语料库与Python应用/语料库翻译学文库
  10. Debian Stable分支对于开发者的意义[续软件系统。。。]
  11. 微信8.0.0中的[烟花]\[炸弹]原来还可以这样玩(JAVA脚本)程序员的快乐,很简单,快来看!!!
  12. 漂亮的不像实力派--锤子新品“坚果手机”发布会
  13. 【MISC怼题入门系列】BUU-MISC-page1
  14. 选项卡切换 内容也跟着变 微信小程序
  15. java 给图片加水印图片(水印位置与角度可定义)
  16. 查看计算机内存过高,物理内存过高怎么办,小编教你电脑物理内存过高怎么办...
  17. 珍藏5个在线免费接收国内外手机短信验证码的网络服务
  18. 【明解C语言】之指针初阶详解
  19. kafka干货(五):kakfka的python客户端----Confluent-kafka
  20. 以太坊又分叉了,但是你真的了解分叉吗?

热门文章

  1. 如何绘制四线3格拼音
  2. 永久删除计算机文件怎么操作步骤,如何彻底删除电脑中的文件 永久删除文件方法...
  3. mac 打开网页慢_苹果笔记本打开网页很慢是什么原因
  4. 终止所有mysql进程_如何查找和终止MySQL进程
  5. 使用Python获取100以内的质数
  6. 复合函数高阶求导公式_常用复合函数的导数公式大学微积分常用的复合函数导数,不要推理过程只要导数公式,上课的时候老师是讲了四个,...
  7. JDK8 到 JDK17版本新增特性
  8. metricbeat监控logstash运行状态上报Elasticsearch后Kibana可视化查看
  9. 初谈黑客破解密码的原理
  10. 重磅!程序员工资不会上涨的几个现象