原文地址:http://www.2cto.com/kf/201502/378704.html

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

实现思路

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添加权限:

1
2
3
<code class="hljs" xml="">    <uses-permission android:name="android.permission.RECORD_AUDIO">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission></uses-permission></uses-permission></code>

代码参考

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<code class="hljs" java="">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);
    }
}
</code>

Dialog布局:

1
2
3
4
5
6
7
8
<code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?-->
<linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
 
    <imageview android:id="@+id/record_dialog_img" android:layout_height="wrap_content" android:layout_width="wrap_content">
 
    <textview android:id="@+id/record_dialog_txt" android:layout_height="wrap_content" android:layout_margintop="5dp" android:layout_width="wrap_content" android:textcolor="@android:color/white">
 
</textview></imageview></linearlayout></code>

录音时间太短的Toast布局:

1
2
3
4
5
6
7
8
<code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?-->
<linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
 
    <imageview android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/voice_to_short">
 
    <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="时间太短" android:textcolor="@android:color/white" android:textsize="15sp">
 
</textview></imageview></linearlayout></code>

自定义的Dialogstyle,对话框样式

1
2
3
4
5
6
7
8
<code applescript="" class="hljs"><style name="Dialogstyle" type="text/css"><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:windowAnimationStyle>@android:style/Animation.Dialog</item>
        <!-- 显示对话框时当前的屏幕是否变暗 -->
        <item name=android:backgroundDimEnabled>false</item></style></code>

RecordStrategy 录音策略接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<code class="hljs" java="">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();
 
}
</code>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<code class="hljs" java="">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;
    }
 
}
</code>

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<code class="hljs" java="">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;
    }
 
}
</code>

Android开发--仿微信语音对讲录音的更多相关文章

  1. Android 高仿微信语音聊天页面高斯模糊效果

    目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...

  2. uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话

    ​ 本篇文章是利用uni-app和nvue实现微信效果功能的第三篇了,今天我们基于uniapp + nvue实现的uniapp仿微信音视频通话插件实例项目,实现了以下功能: 1: 语音通话 2: 视频 ...

  3. html5的audio实现高仿微信语音播放效果

    效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...

  4. [deviceone开发]-仿微信应用(一):框架搭建

    一.简介 这个示例是一步一步跟我学DeviceOne开发 - 仿微信应用系列文档对应的文档.详细介绍了ListView,IndexListView,add方法等常用功能,推荐初学者学习. 二.效果图 ...

  5. [转]Android 超高仿微信图片选择器 图片该这么加载

    快速加载本地图片缩略图的方法: 原文地址:Android 超高仿微信图片选择器 图片该这么加载 其示例代码下载: 仿微信图片选择器 ImageLoader

  6. Android开发 对接微信分享SDK总结

    原文:Android开发 对接微信分享SDK总结 - Stars-One的杂货小窝 公司项目需要对接微信分享,本来之前准备对接友盟分享的,但友盟的分享实际参数太多,而我又只需要对接一个微信分享,于是便 ...

  7. 五、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信的IM聊天APP,支持各类消息收发,音视频通话,附vue实现源码(已开源)-聊天输入框的实现

    会话好友列表的实现 1.项目引言 2.腾讯云后台配置TXIM 3.配置项目并实现IM登录 4.会话好友列表的实现 5.聊天输入框的实现 6.聊天界面容器的实现 7.聊天消息项的实现 8.聊天输入框扩展 ...

  8. android高仿微信拍照、多选、预览、删除(去除相片)相冊功能

    先声明授人与鱼不如授人与渔,仅仅能提供一个思路,当然须要源代码的同学能够私下有偿问我要源代码:QQ:508181017 工作了将近三年时间了,一直没正儿八经的研究系统自带的相冊和拍照,这回来个高仿微信 ...

  9. Android 超高仿微信图片选择器 图片该这么加载

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自:[张鸿洋的博客] 1.概述 关于手机图片加载器,在当今像 ...

随机推荐

  1. Activity Intent Flags及Task相关属性

    转自http://www.cnblogs.com/lwbqqyumidi/p/3775479.html 今天我们来讲一下Activity的task相关内容. 上次我们讲到Activity的四种启动模式 ...

  2. SVN设置钩子

    在post-commit 文件后增加两行: WEB_DIR="/data/www/wb.abc.cn/2.4" /usr/bin/svn update $WEB_DIR --use ...

  3. 常用Java排序算法

    常用Java排序算法 冒泡排序 .选择排序.快速排序 package com.javaee.corejava; public class DataSort { public DataSort() { ...

  4. Spring AOP不拦截从对象内部调用的方法原因

    拦截器的实现原理很简单,就是动态代理,实现AOP机制.当外部调用被拦截bean的拦截方法时,可以选择在拦截之前或者之后等条件执行拦截方法之外的逻辑,比如特殊权限验证,参数修正等操作. 但是最近在项目中 ...

  5. Oracle行转列操作

    有时候我们在展示表中数据的时候,需要将行转为列来显示,如以下形式: 原表结构展示如下:---------------------------产品名称    销售额     季度------------ ...

  6. Premier使用笔记

    在高中时喜欢打CS,也喜欢看高手的视频,一直打到这游戏淘汰了,为了纪念,也尝试着做了一个视频,就是利用该入门级软件.无意中翻出了当时使用这软件时的笔记,整理到这里,可能这些内容对于博客园来讲太垃圾,毫 ...

  7. 删除项目中的.svn信息

    有时候我们新开发一个项目时,会将以前的项目从svn上down下来,然后复制一份.这样就会有个问题,项目中的svn信息就会一直存在.下面介绍删除方法: 1.新建一个.txt的文档.然后将下面代码粘贴到文 ...

  8. Shuffling Machine和双向链表

    1. 双向链表 https://github.com/BodhiXing/Data_Structure 2. Shuffling Machine https://pta.patest.cn/pta/t ...

  9. C# .NET 基本概念

    1. private. protected. public. internal 修饰符的访问权限.   private : 私有成员, 在类的内部才可以访问.    protected : 保护成员, ...

  10. javascript 字符串数组链接

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...