仿微信,录制音频并发送功能

来源:互联网 发布:视频大头是什么软件 编辑:程序博客网 时间:2024/04/29 17:01

MyRecorder(仿微信,录制音频并发送功能)

这里写图片描述

①布局实现(activity_main.xml)
布局采用线性布局,上面使用的一个ListView,下面使用的是一个自定义的Button(会在下面进行介绍)

<?xml version="1.0" encoding="utf-8"?><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.yitong.myrecorder.MainActivity">    <ListView        android:id="@+id/main_listview"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:background="#ebebeb"        android:dividerHeight="10dp"        android:divider="@null"        />    <FrameLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="#fff">        <com.yitong.myrecorder.view.AudioRecorderButton            android:id="@+id/main_btn"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:layout_marginTop="6dp"            android:layout_marginBottom="6dp"            android:layout_marginLeft="60dp"            android:layout_marginRight="60dp"            android:minHeight="0dp"            android:padding="6dp"            android:text="@string/str_recoder_normal"            android:textSize="20sp"            android:textColor="#727272"            android:background="@drawable/btn_recorder_normal"            />        <View            android:layout_width="match_parent"            android:layout_height="1dp"            android:background="#ccc"/>    </FrameLayout></LinearLayout>相关使用的string值(需要添加到value/string中):    <string name="str_recoder_normal">按住说话</string>    <string name="str_recorder_recording">松开结束</string>    <string name="str_recorder_want_cancel">松开手指,取消发送</string>    <string name="str_dialog_want_cancel">手指上滑,取消发送</string>    <string name="str_dialog_want_send">手指上滑,取消发送</string>    <string name="str_dialog_time_short">录音时间过短</string>

②我们分析一下自定Button的几种状态:
1.正常状态 (在初次显示,即没有点击的时候显示的状态,显示的文本为“按住说话”)
2.录音状态 (当手指按在Button上时,即为录音状态,显示的文本为“松开结束”)
3.取消状态 (当手指上滑,此时若松开手指,便取消发送,即为取消状态,显示的文本为“松开手指,取消发送”)

③当录音状态时,在View上有一个Dialog的提示,首先我们先自定义这个Dialog的布局:
自定Dialog的效果

<?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/dialog_bg"              android:orientation="vertical"              android:padding="20dp">    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:orientation="horizontal">        <ImageView            android:id="@+id/main_recorder_dialog_icon"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@mipmap/recorder"            android:visibility="visible"/>        <ImageView            android:id="@+id/main_recorder_dialog_voice"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@mipmap/v1"            android:visibility="visible"/>    </LinearLayout>    <TextView        android:id="@+id/main_recorder_dialog_label"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:layout_marginTop="5dp"        android:text="@string/str_dialog_want_cancel"        android:textColor="#fff"        android:textSize="20sp"/></LinearLayout>其中用到的@drawable/dialog_bg即为自定的shape    <?xml version="1.0" encoding="utf-8"?>    <shape xmlns:android="http://schemas.android.com/apk/res/android"           android:shape="rectangle">        <corners android:radius="12dp"/>        <solid android:color="#a3d7f5"/>        <stroke            android:width="1dp"            android:color="#9b9b9b"/>    </shape>

④定义DialogManager,便于对这个自定义布局的Dialog进行操作

public class DialogManager {    private static final String TAG = "DialogManager";    private Dialog mDialog;    private ImageView mIcon;    private ImageView mVoice;    private TextView mLabel;    private Context mContext;    public DialogManager(Context mContext) {        this.mContext = mContext;    }    /**     * 显示对话框     */    public void showRecordeingDialog() {        mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);        View view = inflater.inflate(R.layout.dialog, null);        mDialog.setContentView(view);        mIcon = (ImageView) mDialog.findViewById(R.id.main_recorder_dialog_icon);        mVoice = (ImageView) mDialog.findViewById(R.id.main_recorder_dialog_voice);        mLabel = (TextView) mDialog.findViewById(R.id.main_recorder_dialog_label);        mDialog.show();    }    /**     * 正在录制提示     */    public void recording() {        if (mDialog != null && mDialog.isShowing()) {            mIcon.setVisibility(View.VISIBLE);            mVoice.setVisibility(View.VISIBLE);            mLabel.setVisibility(View.VISIBLE);            mIcon.setImageResource(R.mipmap.recorder);            mLabel.setText(R.string.str_dialog_want_send);        }    }    /**     * 取消录制对话框提示     */    public void wantToCancel() {        if (mDialog != null && mDialog.isShowing()) {            mIcon.setVisibility(View.VISIBLE);            mVoice.setVisibility(View.VISIBLE);            mLabel.setVisibility(View.VISIBLE);            mIcon.setImageResource(R.mipmap.recorder);            mLabel.setText(R.string.str_recorder_want_cancel);        }    }    /**     * 录音时间过短提示     */    public void tooShort() {        if (mDialog != null && mDialog.isShowing()) {            mIcon.setVisibility(View.VISIBLE);            mVoice.setVisibility(View.VISIBLE);            mLabel.setVisibility(View.VISIBLE);            mIcon.setImageResource(R.mipmap.recorder);            mLabel.setText(R.string.str_dialog_time_short);        }    }    /**     * 取消对话框     */    public void dismissDialog() {        if (mDialog != null && mDialog.isShowing()) {            mDialog.dismiss();            mDialog = null;        }    }    /**     * 显示音量大小     */    public void updateVoiceLevel(int level) {        if (mDialog != null && mDialog.isShowing()) {            int resId = mContext.getResources().getIdentifier("v" + level, "mipmap", mContext.getPackageName());            mVoice.setImageResource(resId);        }    }}Dialog的样式Theme_AudioDialog,需要在values/styles.xml中定义    <style name="Theme_AudioDialog">        <item name="android:windowBackground">@android:color/transparent</item>        <item name="android:windowFrame">@null</item>        <item name="android:windowIsFloating">true</item>        <item name="android:windowIsTranslucent">true</item>        <item name="android:backgroundDimEnabled">false</item>    </style>

⑤当手指按住Button时,便开始录音,所以我们还需要定义一个录音的管理类AudioManager来控制录制状态。

public class AudioManager {    private MediaRecorder mMediaRecorder;    private String mDir;// 保存的目录    private String mCurrentFilePath;// 保存音频文件的全路径    private boolean isPrepared = false;// 是否准备完毕    private AudioManager(String dir) {        mDir = dir;    }    private static AudioManager mInstance;    public static AudioManager getmInstance(String mDir) {        if (mInstance == null) {            synchronized (AudioManager.class) {                if (mInstance == null) {                    mInstance = new AudioManager(mDir);                }            }        }        return mInstance;    }    /**     * 准备完毕的回调     */    public interface AudioStateListener {        void wellPrepared();    }    private AudioStateListener mListener;    public void setAudioStateListener(AudioStateListener listener) {        mListener = listener;    }    /** 准备录制 */    public void prepareAudio() {        try {            isPrepared = false;            File dir = new File(mDir);            if (!dir.exists()) {                dir.mkdirs();            }            String fileName = generateName();            File file = new File(dir, fileName);            mCurrentFilePath = file.getAbsolutePath();            mMediaRecorder = new MediaRecorder();            // 设置输出文件            mMediaRecorder.setOutputFile(mCurrentFilePath);            // 设置音频源为麦克风            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);            // 设置音频格式            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);            // 设置音频编码            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);            mMediaRecorder.prepare();            mMediaRecorder.start();            isPrepared = true;            if (mListener != null) {                mListener.wellPrepared();            }        } catch (Exception e) {            e.printStackTrace();        }    }    /** 获取音量大小 */    public int getVoiceLevel(int maxLevel) {        if (isPrepared) {            try {                //mMediaRecorder.getMaxAmplitude() 1-32767                //注意此处mMediaRecorder.getMaxAmplitude 只能取一次,如果前面取了一次,后边再取就为0了                return ((mMediaRecorder.getMaxAmplitude() * maxLevel) / 32768) + 1;            } catch (Exception e) {            }        }        return 1;    }    /** 保存录音,释放资源 */    public void release() {        if(mMediaRecorder != null) {            mMediaRecorder.stop();            mMediaRecorder.release();            mMediaRecorder = null;        }    }    /** 取消录制 */    public void cancel() {        release();        if(mCurrentFilePath != null) {            File file = new File(mCurrentFilePath);            if(file.exists()) {                file.delete();                mCurrentFilePath = null;            }        }    }    /** 获取录制音频的总路径 */    public String getmCurrentFilePath(){        return mCurrentFilePath;    }    /**     * 生成一个随机名字     */    private String generateName() {        return UUID.randomUUID().toString() + ".mp3";    }}

⑥处理完DialogManager和AudioManger后,接着我们回到自定义的Button,即AudioRecorderButton

public class AudioRecorderButton extends Button implements AudioManager.AudioStateListener {    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 String TAG = "AudioRecorderButton";    private int mCurState = STATE_NORMAL;//当前状态    private boolean isRecording = false;//是否正在录音    private DialogManager mDialogManger;    private AudioManager mAudioManager;    private boolean mReady = false;//是否触发longClick    private float mTime;//计时    public AudioRecorderButton(Context context) {        this(context, null);    }    public AudioRecorderButton(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public AudioRecorderButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mDialogManger = new DialogManager(getContext());        String dir = Environment.getExternalStorageDirectory() + "/my_recorder_audios";        mAudioManager = AudioManager.getmInstance(dir);        mAudioManager.setAudioStateListener(this);        setOnLongClickListener(new OnLongClickListener() {            @Override            public boolean onLongClick(View v) {                mReady = true;                mAudioManager.prepareAudio();                return false;            }        });    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                changeSate(STATE_RECORDING);                break;            case MotionEvent.ACTION_MOVE:                if (isRecording) {                    if (isCancelRecorder(x, y)) {                        changeSate(STATE_WANT_TO_CANCEL);                    } else {                        changeSate(STATE_RECORDING);                    }                }                break;            case MotionEvent.ACTION_UP:                if (!mReady) {                    reset();                    return super.onTouchEvent(event);                }                if (!isRecording || mTime < 0.6f) {                    mDialogManger.tooShort();                    mAudioManager.cancel();                    mHandler.sendEmptyMessageDelayed(MSG_LODING_DISMISS, 1000);                } else if (mCurState == STATE_RECORDING) {//正常录制结束                    mDialogManger.dismissDialog();                    mAudioManager.release();                    if (mListener != null) {                        mListener.onFinish(mTime, mAudioManager.getmCurrentFilePath());                    }                } else if (mCurState == STATE_WANT_TO_CANCEL) {                    mDialogManger.dismissDialog();                    mAudioManager.cancel();                }                reset();                break;        }        return super.onTouchEvent(event);    }    /**     * 根据不同状态,更改不同的文字和显示的背景     */    private void changeSate(int stateRecording) {        if (mCurState != stateRecording) {            mCurState = stateRecording;            switch (mCurState) {                case STATE_NORMAL:                    setBackgroundResource(R.drawable.btn_recorder_normal);                    setText(R.string.str_recoder_normal);                    break;                case STATE_RECORDING:                    setBackgroundResource(R.drawable.btn_recording);                    setText(R.string.str_recorder_recording);                    if (isRecording) {                        mDialogManger.recording();                    }                    break;                case STATE_WANT_TO_CANCEL:                    setBackgroundResource(R.drawable.btn_recording);                    setText(R.string.str_recorder_want_cancel);                    mDialogManger.wantToCancel();                    break;            }        }    }    /**     * 根据移动后的位置,判断是否取消录音     */    private boolean isCancelRecorder(int x, int y) {        if (x < 0 || x > getWidth() || y < 0 || y > getHeight()) {            return true;        }        return false;    }    /**     * 重置标识位     */    private void reset() {        changeSate(STATE_NORMAL);        isRecording = false;        mReady = false;        mTime = 0;    }    /**     * 开始播放时回调此方法     */    @Override    public void wellPrepared() {        mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);    }    private static final int MSG_AUDIO_PREPARED = 0x110;    private static final int MSG_VOICE_CHAGE = 0x111;    private static final int MSG_LODING_DISMISS = 0x112;    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what) {                case MSG_AUDIO_PREPARED:                    mDialogManger.showRecordeingDialog();                    isRecording = true;                    new Thread(mGetVoiceLevelRunnable).start();                    break;                case MSG_VOICE_CHAGE:                    mDialogManger.updateVoiceLevel(mAudioManager.getVoiceLevel(7));                    break;                case MSG_LODING_DISMISS:                    mDialogManger.dismissDialog();                    break;            }        }    };    /**     * 获取音量大小,并计时     */    private Runnable mGetVoiceLevelRunnable = new Runnable() {        @Override        public void run() {            while (isRecording) {                SystemClock.sleep(100);                mTime += 0.1f;                mHandler.sendEmptyMessage(MSG_VOICE_CHAGE);            }        }    };    /**     * 完成录制后的回调接口     */    public interface AudioFinishRecorderListener {        void onFinish(float time, String filePath);    }    private AudioFinishRecorderListener mListener;    public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) {        mListener = listener;    }}

=====================至此自定义Button就定义完===================================

①接着我们回到了MainActivity,我们需要获取ListView和AudioRecorderButton组件。对于ListView,需要定义Adapter,当点击某个条目的需要把录制的音频播放出来,需要定义一个MediaManager来控制音频的播放。

②首先我们先定义RecorderAdapter

/** * 音频实体类,包含音频的长度和保存的路径 */public class Recorder implements Serializable {    private int time;    private String filePath;    public Recorder() {    }    public Recorder(int time, String filePath) {        this.time = time;        this.filePath = filePath;    }    public void setTime(int time) {        this.time = time;    }    public void setFilePath(String filePath) {        this.filePath = filePath;    }    public float getTime() {        return time;    }    public String getFilePath() {        return filePath;    }}/** * 继承ArrayAdater,重写getView方法 */public class RecorderAdapter extends ArrayAdapter<Recorder> {    private List<Recorder> mDatas;    private Context mContext;    private LayoutInflater mInfalter;    private int mMinItemWidhth;    private int mMaxItemWidhth;    public RecorderAdapter(Context context, List<Recorder> datas) {        super(context, -1, datas);        mDatas = datas;        mContext = context;        mInfalter = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        DisplayMetrics outMetrics = new DisplayMetrics();        wm.getDefaultDisplay().getMetrics(outMetrics);        mMaxItemWidhth = (int) (outMetrics.widthPixels * 0.7f);        mMinItemWidhth = (int) (outMetrics.widthPixels * 0.15f);    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder = new ViewHolder();        if(convertView == null) {            convertView = mInfalter.inflate(R.layout.item_recorder, null);        }        holder = holder.getHolder(convertView);        holder.setView(holder, mDatas.get(position));        return convertView;    }    private class ViewHolder{        TextView time;        View length;        public ViewHolder getHolder(View view){            ViewHolder holder = (ViewHolder) view.getTag();            if(holder == null) {                holder = new ViewHolder();            }            holder.time = (TextView) view.findViewById(R.id.item_recorder_time);            holder.length = view.findViewById(R.id.item_recorder_length);            view.setTag(holder);            return holder;        }        public void setView(ViewHolder holder, Recorder recorder) {            holder.time.setText(recorder.getTime() + "\"");            ViewGroup.LayoutParams layoutParams = holder.length.getLayoutParams();            layoutParams.width = (int) (mMinItemWidhth + (mMaxItemWidhth / 60f * recorder.getTime()));        }    }}

③定义MediaManger,用于播放音频

public class MediaManager {    private static MediaPlayer mMediaPlayer;    private static boolean isPause = false;//是否是暂停    /**     * 播放音频     */    public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) {        if (mMediaPlayer == null) {            mMediaPlayer = new MediaPlayer();            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {                @Override                public boolean onError(MediaPlayer mp, int what, int extra) {                    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) {            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;        }    }}

④MainActivity的实现

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private List<Recorder> mDatas = new ArrayList<Recorder>();    private AudioRecorderButton mAudioRecorderButton;    private ListView mListView;    private RecorderAdapter mAdapter;    private View mAnimView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();        initAction();    }    private void initView() {        mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.main_btn);        mListView = (ListView) findViewById(R.id.main_listview);    }    private void initAction() {        mAudioRecorderButton.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {            @Override            public void onFinish(float time, String filePath) {                Recorder recorder = new Recorder((int)time, 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) {                // 播放帧动画                mAnimView = view.findViewById(R.id.item_anim);                mAnimView.setBackgroundResource(R.drawable.play_anim);                AnimationDrawable anim = (AnimationDrawable) mAnimView.getBackground();                anim.start();                // 播放音频                MediaManager.playSound(mDatas.get(position).getFilePath(), new MediaPlayer.OnCompletionListener() {                    @Override                    public void onCompletion(MediaPlayer mp) {                        mAnimView.setBackgroundResource(R.mipmap.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();    }}帧动画play_anim定义在drawable下    <?xml version="1.0" encoding="utf-8"?>    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"        >        <item            android:drawable="@mipmap/v_anim1"            android:duration="300"/>        <item            android:drawable="@mipmap/v_anim2"            android:duration="300"/>        <item            android:drawable="@mipmap/v_anim3"            android:duration="300"/>    </animation-list>

⑤最后,不要忘了添加权限

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

⑥如果有哪里不懂,可以留言,博主会积极解答。源码下载,博主用的是as开发的

3 0