项目效果如下:

项目目录结构如下:

代码如下:

AudioManager.java

package com.xuliugen.weichat;import java.io.File;
import java.io.IOException;
import java.util.UUID;import android.media.MediaRecorder;public class AudioManager {private MediaRecorder mMediaRecorder;private String mDir;private String mCurrentFilePath;private static AudioManager mInstance;private boolean isPrepare;private AudioManager(String dir) {mDir = dir;}public static AudioManager getInstance(String dir) {if (mInstance == null) {synchronized (AudioManager.class) {if (mInstance == null) {mInstance = new AudioManager(dir);}}}return mInstance;}/*** 使用接口 用于回调*/public interface AudioStateListener {void wellPrepared();}public AudioStateListener mAudioStateListener;/*** 回调方法*/public void setOnAudioStateListener(AudioStateListener listener) {mAudioStateListener = listener;}// 去准备public void prepareAudio() {try {isPrepare = false;File dir = new File(mDir);if (!dir.exists()) {dir.mkdirs();}String fileName = generateFileName();File file = new File(dir, fileName);mCurrentFilePath =file.getAbsolutePath();mMediaRecorder = new MediaRecorder();// 设置输出文件mMediaRecorder.setOutputFile(dir.getAbsolutePath());// 设置MediaRecorder的音频源为麦克风mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置音频格式mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置音频编码mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 准备录音mMediaRecorder.prepare();// 开始mMediaRecorder.start();// 准备结束isPrepare = true;if (mAudioStateListener != null) {mAudioStateListener.wellPrepared();}} catch (IllegalStateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}/*** 随机生成文件的名称*/private String generateFileName() {return UUID.randomUUID().toString() + ".amr";}public int getVoiceLevel(int maxlevel) {if (isPrepare) {try {// mMediaRecorder.getMaxAmplitude() 1~32767return maxlevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;} catch (Exception e) {}}return 1;}/*** 释放资源*/public void release() {//mMediaRecorder.stop();mMediaRecorder.reset();mMediaRecorder = null;}/*** 取消录音*/public void cancel() {release();if (mCurrentFilePath != null) {File file = new File(mCurrentFilePath);file.delete();mCurrentFilePath = null;}}public String getCurrentFilePath() {return mCurrentFilePath;}
}

AudioRecorderButton.java

package com.xuliugen.weichat;import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;import com.xuliugen.weichat.R;
import com.xuliugen.weichat.AudioManager.AudioStateListener;public class AudioRecorderButton extends Button {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 mCurrentState = STATE_NORMAL; // 当前的状态private boolean isRecording = false;// 已经开始录音private static final int DISTANCE_Y_CANCEL = 50;private DialogManager mDialogManager;private AudioManager mAudioManager;private float mTime;// 是否触发longClickprivate boolean mReady;private static final int MSG_AUDIO_PREPARED = 0x110;private static final int MSG_VOICE_CHANGED = 0x111;private static final int MSG_DIALOG_DIMISS = 0x112;/** 获取音量大小的线程*/private Runnable mGetVoiceLevelRunnable = new Runnable() {public void run() {while (isRecording) {try {Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);} catch (InterruptedException e) {e.printStackTrace();}}}};private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED:// 显示對話框在开始录音以后mDialogManager.showRecordingDialog();isRecording = true;// 开启一个线程new Thread(mGetVoiceLevelRunnable).start();break;case MSG_VOICE_CHANGED:mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));break;case MSG_DIALOG_DIMISS:mDialogManager.dimissDialog();break;}super.handleMessage(msg);}};/*** 以下2个方法是构造方法*/public AudioRecorderButton(Context context, AttributeSet attrs) {super(context, attrs);mDialogManager = new DialogManager(context);String dir = "/storage/sdcard0/my_weixin";//String dir = Environment.getExternalStorageDirectory()+"/my_weixin";mAudioManager = AudioManager.getInstance(dir);mAudioManager.setOnAudioStateListener(new AudioStateListener() {public void wellPrepared() {mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}});// 由于这个类是button所以在构造方法中添加监听事件setOnLongClickListener(new OnLongClickListener() {public boolean onLongClick(View v) {mReady = true;mAudioManager.prepareAudio();return false;}});}public AudioRecorderButton(Context context) {this(context, null);}/*** 录音完成后的回调*/public interface AudioFinishRecorderListener {void onFinish(float seconds, String filePath);}private AudioFinishRecorderListener audioFinishRecorderListener;public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) {audioFinishRecorderListener = listener;}/*** 屏幕的触摸事件*/@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();int x = (int) event.getX();// 获得x轴坐标int y = (int) event.getY();// 获得y轴坐标switch (action) {case MotionEvent.ACTION_DOWN:changeState(STATE_RECORDING);break;case MotionEvent.ACTION_MOVE:if (isRecording) {// 如果想要取消,根据x,y的坐标看是否需要取消if (wantToCancle(x, y)) {changeState(STATE_WANT_TO_CANCEL);} else {changeState(STATE_RECORDING);}}break;case MotionEvent.ACTION_UP:if (!mReady) {reset();return super.onTouchEvent(event);}if (!isRecording || mTime < 0.6f) {mDialogManager.tooShort();mAudioManager.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1000);// 延迟显示对话框} else if (mCurrentState == STATE_RECORDING) { // 正在录音的时候,结束mDialogManager.dimissDialog();mAudioManager.release();if (audioFinishRecorderListener != null) {audioFinishRecorderListener.onFinish(mTime,mAudioManager.getCurrentFilePath());}} else if (mCurrentState == STATE_WANT_TO_CANCEL) { // 想要取消mDialogManager.dimissDialog();mAudioManager.cancel();}reset();break;}return super.onTouchEvent(event);}/*** 恢复状态及标志位*/private void reset() {isRecording = false;mTime = 0;mReady = false;changeState(STATE_NORMAL);}private boolean wantToCancle(int x, int y) {if (x < 0 || x > getWidth()) { // 超过按钮的宽度return true;}// 超过按钮的高度if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {return true;}return false;}/*** 改变*/private void changeState(int state) {if (mCurrentState != state) {mCurrentState = state;switch (state) {case STATE_NORMAL:setBackgroundResource(R.drawable.btn_recorder_normal);setText(R.string.str_recorder_normal);break;case STATE_RECORDING:setBackgroundResource(R.drawable.btn_recorder_recording);setText(R.string.str_recorder_recording);if (isRecording) {mDialogManager.recording();}break;case STATE_WANT_TO_CANCEL:setBackgroundResource(R.drawable.btn_recorder_recording);setText(R.string.str_recorder_want_cancel);mDialogManager.wantToCancel();break;}}}
}

DialogManager.java

package com.xuliugen.weichat;import android.app.AlertDialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;import com.xuliugen.weichat.R;/*** 用于管理Dialog* * @author xuliugen* */
public class DialogManager {private AlertDialog.Builder builder;private ImageView mIcon;private ImageView mVoice;private TextView mLable;private Context mContext;private AlertDialog dialog;//用于取消AlertDialog.Builder/*** 构造方法 传入上下文*/public DialogManager(Context context) {this.mContext = context;}// 显示录音的对话框public void showRecordingDialog() {builder = new AlertDialog.Builder(mContext, R.style.AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.dialog_recorder,null);mIcon = (ImageView) view.findViewById(R.id.id_recorder_dialog_icon);mVoice = (ImageView) view.findViewById(R.id.id_recorder_dialog_voice);mLable = (TextView) view.findViewById(R.id.id_recorder_dialog_label);builder.setView(view);builder.create();dialog = builder.show();}public void recording(){if(dialog != null && dialog.isShowing()){ //显示状态mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.recorder);mLable.setText("手指上滑,取消发送");}}// 显示想取消的对话框public void wantToCancel() {if(dialog != null && dialog.isShowing()){ //显示状态mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.cancel);mLable.setText("松开手指,取消发送");}}// 显示时间过短的对话框public void tooShort() {if(dialog != null && dialog.isShowing()){ //显示状态mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.voice_to_short);mLable.setText("录音时间过短");}}// 显示取消的对话框public void dimissDialog() {if(dialog != null && dialog.isShowing()){ //显示状态dialog.dismiss();dialog = null;}}// 显示更新音量级别的对话框public void updateVoiceLevel(int level) {if(dialog != null && dialog.isShowing()){ //显示状态
//          mIcon.setVisibility(View.VISIBLE);
//          mVoice.setVisibility(View.VISIBLE);
//          mLable.setVisibility(View.VISIBLE);//设置图片的idint resId = mContext.getResources().getIdentifier("v"+level, "drawable", mContext.getPackageName());mVoice.setImageResource(resId);}}}

MainActivity.java

package com.xuliugen.weichat;import java.util.ArrayList;
import java.util.List;import com.xuliugen.weichat.AudioRecorderButton.AudioFinishRecorderListener;import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;public class MainActivity extends Activity {private ListView mListView;private ArrayAdapter<Recorder> mAdapter;private List<Recorder> mDatas = new ArrayList<MainActivity.Recorder>();private AudioRecorderButton mAudioRecorderButton;private View animView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.id_listview);mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button);mAudioRecorderButton.setAudioFinishRecorderListener(new AudioFinishRecorderListener() {public void onFinish(float seconds, String filePath) {Recorder recorder = new Recorder(seconds, filePath);mDatas.add(recorder);mAdapter.notifyDataSetChanged(); //通知更新的内容mListView.setSelection(mDatas.size() - 1); //将lisview设置为最后一个}});mAdapter = new RecoderAdapter(this, mDatas);mListView.setAdapter(mAdapter);//listView的item点击事件mListView.setOnItemClickListener(new OnItemClickListener() {public void onItemClick(AdapterView<?> arg0, View view,int position, long id) {// 播放动画(帧动画)if (animView != null) {animView.setBackgroundResource(R.drawable.adj);animView = null;}animView = view.findViewById(R.id.id_recoder_anim);animView.setBackgroundResource(R.drawable.play_anim);AnimationDrawable animation = (AnimationDrawable) animView.getBackground();animation.start();// 播放录音MediaManager.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() {public void onCompletion(MediaPlayer mp) {animView.setBackgroundResource(R.drawable.adj);}});}});}@Overrideprotected void onPause() {super.onPause();MediaManager.pause();}@Overrideprotected void onResume() {super.onResume();MediaManager.resume();}@Overrideprotected void onDestroy() {super.onDestroy();MediaManager.release();}class Recorder {float time;String filePath;public Recorder(float time, String filePath) {super();this.time = time;this.filePath = filePath;}public float getTime() {return time;}public void setTime(float time) {this.time = time;}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}}}

MediaManager.java

package com.xuliugen.weichat;import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;public class MediaManager {private static MediaPlayer mMediaPlayer; private static boolean isPause;/*** 播放音乐* @param filePath* @param onCompletionListener*/public static void playSound(String filePath,OnCompletionListener onCompletionListener) {if (mMediaPlayer == null) {mMediaPlayer = new MediaPlayer();//设置一个error监听器mMediaPlayer.setOnErrorListener(new OnErrorListener() {public boolean onError(MediaPlayer arg0, int arg1, int arg2) {mMediaPlayer.reset();return false;}});} else {mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (Exception e) {}}/*** 暂停播放*/public static void pause() {if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的时候mMediaPlayer.pause();isPause = true;}}/*** 当前是isPause状态*/public static void resume() {if (mMediaPlayer != null && isPause) {  mMediaPlayer.start();isPause = false;}}/*** 释放资源*/public static void release() {if (mMediaPlayer != null) {mMediaPlayer.release();mMediaPlayer = null;}}
}

RecoderAdapter.java

package com.xuliugen.weichat;import java.util.List;import android.content.Context;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.TextView;import com.xuliugen.weichat.MainActivity.Recorder;public class RecoderAdapter extends ArrayAdapter<Recorder> {private Context mContext;private List<Recorder> mDatas;private int mMinItemWidth; //最小的item宽度private int mMaxItemWidth; //最大的item宽度private LayoutInflater mInflater;public RecoderAdapter(Context context, List<Recorder> datas) {super(context, -1, datas);mContext = context;mDatas = datas;//获取屏幕的宽度WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f);mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f);mInflater = LayoutInflater.from(context);}/*** 定义一个ViewHolder*/private class ViewHolder {TextView seconds; // 时间View length; // 长度}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null) {convertView = mInflater.inflate(R.layout.item_recoder, parent,false);holder = new ViewHolder();holder.seconds = (TextView) convertView.findViewById(R.id.id_recoder_time);holder.length = convertView.findViewById(R.id.id_recoder_lenght);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.seconds.setText(Math.round(getItem(position).time) + "\"");ViewGroup.LayoutParams lp = holder.length.getLayoutParams();lp.width = (int) (mMinItemWidth + (mMaxItemWidth / 60f)* getItem(position).time);return convertView;}
}

完整项目下载地址:http://download.csdn.net/detail/u010870518/8655667

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

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

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

  2. 模仿微信语音聊天功能(4) 音频播放实现以及项目结束

    在上一篇中,我们实现了核心的录音功能.当然,此时你是没有感觉的,因为我们还没法把它播放出来,所以你还不知道到底有没有录音实现.没读过上一篇的朋友,请点击一下链接: http://www.cnblogs ...

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

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

  4. android 仿微信语音聊天

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

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

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

  6. Android 实现微信语音聊天

    1.效果图 2.代码摘自 imooc 之hyman 视频 代码详情,请移至imooc.com 之 Android-仿微信语音聊天 此处只提供下载 3.源码说明 采用 Android Studio 1. ...

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

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

  8. Android仿微信语音聊天

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

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

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

最新文章

  1. td不显示边框_FANUC Series 0i Mate-TD系统上电显示SP1241 (S)D/A变换器异常维修,FANUC主板SP1_维修中心...
  2. map vs hash_map
  3. Linux驱动中,probe函数何时被调用
  4. sql查询每个学生的最高成绩mysql语句
  5. 使用C#删除一个字符串数组中的空字符串
  6. python正则表达式提取字符串的字母_Python正则表达式提取一部分字符串
  7. C++中的文件输入/输出(6):一些有用的函数
  8. java 检测表情符号_java – 检测String中的字符是否是表情符号(使用Android)
  9. Spring 源码解析 -- SpringWeb请求映射解析
  10. 敏捷开发“松结对编程”系列之八:微软 Tech ed2011 自组织团队与松结对编程讲稿(敏捷开发)...
  11. [蓝牙 4.0 CC2541 开发] BLE架构与OSAL
  12. 基于人机环境系统工程的智慧企业建设思考
  13. android 手机 打印 图片,Mopria打印PDF、TXT文档或图片(适用于Android安卓系统)
  14. 物理学经济学java周易_来自核物理学家的“中医证明”和“经济学降维打击”...
  15. SECS/GEM协议开发系列(一)准备工作
  16. 属性动画cancel
  17. Apsara Clouder基础技能认证-阿里巴巴编程规范考试真题
  18. Saas.为什么要搞Saas,会遇到哪些问题,看看5年Saas开发踩过的坑
  19. 求web嘎嘎厉害的朋友
  20. 四十二、Fluent欧拉模型流化床模拟

热门文章

  1. 微信小程序创建订单号思路(附将带其它符号字符串转换成纯数字字符串)
  2. APS的定义是什么?高级计划与排程APS有哪些痛点?
  3. 【IIS小技巧】将IIS Express改成可以通过ip地址访问
  4. 卷积神经网络的参数计算
  5. python发微信提醒天气冷了注意保暖_天气变冷了,用Python给父母制作一个天气提醒小助手~...
  6. Storm-Kafka: Offset lags for kafka not supported for older versions
  7. 如何把应用程序和资料转移到新的硬盘?
  8. 博图注册表删除方法_回收站被清空文件删除的恢复方法
  9. Dynamics CRM: 权限问题之SecLib::AccessCheckEx2 failed
  10. ssm客户关系管理系统