Android开发--仿微信语音对讲录音

自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现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添加权限:

    <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" />

代码参考

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 stubinit(context);}public RecordButton(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stubinit(context);}public RecordButton(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinit(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;}// 录音时显示Dialogprivate 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() {@Overridepublic void run() {recodeTime = 0.0f;while (recordState == RECORD_ON) {{try {Thread.sleep(100);recodeTime += 0.1;// 获取音量,更新dialogif (!isCanceled) {voiceValue = mAudioRecorder.getAmplitude();recordHandler.sendEmptyMessage(1);}} catch (InterruptedException e) {e.printStackTrace();}}}}};@SuppressLint("HandlerLeak")private Handler recordHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {setDialogImage();}};@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (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布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"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" ><ImageView
        android:id="@+id/record_dialog_img"android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextView
        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" /></LinearLayout>

录音时间太短的Toast布局:

<?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:background="@drawable/record_bg"android:padding="20dp"android:gravity="center"android:orientation="vertical" ><ImageView
        android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/voice_to_short" /><TextView
        android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@android:color/white"android:textSize="15sp"android:text="时间太短  录音失败" /></LinearLayout>

自定义的Dialogstyle,对话框样式

    <style name="Dialogstyle"><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>

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;@Overridepublic void ready() {// TODO Auto-generated method stubFile 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;}@Overridepublic void start() {// TODO Auto-generated method stubif (!isRecording) {try {recorder.prepare();recorder.start();} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}isRecording = true;}}@Overridepublic void stop() {// TODO Auto-generated method stubif (isRecording) {recorder.stop();recorder.release();isRecording = false;}}@Overridepublic void deleteOldFile() {// TODO Auto-generated method stubFile file = new File(fileFolder + "/" + fileName + ".amr");file.deleteOnExit();}@Overridepublic double getAmplitude() {// TODO Auto-generated method stubif (!isRecording) {return 0;}return recorder.getMaxAmplitude();}@Overridepublic String getFilePath() {// TODO Auto-generated method stubreturn 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (RecordButton) findViewById(R.id.btn_record);button.setAudioRecord(new AudioRecorder());}@Overridepublic 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;}}

源码下载

点击下载源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25348.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 录音实现方法、仿微信语音、麦克风录音、发送语音、解决5.0以上BUG

本文修改自&#xff1a;http://www.jianshu.com/p/06eca50ddda4 效果图&#xff08;注&#xff01;由于使用的模拟器录制&#xff0c;所以图片中的录音时候话筒上下波动比较小&#xff0c;手机上正常&#xff01;&#xff09;&#xff1a; 使用方法&#xff1a; 录音工具类&…

能听懂语音的ChatGPT来了:10小时录音扔进去,想问什么问什么

类 ChatGPT 模型的输入框里可以粘贴语音文档了。 大型语言模型&#xff08;LLM&#xff09;正在改变每个行业的用户期望。然而&#xff0c;建立以人类语音为中心的生成式人工智能产品仍然很困难&#xff0c;因为音频文件对大型语言模型构成了挑战。 将 LLM 应用于音频文件的一…

weui icon图标大全

转载 https://blog.csdn.net/qq_36433857/article/details/84190038

git 突然不出 绿色,红色的图标了

winr 打开后找到“HKEY_LOCAL_MACHINE–>SOFTWARE–>Microsoft–>Windows–>CurrentVersion–>Explorer–>ShellIconOverlayIdentifiers”这一项 将Tortoise相关的项都提到靠前的位置&#xff08;重命名&#xff0c;在名称之前加几个空格&#xff09; &am…

git绿色、红色图标不显示的问题(有图有真相)

今天在使用git的时候发现项目没有图标显示&#xff0c;感觉很难受&#xff0c;百度搜了好多&#xff0c;感觉这个挺好用的&#xff0c;记录一下。 图标&#xff0c;我们一般情况下就用两个图标&#xff0c;红标和绿标 顾名思义&#xff1a;绿标是指代码提交成功的&#xff0c;红…

exe文件更换图标方法

exe文件更换图标 想更换exe文件的默认图标&#xff0c;但又不想下载什么软件&#xff0c;在网上搜索后找到一种不需要下载特定软件的“欺骗法”。 注意&#xff1a;本方法并不是真的更换了exe文件的图标&#xff0c;而是为exe文件包装了一个壳子&#xff0c;使得外观上看起来更…

如何引入iconfont中的单色图标和多色图标(超简单)

网址&#xff1a;iconfont-阿里巴巴矢量图标库 超简单&#xff01;再不会引入就过分了哈! 一、单色图标引入 1、选择需要的图标 2、创建新项目、添加至该项目、下载到本地 3、将压缩包解压到自己的代码文件夹中 4、在html中link引入并回到网址上复制图标代码 5、以i标签和icon…

VC应用 -个性化你的工具栏图标

1、首先&#xff0c;提供一些最好的图标&#xff0c;大家可以省去自己绘画图标的功夫&#xff0c;可以从繁琐的绘图工作中解脱出来。 ToYcon 在线网页转换PNG-ICON http://converticon.com/ 一个非常好的ICO图标查找网站 http://www.iconfinder.net http://www.iconlet.c…

iconfont 彩色图标的使用

之前写过iconfont单色图标的应用&#xff0c;今天来介绍一下彩色图标的使用 文章目录 一、去iconfont矢量图库下载素材1、挑选自己喜欢的素材2、然后下载到本地&#xff0c;是个压缩包3、把压缩包解压到你的项目底下4、打开解压后的文件5、打开demo_index.html 二、在页面中引入…

下载iconfont图标

iconfont.js&#xff1a; const fs require(‘fs’); const https require(‘https’); // 解压工具&#xff0c;需要安装 const Admzip require(‘adm-zip’); // 项目成员在阿里图标库网站的cookie const cookie require(‘./iconfont.json’).cookie; // 阿里图标库域…

引入icon图标

最新方法:使用svg-sprite-loader引入icon 一.svg-sprite-loader的基本使用 1.在iconfont.cn下载好需要的图标后,把它添加到项目assets里并引入。 那如何引入呢&#xff1f; &#x1f50d;typescript svg cannot find module 方法:在系统文件shims-vue.d.ts里添加如下代码 sr…

Qt 更改exe图标显示

1、制作.ico格式图标。 &#xff08;网上有在线转换工具) 2、将制作好的ico图标重新命名&#xff0c;比如&#xff1a;exe._ico.ico,然后拷贝到项目工程.pro相同路径下。 3、在Qt 项目工程文件下.pro。添加一行 : RC_ICONS exe_ico.ico 4、重新构建可执行程序。

震撼来袭,GPT-4全解读

今年3月&#xff0c;OpenAI团队正式宣布&#xff1a;GPT-4 来了&#xff01; 微信搜索关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 OpenAI发布的GPT-4是深度学习方面的最新里程碑。GPT-4是一个大型的多模态模型&#xff08;可接收图像和文本输入…

chatgpt赋能python:Python发短信给手机:一种快速高效的通讯方式

Python 发短信给手机&#xff1a;一种快速高效的通讯方式 在当今数字化的时代&#xff0c;手机已成为人们日常生活不可或缺的一部分。然而&#xff0c;对于那些需要快速高效地与手机用户进行通讯的人们来说&#xff0c;传统的打电话和发送短信的方式则显得有些过于繁琐。对于这…

chatgpt赋能python:如何使用Python读取手机短信

如何使用Python读取手机短信 在现代社会&#xff0c;短信已成为人们生活中不可或缺的一部分。那么&#xff0c;如何利用Python读取手机短信呢&#xff1f;本文将介绍如何使用Python读取手机短信&#xff0c;并提出一些相关的建议。 什么是Python Python是一种高级的、开源的…

chatgpt赋能python:Python如何免费群发短信

Python如何免费群发短信 在数字化时代&#xff0c;短信成为了快速高效的沟通方式之一。针对群发短信需求&#xff0c;市场上存在着多种短信群发软件&#xff0c;而Python作为一个强大的编程工具&#xff0c;也可以轻松实现免费的短信群发功能。本篇文章将为大家介绍如何通过Py…

chatgpt赋能python:Python短信通知:方便快捷的业务提醒方式

Python短信通知&#xff1a;方便快捷的业务提醒方式 在现代社会&#xff0c;很多企业和组织需要及时地向员工、客户和用户发送通知&#xff0c;以便于协调业务和提高效率。而短信通知作为一种常用的业务提醒方式&#xff0c;已经成为了很多组织中不可或缺的一部分。Python语言…

北京十大律师事务所(排名涉及刑事、拆迁婚姻)

无论任何行业&#xff0c;首先要敬业。你需要了解&#xff1a;这个行业是干什么的&#xff1f;你在行业中处于什么地位&#xff1f;这个行业在社会各界的地位和价值如何&#xff1f;它的基本价值观是什么&#xff1f;作为律师&#xff0c;首先要对行业有敬畏感、尊崇感&#xf…

中国裁判文书网(2020最新版)

1.序言 因业务需要去爬取裁判文书网&#xff0c;查看了网上的诸多教程发现裁判文书网的反爬更新频率很高&#xff0c;但是从19年8月份更新之后再也没有新的更新了。估计是现在的反爬已经足够使用了&#xff0c;裁判文书网的反爬主要有四种。听我一一道来 2.正文 裁判文书网的…

甄选北京十大律师事务所排名榜(资深团队、胜诉率高)

根据司法部数据统计得知&#xff0c;截至2022年第三季度&#xff0c;全国共有执业律师50万多人。全国共有律师事务所4万多家。律师行业正在正确的轨道蓬勃发展中。我们期待律师行业的不断壮大和发展&#xff0c;也希望更多的人能通过法律途径维护自己的合法权益。 大家都知道北…