参考与:http://www.2cto.com/kf/201505/396842.html,这网站代码跑起来有问题,自己改动了一下,基本上没什么大问题
发现问题可以评论,我会回复的
先贴下效果图
1、三个布局文件
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.weixin_record.MainActivity" > <ListView android: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" > </ListView> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <!-- minHeight消除主界面上的一些间距 --> <com.nickming.view.AudioRecordButton android:id="@+id/recordButton" 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/button_recordnormal" android:gravity="center" android:minHeight="0dp" android:padding="5dp" android:text="@string/normal" android:textColor="#727272" > </com.nickming.view.AudioRecordButton> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#ccc" /> </FrameLayout> </LinearLayout>
dialog_manger.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/dialog_loading_bg" android:gravity="center" android:orientation="vertical" android:padding="20dp" tools:context="com.example.weixin_record.MainActivity" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > <ImageView android:id="@+id/dialog_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/recorder" android:visibility="visible" /> <ImageView android:id="@+id/dialog_voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/v1" android:visibility="visible" /> </LinearLayout> <TextView android:id="@+id/recorder_dialogtext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="@string/shouzhishanghua" android:textColor="#ffffffff" /> </LinearLayout>
item_layout
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:layout_marginTop="5dp" > <ImageView android:id="@+id/item_icon" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="5dp" android:src="@drawable/icon" /> <FrameLayout android:id="@+id/recorder_length" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toLeftOf="@id/item_icon" android:background="@drawable/chatto_bg_focused" > <View android:id="@+id/show_anim01" android:layout_width="25dp" android:layout_height="25dp" android:layout_gravity="center_vertical|right" android:background="@drawable/adj"/> </FrameLayout> <TextView android:id="@+id/recorder_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="3dp" android:layout_toLeftOf="@id/recorder_length" android:text="" android:textColor="#ff777777"/> </RelativeLayout>
2.自定义的类
(1)DialogManger
package com.nickming.view; import com.example.weixin_record.R; import android.app.Dialog; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; /** * * @ClassName: DialogManager * @Description:对话框管理类 * @author: 张 维 * @date: 2016-5-23 下午4:56:03 * */ public class DialogManager { /** * 以下为dialog的初始化控件,包括其中的布局文件 */ private Dialog mDialog; private ImageView mIcon; private ImageView mVoice; private TextView mLable; private Context mContext; public DialogManager(Context context) { mContext = context; } public void showRecordingDialog() { mDialog = new Dialog(mContext,R.style.Theme_audioDialog); // 用layoutinflater来引用布局 LayoutInflater inflater = LayoutInflater.from(mContext); View view = inflater.inflate(R.layout.dialog_manager, null); mDialog.setContentView(view); mIcon = (ImageView) mDialog.findViewById(R.id.dialog_icon); mVoice = (ImageView) mDialog.findViewById(R.id.dialog_voice); mVoice.setBackgroundResource(R.drawable.play02); AnimationDrawable drawable = (AnimationDrawable) mVoice .getBackground(); drawable.start(); mLable = (TextView) mDialog.findViewById(R.id.recorder_dialogtext); mDialog.show(); } /** * 设置正在录音时的dialog界面 */ 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(R.string.shouzhishanghua); } } /** * 取消界面 */ public void wantToCancel() { // TODO Auto-generated method stub if (mDialog != null && mDialog.isShowing()) { mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.GONE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.cancel); mLable.setText(R.string.want_to_cancle); } } // 时间过短 public void timeShort() { // TODO Auto-generated method stub 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(R.string.timeshort); } } // 隐藏dialog public void dimissDialog() { // TODO Auto-generated method stub if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); mDialog = null; } } public void updateVoiceLevel(int level) { // TODO Auto-generated method stub if (mDialog != null && mDialog.isShowing()) { //先不改变它的默认状态 // mIcon.setVisibility(View.VISIBLE); // mVoice.setVisibility(View.VISIBLE); // mLable.setVisibility(View.VISIBLE); //通过level来找到图片的id,也可以用switch来寻址,但是代码可能会比较长 int resId = mContext.getResources().getIdentifier("v" + level, "drawable", mContext.getPackageName()); mVoice.setImageResource(resId); } } }(2)AudioRecordButton
package com.nickming.view; import com.example.weixin_record.R; import com.example.weixin_record.R.string; import com.nickming.view.AudioManager.AudioStageListener; import android.R.bool; 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; /** * * @ClassName: AudioRecordButton * @Description:自定义的button按钮 * @author: 张 维 * @date: 2016-5-23 下午2:13:20 * */ public class AudioRecordButton extends Button implements AudioStageListener { private static final int STATE_NORMAL = 1; private static final int STATE_RECORDING = 2; private static final int STATE_WANT_TO_CANCEL = 3; private static final int DISTANCE_Y_CANCEL = 50; private int mCurrentState = STATE_NORMAL; // 已经开始录音 private boolean isRecording = false; private DialogManager mDialogManager; private AudioManager mAudioManager; private float mTime = 0; // 是否触发了onlongclick,准备好了 private boolean mReady; /** * 先实现两个参数的构造方法,布局会默认引用这个构造方法, * 用一个 构造参数的构造方法来引用这个方法 * @param context */ public AudioRecordButton(Context context) { this(context, null); // TODO Auto-generated constructor stub } public AudioRecordButton(Context context, AttributeSet attrs) { super(context, attrs); mDialogManager = new DialogManager(getContext()); // 这里没有判断储存卡是否存在,有空要判断 String dir = Environment.getExternalStorageDirectory() + "/temp"; mAudioManager = AudioManager.getInstance(dir); mAudioManager.setOnAudioStageListener(this); setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { // TODO Auto-generated method mReady = true; mAudioManager.prepareAudio(); return false; } }); } /** * 录音完成后的回调,回调给activiy,可以获得mtime和文件的路径 * @author nickming * */ public interface AudioFinishRecorderListener{ void onFinished(float mtime,String filePath); } private AudioFinishRecorderListener mListener; public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) { mListener=listener; } // 获取音量大小的runnable private Runnable mGetVoiceLevelRunnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (isRecording) { try { Thread.sleep(100); mTime += 0.1f; mhandler.sendEmptyMessage(MSG_VOICE_CHANGE); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }; // 准备三个常量 private static final int MSG_AUDIO_PREPARED = 0X110; private static final int MSG_VOICE_CHANGE = 0X111; private static final int MSG_DIALOG_DIMISS = 0X112; private Handler mhandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_AUDIO_PREPARED: // 显示应该是在audio end prepare之后回调 mDialogManager.showRecordingDialog(); isRecording = true; new Thread(mGetVoiceLevelRunnable).start(); // 需要开启一个线程来变换音量 break; case MSG_VOICE_CHANGE: mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7)); break; case MSG_DIALOG_DIMISS: break; } }; }; // 在这里面发送一个handler的消息 @Override public void wellPrepared() { // TODO Auto-generated method stub mhandler.sendEmptyMessage(MSG_AUDIO_PREPARED); } /** * 直接复写这个监听函数 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub 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_WANT_TO_CANCEL); } else { changeState(STATE_RECORDING); } } break; case MotionEvent.ACTION_UP://表示用户抬起了手指 // 首先判断是否有触发onlongclick事件,没有的话直接返回reset if (!mReady) { reset(); return super.onTouchEvent(event); } // 如果按的时间太短,还没准备好或者时间录制太短,就离开了,则显示这个dialog if (!isRecording || mTime < 0.6f) { mDialogManager.timeShort();//取消对话框 mAudioManager.cancel(); mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持续1.3s } else if (mCurrentState == STATE_RECORDING) {//正常录制结束 mDialogManager.dimissDialog(); mAudioManager.release();// release释放一个mediarecorder if (mListener!=null) {// 并且callbackActivity,保存录音 mListener.onFinished(mTime, mAudioManager.getCurrentFilePath()); } } else if (mCurrentState == STATE_WANT_TO_CANCEL) { // cancel mAudioManager.cancel(); mDialogManager.dimissDialog(); } reset();// 恢复标志位 break; } return super.onTouchEvent(event); } /** * 回复标志位以及状态 */ private void reset() { // TODO Auto-generated method stub isRecording = false; changeState(STATE_NORMAL); mReady = false; mTime = 0; } 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 (mCurrentState != state) { mCurrentState = state; switch (mCurrentState) { case STATE_NORMAL: setBackgroundResource(R.drawable.button_recordnormal); setText(R.string.normal); break; case STATE_RECORDING: setBackgroundResource(R.drawable.button_recording); setText(R.string.recording); if (isRecording) { mDialogManager.recording(); // 复写dialog.recording(); } break; case STATE_WANT_TO_CANCEL: setBackgroundResource(R.drawable.button_recording); setText(R.string.want_to_cancle); // 取消 mDialogManager.wantToCancel(); break; } } } @Override public boolean onPreDraw() { return false; } }(3)MediaRecorder
package com.nickming.view; import java.io.File; import java.io.IOException; import java.util.UUID; import android.media.MediaRecorder; /** * * @ClassName: AudioManager * @Description: 录音的管理类(准备工作) * @author: 张 维 * @date: 2016-5-23 下午2:10:35 * */ public class AudioManager { private MediaRecorder mRecorder; private String mDirString; private String mCurrentFilePath; private boolean isPrepared;// 是否准备好了 /** * 单例化的方法 * 1 先声明一个static 类型的变量a * 2 在声明默认的构造函数 * 3 再用public synchronized static * 类名 getInstance() { if(a==null) { a=new 类();} return a; } 或者用以下的方法 */ /** * 单例化这个类 */ private static AudioManager mInstance; private AudioManager(String dir) { mDirString=dir; } public static AudioManager getInstance(String dir) { if (mInstance == null) { synchronized (AudioManager.class) { if (mInstance == null) { mInstance = new AudioManager(dir); } } } return mInstance; } /** * 回调函数,准备完毕,准备好后,button才会开始显示录音框 * * @author nickming * */ public interface AudioStageListener { void wellPrepared(); } public AudioStageListener mListener; public void setOnAudioStageListener(AudioStageListener listener) { mListener = listener; } // 准备方法 public void prepareAudio() { try { // 一开始应该是false的 isPrepared = false; File dir = new File(mDirString); //判断对象file是否存在 if (!dir.exists()) { //创建此抽象路径指定的目录,包括所有必须但不存在的父目录。(及可以创建多级目录,无论是否存在父目录) dir.mkdirs(); } String fileNameString = generalFileName(); File file = new File(dir, fileNameString); mCurrentFilePath = file.getAbsolutePath(); mRecorder = new MediaRecorder(); // 设置输出文件 mRecorder.setOutputFile(file.getAbsolutePath()); // 设置meidaRecorder的音频源是麦克风 mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置文件音频的输出格式为amr mRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 设置音频的编码格式为amr mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 严格遵守google官方api给出的mediaRecorder的状态流程图 mRecorder.prepare(); mRecorder.start(); // 准备结束 isPrepared = true; // 已经准备好了,可以录制了 if (mListener != null) { mListener.wellPrepared(); } } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 随机生成文件的名称 * * @return */ private String generalFileName() { //UUID.randomUUID().toString()是javaJDK提供的一个自动生成主键的方法。 return UUID.randomUUID().toString() + ".amr"; } // 获得声音的大小 public int getVoiceLevel(int maxLevel) { // mRecorder.getMaxAmplitude()这个是音频的振幅范围,值域是1-32767 if (isPrepared) { try { // 取证+1,否则去不到7 return maxLevel * mRecorder.getMaxAmplitude() / 32768 + 1; } catch (Exception e) { } } return 1; } // 释放资源 public void release() { // 严格按照api流程进行 mRecorder.stop(); mRecorder.release(); mRecorder = null; } // 取消,因为prepare时产生了一个文件,所以cancel方法应该要删除这个文件, // 这是与release的方法的区别 public void cancel() { release(); if (mCurrentFilePath != null) { File file = new File(mCurrentFilePath); file.delete();//删除文件 mCurrentFilePath = null; } } public String getCurrentFilePath() { return mCurrentFilePath; } }
3.调用的类
(1)RecorderAdapter
package com.example.weixin_record; import java.util.List; import com.example.weixin_record.MainActivity.Recorder; 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; /** * * @ClassName: RecorderAdapter * @Description:list的适配器 * @author: 张 维 * @date: 2016-5-23 下午4:04:02 * */ public class RecorderAdapter extends ArrayAdapter<Recorder> { private LayoutInflater inflater; private int mMinItemWith;// 设置对话框的最大宽度和最小宽度 private int mMaxItemWith; public RecorderAdapter(Context context, List<Recorder> dataList) { super(context, -1, dataList); inflater = LayoutInflater.from(context); // 获取系统宽度 WindowManager wManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wManager.getDefaultDisplay().getMetrics(outMetrics); mMaxItemWith = (int) (outMetrics.widthPixels * 0.7f); mMinItemWith = (int) (outMetrics.widthPixels * 0.15f); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if (convertView == null) { convertView = inflater.inflate(R.layout.item_layout, parent, false); viewHolder=new ViewHolder(); viewHolder.seconds=(TextView) convertView.findViewById(R.id.recorder_time); viewHolder.length=convertView.findViewById(R.id.recorder_length); convertView.setTag(viewHolder); }else { viewHolder=(ViewHolder) convertView.getTag(); } viewHolder.seconds.setText(Math.round(getItem(position).time)+"\""); ViewGroup.LayoutParams lParams=viewHolder.length.getLayoutParams(); lParams.width=(int) (mMinItemWith+mMaxItemWith/60f*getItem(position).time); viewHolder.length.setLayoutParams(lParams); return convertView; } class ViewHolder { TextView seconds;// 时间 View length;// 对话框长度 } }
(2)MediaManager
package com.example.weixin_record; import java.io.IOException; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.util.Log; /** * * @ClassName: MediaManager * @Description:播放的管理类 * @author: 张 维 * @date: 2016-5-23 下午4:04:43 * */ public class MediaManager { private static MediaPlayer mPlayer; private static boolean isPause; public static void playSound(String filePathString, OnCompletionListener onCompletionListener) { if (mPlayer==null) { mPlayer=new MediaPlayer(); //保险起见,设置报错监听 mPlayer.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { Log.i("info", ""); mPlayer.reset(); return false; } }); }else { mPlayer.reset();//就回复 } try { mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setOnCompletionListener(onCompletionListener); mPlayer.setDataSource(filePathString); mPlayer.prepare(); mPlayer.start(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } //停止函数 public static void pause(){ if (mPlayer!=null&&mPlayer.isPlaying()) { mPlayer.pause(); isPause=true; } } //继续 public static void resume() { if (mPlayer!=null&&isPause) { mPlayer.start(); isPause=false; } } public static void release() { if (mPlayer!=null) { mPlayer.release(); mPlayer=null; } } }
(3)MainActivity
package com.example.weixin_record; import java.util.ArrayList; import java.util.List; import com.nickming.view.AudioRecordButton; import com.nickming.view.AudioRecordButton.AudioFinishRecorderListener; import android.app.Activity; import android.graphics.drawable.AnimationDrawable; import android.media.MediaPlayer; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { AudioRecordButton button; private ListView mlistview; private ArrayAdapter<Recorder> mAdapter; private View viewanim; private List<Recorder> mDatas = new ArrayList<MainActivity.Recorder>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mlistview = (ListView) findViewById(R.id.listview); button = (AudioRecordButton) findViewById(R.id.recordButton); button.setAudioFinishRecorderListener(new AudioFinishRecorderListener() { @Override public void onFinished(float seconds, String filePath) { // TODO Auto-generated method stub 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 OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 播放动画 if (viewanim!=null) {//让第二个播放的时候第一个停止播放 viewanim.setBackgroundResource(R.drawable.adj); viewanim=null; } viewanim = view.findViewById(R.id.show_anim01); viewanim.setBackgroundResource(R.drawable.play); AnimationDrawable drawable = (AnimationDrawable) viewanim .getBackground(); drawable.start(); // 播放音频 Log.i("info", "position==="+position); MediaManager.playSound(mDatas.get(position).filePathString, new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { viewanim.setBackgroundResource(R.drawable.adj); } }); } }); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); MediaManager.pause(); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); MediaManager.resume(); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); MediaManager.release(); } class Recorder { float time; String filePathString; public Recorder(float time, String filePathString) { super(); this.time = time; this.filePathString = filePathString; } public float getTime() { return time; } public void setTime(float time) { this.time = time; } public String getFilePathString() { return filePathString; } public void setFilePathString(String filePathString) { this.filePathString = filePathString; } } }
要完整的Demo下载地址:
http://download.csdn.net/detail/zhang106209/9528686