仿微信语音聊天
2016-04-09 11:03
579 查看
分析思路
录音的三种状态
AudioRecorderButton
State : STATE_NORMAL , STATE_RECORDING, STATE_WANT_TO_CANCEL
录音对话框状态
Style :RECORDING ,WANT_TO_CANCEL,TOO_SHORT
AudioManager
prepare();
cancel();
release();
getVoiceLevel();
现在开始写代码:吼吼,需要完整代码的可以去http://download.csdn.net/detail/qq_29502523/9476980下载
需要的依赖 compile ‘com.android.support:appcompat-v7:23.2.1’
MainActivity.java
activity_main.xml
AudioManager
录音的三种状态
AudioRecorderButton
State : STATE_NORMAL , STATE_RECORDING, STATE_WANT_TO_CANCEL
录音对话框状态
Style :RECORDING ,WANT_TO_CANCEL,TOO_SHORT
AudioManager
prepare();
cancel();
release();
getVoiceLevel();
现在开始写代码:吼吼,需要完整代码的可以去http://download.csdn.net/detail/qq_29502523/9476980下载
需要的依赖 compile ‘com.android.support:appcompat-v7:23.2.1’
MainActivity.java
package com.example.myrecorder; import android.graphics.drawable.AnimationDrawable; import android.media.MediaPlayer; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; private ArrayAdapter<Recorder> mAdapter; private List<Recorder> mDatas = new ArrayList<>(); private AudioRecorderButton mAudioRecorderButton; private View animView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findView(); initView(); } private void findView() { mListView = (ListView) findViewById(R.id.id_listview); mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button); } private void initView() { mAudioRecorderButton.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() { @Override public void onFinish(float seconds, String filePath) { Recorder recorder = new Recorder(seconds, filePath); mDatas.add(recorder); mAdapter.notifyDataSetChanged(); mListView.setSelection(mDatas.size() - 1); } }); mAdapter = new RecorderAdapter(this, mDatas); mListView.setAdapter(mAdapter); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (animView != null) { animView.setBackgroundResource(R.drawable.adj); animView = null; } //播放动画 animView = view.findViewById(R.id.id_recorder_anim); animView.setBackgroundResource(R.drawable.play_anim); AnimationDrawable anim = (AnimationDrawable) animView.getBackground(); anim.start(); //播放音频 MediaManager.playSound(mDatas.get(position).filePath, new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { animView.setBackgroundResource(R.drawable.adj); } }); } }); } @Override protected void onPause() { super.onPause(); MediaManager.pause(); } @Override protected void onResume() { super.onResume(); MediaManager.resume(); } @Override protected void onDestroy() { super.onDestroy(); MediaManager.release(); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/id_listview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ebebeb" android:divider="@null" android:dividerHeight="10dp" /> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.example.myrecorder.AudioRecorderButton android:id="@+id/id_recorder_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="7dp" android:layout_marginLeft="50dp" android:layout_marginRight="50dp" android:layout_marginTop="6dp" android:background="@drawable/btn_recorder_normal" android:gravity="center" android:minHeight="0dp" android:padding="16dp" android:text="@string/str_recorder_normal" android:textColor="#727272" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#ccc" /> </FrameLayout> </LinearLayout>
AudioManager
package com.example.myrecorder; import android.media.MediaRecorder; import java.io.File; import java.io.IOException; import java.util.UUID; /** * Created by Administrator on 2016/2/28. */ public class AudioManager { private MediaRecorder mMediaRecorder; private String mDir; private String mCurrentFilePath; private static AudioManager mInstance; private boolean isPrepared; private AudioManager(String dir) { this.mDir = dir; } /** * 回调准备完毕 */ public interface AudioStateListener { void wellPrepared(); } public AudioStateListener mListener; public void setOnAudioStateListener(AudioStateListener listener) { this.mListener = listener; } public static AudioManager getInstance(String dir) { if (mInstance == null) { synchronized (AudioManager.class) { if (mInstance == null) mInstance = new AudioManager(dir); } } return mInstance; } public void prepareAudio() { try { isPrepared = false; File dir = new File(mDir); if (!dir.exists()) dir.mkdirs(); String fileName = genetateFileName(); File file = new File(dir, fileName); mCurrentFilePath = file.getAbsolutePath(); mMediaRecorder = new MediaRecorder(); //设置输出文件 mMediaRecorder.setOutputFile(file.getAbsolutePath()); //设置MediaRecorder的音频源为麦克风 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置音频的格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); //设置音频的编码为amr mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mMediaRecorder.prepare(); mMediaRecorder.start(); //准备结束 isPrepared = true; if (mListener != null) mListener.wellPrepared(); } catch (IOException e) { e.printStackTrace(); } } /** * 随机生成文件的名称 * * @return */ private String genetateFileName() { return UUID.randomUUID().toString() + ".amr"; } /** * 获取声音大小 * * @param maxLevel * @return */ public int getVoiceLevel(int maxLevel) { if (isPrepared) { //mMediaRecorder.getMaxAmplitude() 1-32767 try { return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1; } catch (Exception e) { } } return 1; } public void release() { mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder = null; } public void cancel() { release(); if (mCurrentFilePath != null) { File file = new File(mCurrentFilePath); file.delete(); mCurrentFilePath = null; } } public String getCurrentFilePath() { return mCurrentFilePath; } }
package com.example.myrecorder; 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; public class AudioRecorderButton extends Button implements AudioManager.AudioStateListener { private static final int DISTANCE_Y_CANCEL = 50; private static final int STATE_NORMAL = 1; private static final int STATE_RECORDING = 2; private static final int STATE_WANT_TO_CANCEL = 3; private float mTime; //是否触发longclik private boolean mReady; private int mCurState = STATE_NORMAL; //已经开始录音 private boolean isRecording = false; private DialogManager mDialogManager; private AudioManager mAudioManager; public AudioRecorderButton(Context context) { this(context, null); } public AudioRecorderButton(Context context, AttributeSet attrs) { super(context, attrs); mDialogManager = new DialogManager(getContext()); String dir = Environment.getExternalStorageDirectory() + "/recorder_audios"; mAudioManager = AudioManager.getInstance(dir); mAudioManager.setOnAudioStateListener(this); setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { mReady = true; mAudioManager.prepareAudio(); return false; } }); } 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 Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_AUDIO_PREPARED: //真正显示应该在audio end 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.dismissDialog(); break; default: break; } } }; /** * 录音完成后的回调 */ public interface AudioFinishRecorderListener { void onFinish(float seconds, String filePath); } private AudioFinishRecorderListener mListener; public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) { mListener = listener; } @Override public void wellPrepared() { mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED); } /** * 获取音量大小的Runnable */ private Runnable mGetVoiceLevelRunnable = new Runnable() { @Override public void run() { while (isRecording) { try { Thread.sleep(100); mTime += 0.1f; mHandler.sendEmptyMessage(MSG_VOICE_CHANGED); } catch (InterruptedException e) { e.printStackTrace(); } } } }; @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: //TODO isRecording = true; changeState(STATE_RECORDING); break; case MotionEvent.ACTION_MOVE: if (isRecording) { //根据x,y的坐标,判断是否想要取消 if (wantToCancel(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, 1300); } else if (mCurState == STATE_RECORDING) //正常录制结束 { //release mDialogManager.dismissDialog(); mAudioManager.release(); if (mListener != null) { mListener.onFinish(mTime, mAudioManager.getCurrentFilePath()); } //callbackToAct } else if (mCurState == STATE_WANT_TO_CANCEL) { mDialogManager.dismissDialog(); mAudioManager.cancel(); //cancel } reset(); break; } return super.onTouchEvent(event); } /** * 恢复状态及标志位 */ private void reset() { mReady = false; isRecording = false; mTime = 0; changeState(STATE_NORMAL); } private boolean wantToCancel(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 (mCurState != state) { mCurState = 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_recording); setText(R.string.str_recorder_recording); if (isRecording) { mDialogManager.recording(); } break; case STATE_WANT_TO_CANCEL: setBackgroundResource(R.drawable.btn_recording); setText(R.string.str_recorder_want_cancel); mDialogManager.wantToCancel(); break; default: break; } } } }
package com.example.myrecorder; import android.app.Dialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; /** * Created by R on 2016/2/28. */ public class DialogManager { private Dialog mDialog; private ImageView mIcon; private ImageView mVoice; private TextView mLable; private Context context; public DialogManager(Context context) { this.context = context; } public void showRecordingDialog() { mDialog = new Dialog(context, R.style.Theme_AudioDialog); LayoutInflater inflater = LayoutInflater.from(context); View view = inflater.inflate(R.layout.dialog_recorder, null); mDialog.setContentView(view); mIcon = (ImageView) mDialog.findViewById(R.id.id_recorder_dialog_icon); mVoice = (ImageView) mDialog.findViewById(R.id.id_recorder_dialog_voice); mLable = (TextView) mDialog.findViewById(R.id.id_recorder_dialog_label); mDialog.show(); } public void recording() { if (mDialog != null && mDialog.isShowing()) { mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.VISIBLE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.recorder); mLable.setText("手指上滑,取消发送"); } } public void wantToCancel() { if (mDialog != null && mDialog.isShowing()) { mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.GONE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.cancel); mLable.setText("松开手指,取消发送"); } } public void tooShort() { if (mDialog != null && mDialog.isShowing()) { mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.GONE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.voice_to_short); mLable.setText("录音时间过短"); } } public void dismissDialog() { if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); mDialog = null; } } /** * 通过level去更新voice上的图片 * * @param level 1-7 */ public void updateVoiceLevel(int level) { if (mDialog != null && mDialog.isShowing()) { int resId = context.getResources().getIdentifier("v" + level, "drawable", context.getPackageName()); mVoice.setImageResource(resId); } } }
相关文章推荐
- 挖掘微信Web版通信的全过程 [转]
- ViewPager实现微信主界面
- 微信公众号实现智能聊天-申请免费云服务
- 微信公众号实现智能聊天-接入第三方智能机器人平台
- 微信主动推送消息给用户
- 微信 该连接无法访问问题解决办法
- 微信公众平台教程和SDK收集
- C#开发微信门户及应用(30)--消息的群发处理和预览功能
- Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等 .
- 判断微信内置浏览器的UserAgent
- 蓝牙Ibeacon室内定位和微信摇一摇周边原理分析
- 第三方微信授权登录的iOS代码分析
- 上传media数据到微信,然后获取media_id
- 蓝牙Ibeacon室内定位和微信摇一摇周边原理分析
- 利用微信平台作为飞利浦手机产品客服渠道的可行性调查
- 微信开发视频
- weiPHP微信开发框架
- 在微信内一次报修联想电脑的全过程
- 关于微信二次开发的博客
- zabbix 报警方式之 微信公众号报警(5)