仿微信语音聊天

这里写图片描述这里写图片描述这里写图片描述这里写图片描述

如上图,是常见的仿微信的聊天程序,实现的效果如上图所示,由于项目太大,本文只讲录音部分。本项目示例代码:https://github.com/xiangzhihong/weixinAudio

主要用到4个核心类:
自定义录音按钮(RecoderButton);
弹框管理类(RecorderDialog);
录音管理类(AudioManager);
录音播放类(MediaManager)。
其中
1.AudioRecordButton状态:
1.STATE_NORMAL:普通状态
2.STATE_RECORDING:录音中
3.STATE_CANCEL:取消录音
2.DialogManager状态:
1.RECORDING:录音中
2.WANT_TO_CANCEL:取消录音
3.TOO_SHORT:录音时间太短
3.AudioManager:
1.prepare():准备状态
2.cancel():取消录音
3.release():正常结束录音
4.getVoiceLevel():获取音量

代码实现

自定义Button,重写onTouchEvent()方法,用于执行长按录音操作。

class AudioRecorderButton{onTouchEvent(){DOWN:changeButtonState(STATE_RECORDING);| DialogManager.showDialog(RECORDING)触发LongClick事件(AudioManager.prepare() --> end prepared --> |       );| getVoiceLevel();//开启一个线程,更新Dialog上的音量等级 MOVE:if(wantCancel(x,y)){DialogManager.showDialog(WANT_TO_CANCEL);更新DialogchangeButtonState(STATE_WANT_TO_CANCEL);更新Button状态}else{DialogManager.showDialog(WANT_TO_CANCEL);changeButtonState(STATE_RECORDING);}UP:if(wantCancel == curState){//当前状态是想取消状态AudioManager.cancel();}if(STATE_RECORDING = curState){if(tooShort){//判断录制时长,如果录制时间过短DialogManager.showDialog(TOO_SHORT);}AudioManager.release();callbackActivity(url,time);//(当前录音文件路径,时长)}}
}

相关的逻辑请查看项目源码。

MediaManager

public class MediaManager {private static MediaPlayer mMediaPlayer;private static boolean isPause;public static void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListener) {if(mMediaPlayer == null){mMediaPlayer = new MediaPlayer();}else {mMediaPlayer.reset();}mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);try {mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}public static void pause(){if(mMediaPlayer != null && mMediaPlayer.isPlaying()){mMediaPlayer.pause();isPause = true;}}public static void resume(){if(mMediaPlayer != null && isPause){mMediaPlayer.start();isPause = false;}}public static void release(){if(mMediaPlayer != null){mMediaPlayer.release();mMediaPlayer = null;}}}

RecorderDialog

录音弹窗类,主要包含录音的各种状态及弹窗。源码如下:

public class RecorderDialog {private Dialog mDialog;private ImageView mIcon;private TextView mLable;private TextView mLeftTime;private Context mContext;public RecorderDialog(Context context) {this.mContext = context;}public void showRecordingDialog() {LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.dialog_recorder, null);mDialog = new Dialog(mContext, R.style.style_dialog);mDialog.setContentView(view);mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);mLable = (TextView) mDialog.findViewById(R.id.recoder_dialog_label);mLeftTime=(TextView) mDialog.findViewById(R.id.recoder_leftTime);mDialog.show();}public void recording() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.recorder_icon);mLable.setText("手指上滑,取消发送");}}public void wantToCancel() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.cancel_recorder_icon);mLable.setText("松开手指,取消发送");}}public void tooShort() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.voice_to_short);mLable.setText("录音时间过短");}}//倒计时提示(10-->0)public void recoderConfirm(int time) {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.GONE);mLeftTime.setVisibility(View.VISIBLE);mLeftTime.setText(time+"");mLable.setText("松开手指,取消发送");}}public void dimissDialog() {if (mDialog != null && mDialog.isShowing()) {mDialog.dismiss();mDialog = null;}}public void setVoiceLevel(int level) {if (mDialog != null && mDialog.isShowing()) {//用switch冗余int resId = mContext.getResources().getIdentifier("v"+level,"mipmap",mContext.getPackageName());}}
}

由于需要用到权限系统,所以需要在配置文件中添加相关的权限。

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

RecoderButton

自定义录音按钮,录音的一切判断都在这个文件。

public class RecoderButton extends TextView implements AudioManager.AudioStateListener {private static final int DISANCE_Y_CANCEL = 50;private static final int STATE_RECORDER_NORMAL = 1; //正常private static final int STATE_RECORDING = 2;  //正在录制private static final int STATE_CANCEL = 3;  //取消private static final int MSG_AUDIO_PREPARED = 0X10;private static final int MSG_AUDIO_CHANGED = 0X11;private static final int MSG_AUDIO_DIMISS = 0X12;private static final int MSG_AUDIO_TIME_OUT = 0X13;private boolean mReady;private int mCurState = STATE_RECORDER_NORMAL;private boolean isRecording = false;//正在录音private float maxTime=10;//最大录制时长private float mTime=0;//录制时长private Timer timer = new Timer();private int leftTime=10;//录音倒计时,10开始提示private RecorderDialog dialog=null;private AudioManager audioManager=null;private FinishRecorderListener mListener;public RecoderButton(Context context) {this(context, null);}public RecoderButton(Context context, AttributeSet attrs) {super(context, attrs);initDialog();initClick();}private void initClick() {setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {mReady = true;audioManager.prepareAudio();return false;}});}private void initDialog() {dialog = new RecorderDialog(getContext());String dir = Environment.getExternalStorageDirectory() + "/recorder";//创建文件夹audioManager = AudioManager.getInstance(dir);audioManager.setOnAudioStateListener(this);}/*** 获取音量大小*/private Runnable mGetVoiceLeveelRunnable = new Runnable() {@Overridepublic void run() {while (isRecording) {try {Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_AUDIO_CHANGED);} catch (InterruptedException e) {e.printStackTrace();}}}};private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED:dialog.showDialog();isRecording = true;new Thread(mGetVoiceLeveelRunnable).start();break;case MSG_AUDIO_CHANGED:dialog.voiceLevel(audioManager.getVoiceLevel(7));System.out.println("录音时间:"+mTime);if (mTime>maxTime){confirmTimer();}break;case MSG_AUDIO_DIMISS:dialog.dimissDialog();break;}}};//录音倒计时private Handler handler = new Handler() {public void handleMessage(Message msg) {leftTime--;if (leftTime<=0){dialog.dimissDialog();audioManager.release();if (mListener != null) {mListener.onFinish(mTime, audioManager.getCurrentFilePath());}return;}dialog.recoderConfirm(leftTime);}};@Overridepublic void wellPrepared() {mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}@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:changeState(STATE_RECORDING);break;case MotionEvent.ACTION_MOVE:if (isRecording) {//根据x y的坐标判断是否想取消if (wantToCancel(x, y)) {changeState(STATE_CANCEL);} else {changeState(STATE_RECORDING);}}break;case MotionEvent.ACTION_UP:if (!mReady) {reset();return super.onTouchEvent(event);}if (!isRecording || mTime < 1.0f) {System.out.println("录音时间过短");dialog.recoderShort();audioManager.cancel();mHandler.sendEmptyMessageDelayed(MSG_AUDIO_DIMISS, 1300);} else if (mCurState == STATE_RECORDING) {System.out.println("正常录制");dialog.dimissDialog();audioManager.release();if (mListener != null) {mListener.onFinish(mTime, audioManager.getCurrentFilePath());}} else if (mCurState == STATE_CANCEL) {System.out.println("取消了");dialog.dimissDialog();audioManager.cancel();}reset();break;}return super.onTouchEvent(event);}//恢复状态及标志位private void reset() {isRecording = false;mReady = false;mTime = 0;changeState(STATE_RECORDER_NORMAL);}private boolean wantToCancel(int x, int y) {if (x < 0 || x > getWidth()) {//判断手指的横坐标是否超出按钮的范围return true;}//再判断Yif (y < -DISANCE_Y_CANCEL || y > getHeight() + DISANCE_Y_CANCEL) {//按钮上部或下部return true;}return false;}//随着状态的改变,文本颜色和背景改变private void changeState(int state) {if (mCurState != state) {mCurState = state;switch (state) {case STATE_RECORDER_NORMAL:setBackgroundResource(R.drawable.recentgle_gray_border);setText("按住 说话");break;case STATE_RECORDING:setBackgroundResource(R.drawable.recentgle_gray);setText("松开结束");if (isRecording) {dialog.recording();}break;case STATE_CANCEL:setBackgroundResource(R.drawable.recentgle_gray);setText("松开手指,取消发送");dialog.cancelRecorder();break;}}}//倒计时定时器private void confirmTimer() {timer.schedule(new TimerTask() {@Override public void run() {try {Thread.sleep(1000);handler.sendEmptyMessage(MSG_AUDIO_TIME_OUT);}catch (Exception e){e.printStackTrace();}}}, 0, 1000);}public interface FinishRecorderListener {void onFinish(float seconds, String filePath);}public void setRecorderListener(FinishRecorderListener listener) {mListener = listener;}
}

最后录制完成后,点击列表的语音会完成播放功能。

MediaManager

public class MediaManager {public static MediaManager instance=null;private  MediaPlayer mMediaPlayer=null;private static boolean isPause=false;public static synchronized MediaManager getInstance() {if (instance == null) {instance = new MediaManager();}return instance;}public  void playSound(String filePath){playSound(filePath,null);}public void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListener) {if(mMediaPlayer == null){mMediaPlayer = new MediaPlayer();}else {mMediaPlayer.reset();}mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);try {mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}public  void pause(){if(mMediaPlayer != null && mMediaPlayer.isPlaying()){mMediaPlayer.pause();isPause = true;}}public  void resume(){if(mMediaPlayer != null && isPause){mMediaPlayer.start();isPause = false;}}public  void release(){if(mMediaPlayer != null){mMediaPlayer.release();mMediaPlayer = null;}}
}

对于聊天列表,是一个比较复杂的逻辑,开发的时候可以重写getItemViewType函数,然后不同的ViewType加载不同的视图,例如我的项目代码如下:

 ChatItem struct = getItem(position);switch (struct.chatType) {case ChatItem.CHAT_TYPE_TIME:return initTimeView(convertView, parent, (String) struct.data);case ChatItem.CHAT_TYPE_GROUP_TIP:return initTipView(convertView, parent, (String) struct.data);case ChatItem.CHAT_TYPE_OUT_TEXT:return initOutTextView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_IN_TEXT:return initInTextView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_OUT_IMAGE:return initOutImageView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_IN_IMAGE:return initInImageView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_OUT_AUDIO:return initOutAudioView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_BONUS_NOTICE:return initBonusNotice(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_RECOMMEND:return initRecommendType(convertView, parent, (Message) struct.data);default:return new View(context);}

如果,你想了解mp3格式相关的内容,可以查看下面的链接:http://blog.csdn.net/omrapollo/article/details/50470659

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

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

相关文章

Android 仿微信实现语音聊天功能

在此感谢鸿洋大神&#xff0c;因为我这是在慕课上看大神的视频做出来的。 代码中我已经添加了很多很多注释&#xff0c;不光是为了大家&#xff0c;也是为了自己能够更加透彻的理解该功能 支持原创&#xff0c;也不算原创了哈哈~ http://blog.csdn.net/lhk147852369/article/…

【Unity人物动画】SALSA With RandomEyes (语音生成嘴型/人物说话) 使用

SALSA使用探索 之前做项目时想实现人物说话的效果&#xff0c;因为我们的语音是AI合成的&#xff0c;有很多片段&#xff0c;如果能根据语音生成嘴部的动画&#xff0c;那将极大便利我们的工作。后面是找到了SALSA的这款插件&#xff0c;并摸索出使用方法。 1 插件介绍 官方网…

仿QQ聊天程序(java)

简易版qq聊天&#xff1a;qq聊天[简易版] (resourcecode.cn) 推荐java最新聊天项目&#xff08;java仿微信聊天&#xff09;: java 简单仿微信聊天(springboot)_Garry1115的博客-CSDN博客_springboot 模拟微信 转载请标明出处&#xff1a;牟尼的专栏 牟尼的博客_CSDN博客-算法…

LaTeX数学公式输入初级入门

LaTeX最强大的功能就是显示美丽的数学公式&#xff0c;下面我们来看这些公式是怎么实现的。 1、数学公式的前后要加上 $ 或 \( 和 \)&#xff0c;比如&#xff1a;$f(x) 3x 7$ 和 \(f(x) 3x 7\) 效果是一样的&#xff1b; 如果用 \[ 和 \]&#xff0c;或者使用 $$ 和 $$&a…

《LaTex》LaTex数学公式简介

LaTex数学公式简介 文章目录 一、引用数学公式的方法二、LaTex数学公式的基本代码1. 符号1.1. 常规的数学符号&#xff1a;直接从键盘输入1.2. 任何1.3. 存在1.4. 属于1.5. 小于等于1.6. 大于等于1.7. 约等于1.8. 更多数学符号 2. 希腊字母2.1. 阿尔法2.2. 贝塔2.3. 伽马2.4. 希…

LaTex数学公式简介

LaTex数学公式简介目录 一、引用数学公式的方法二、LaTex数学公式的基本代码1. 符号1.1. 常规的数学符号&#xff1a;直接从键盘输入1.2 标志符1.3 希腊字母1.4 运算符1.4.1 三角函数1.4.2 极限1.4.3 项数和指数1.4.4 积分1.4.5 矩阵 三、补充四、参考文献 一、引用数学公式的方…

常用数学公式,推导记录

1 组合数计算公式 组合公式的推导由排列公式去掉重复的部分得来。 排列是&#xff0c;从n个不相同元素中取出m个排成一列&#xff08;有序&#xff09;&#xff0c;第一个位置可以有n个选择&#xff0c;第二个位置可以有n-1个选择&#xff08;已经有1个放在前一个位置&#xff…

CMU 开源数学神器,可快速将数学公式转为精美图表!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 转自机器之心 在有些人眼里&#xff0c;数学公式就是一堆数字和符号&#xff0c;但在另一些人看来&#xff0c;这些数字和符号是可以动的&#xff0c;而且极富美感。为什么会有这种差距&am…

Markdown / KaTex数学公式汇总

目录 【LaTex和KaTex】【软件推荐 Mathpix】一、如何插入公式二、上下标三、常用运算符四、高级运算符五、常用数学符号六、特殊符号6.1 箭头6.2 公式序号 七、括号使用八、矩阵九、集合运算十、希腊字母十一、字符大小 【LaTex和KaTex】 LaTeX是一种基于ΤΕΧ的排版系统&…

Latex数学公式表

1. Latex的两种公式模式 行间(inline)模式&#xff1a;即在正文中插入数学内容。行间公式用$ … $ 独立(display)模式&#xff1a;独立成行&#xff0c;可以有或没有编号。无编号用\ [ … \ ] 2.基本元素 希腊字母 标注 上下标 分数 运算符 求和&#xff08;\sum&#xff09;&…

陶哲轩预言成真!MIT加州理工让ChatGPT证明数学公式,数学成见证AI重大突破首个学科

来源 | 新智源 ID | AI-era 大语言模型&#xff0c;可以用来证明数学定理了&#xff01; 「数学天才」陶哲轩曾在一篇博客中称&#xff0c;2026年&#xff0c;AI将与搜索和符号数学工具相结合&#xff0c;成为数学研究中值得信赖的合著者。 这个预言&#xff0c;如今已经成…

Typora数学公式大全

Typora数学公式大全 typora做笔记时候经常要插入数学公式&#xff0c;但是好多符号难以记忆&#xff0c;所以该文总结个人常用符号便于查找 1 常见希腊字母 公式代码公式代码公式代码\alphaα\xiξ\omegaω / Ω\betaβ\zetaζ\phiΦ / φ\deltaδ / Δ\DeltaΔ\piπ\epsilo…

福布斯发布2023全球亿万富豪榜

中国内地亿万富豪人数仍位居全球第二&#xff0c;仅次于美国。 对于全球富豪们来说&#xff0c;过去的一年充满挑战。利率骤增、股价承压、独角兽估值受挫&#xff0c;无一不对全球企业经营造成巨大影响。 2023福布斯全球亿万富豪榜的上榜人数较去年下降28位&#xff0c;总人…

智能音箱来了,语音交互设计的一点认知

语音用户界面&#xff08;或VUI&#xff09;是一种交互模型&#xff0c;在该模型中&#xff0c;人与机器进行交互&#xff0c;并至少部分通过使用语音来执行一组任务。 实际上&#xff0c;这种方式的交互式语音应答&#xff08;IVR&#xff09;系统在银行业和旅游业中已经得到了…

简易聊天室app实现:Unity+C# 客户端,Java 服务器端

UnityC# 客户端 UI界面根据个人喜好排版 图1 进入界面 图2 聊天界面 C#脚本代码如下&#xff1a; Client_dxc.cs using UnityEngine; using System.Net.Sockets; using System.Text; using System; using UnityEng…

解放生产力的 AI 神器来了

苏生不惑第405 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 今天分享几个解放生产力的AI写作神器&#xff0c;首先是腾讯出品的智能写作助手 https://effidit.qq.com/ &#xff0c;文字工作者的福音&#xff0c;提供智能纠错、文本补全、文本改…

医学图像配准论文学习:解剖学约束和注意力引导的深层特征融合,用于关节分割和可变形医学图像配准(2023)

原文链接&#xff1a;Anatomically constrained and attention-guided deep feature fusion for joint segmentation and deformable medical image registration 一、Hilghtlights&#xff1a; 利用分割可以在配准过程中培养先验知识。多任务学习策略提高了配准性能。所提出的…

学习遥感大模型(1)

借着课程作业的机会学习了一些遥感大模型相关的工作&#xff0c;现总结一下 根据非盈利组织忧思科学家联盟&#xff08;The Union of Concerned Scientists&#xff09;&#xff0c;截止2023年1月&#xff0c;全球对地观测卫星在轨数目已超过1000颗。大量的对地观测卫星很大程…

影像组学 - pyradiomics库 - 入门1

目录 一、了解 影像组学 影像组学是什么&#xff08;引用一下chatgpt的解释&#xff09; 影像组学的基本步骤 影像组学的作用与价值 影像组学有哪些特征 二、了解 pyradiomics pyradiomics是什么 pyradiomics使用步骤 pyradiomics的作用与价值 三、正文开始&#xff…

网络安全数据集介绍

目录 1、KDD99 入侵检测数据集 2、NSL KDD 入侵检测数据集 3、UNSW_NB15 入侵检测数据集 4、NASA MDP 软件缺陷数据集 5、CNNVD 软件安全漏洞数据集 6、NVD 软件安全漏洞数据集 1、KDD99 入侵检测数据集 该数据集是从一个模拟的美国空军局域网上采集来的9个星期的网络连…