Android 多媒体之实现语音聊天界面

一、概述:

1、需要实现的功能:
1)语音的录制
2)按钮状态的切换
3)对话框状态的切换
4)聊天界面的切换

2、所用到的技术:
1)核心技术:根据按钮的onTouchEvent实现down、move、up触发的事件
down时:

```
changeState(state_record)
     LongClick->AudioManager.prepare()->end parepared -> DialogManager.showDialog(recording);
```

move时:

```
 if(wantCancel(x,y){
              DialogManager.showDialog(want_to_cancel);
               changeState(state_want_to_cancel)
       }else{
               DialogManager.showDialog(record);
               changeState(state_record)
       } 
```

up时:

```
if(wantCancel == curState){
            AudioManager.cancel();
    }else if(state_recode == curState){
            AudioManager.release();
            callbackToActivity(url,time);
    }        
```

3、主要的三个类存在的状态:
1)AudioRecorderButton类(录音按钮):
State:STATE_NORMAL【正常状态】、STATE_RECORDERING【正在录音的状态】、STATE_WANT_TO_CALCEL【想要放弃录音的装填】;
(2)AudioDialogManage类(录音过程中的提示对话框):
Style:RECORDERING【正在录音】、WANT_TO_CANCEL【取消提示对话框】、TOO_SHAORT【录音时间过短提醒对话框】;
(3)AudioManage类(控制录音):
prepare()(end prepare-—》callback)【去录音】, cancel()【取消录音】, release()【正常结束】(-—》callbackToActivity), getVoiceLevel()【获得音量大小】;

4、效果图:
这里写图片描述

5、重点说明:
DialogHelper类里使用了context 是Activity级别的,而Dialog是application级别的,
如果使用了单例模式,在其它Activity里,Dialog就不能释放,就会造成内存泄漏,产生anr异常

二、搭建基本框架:

1、创建按钮:

/**
 * @描述         TODO
 * @项目名称      App_imooc
 * @包名         com.android.imooc.chat
 * @类名         RecordButton
 * @author      chenlin
 * @date        2013年6月13日 下午10:42:50
 */

public class RecordButton extends Button {

    public RecordButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RecordButton(Context context) {
        this(context, null);
    }

    public RecordButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

}

2、创建布局,把按钮添加到布局里

<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" >

    <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.android.imooc.chat.RecordButton
            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.android.imooc.chat.RecordButton>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#ccc" />
    </FrameLayout>

</LinearLayout>

3、创建主页:

/**
 * @描述         聊天布局
 * @项目名称      App_imooc
 * @包名         com.android.imooc.chat
 * @类名         ChatActivity
 * @author      chenlin
 * @date        2013年6月13日 下午10:31:59
 * @version     1.0
 */

public class ChatActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
    }
}

三、按钮的基本功能实现:

1、添加状态

//按住说话状态
    private static final int STATE_NORMAL = 0x00;
    //正在录音状态
    private static final int STATE_RECORDING = 0x01;
    //准备取消状态
    private static final int STATE_WANT_TO_CANCEL = 0x02;
    //当前状态
    private int curState = STATE_NORMAL;
    //表示已经录音
    private boolean isRecording;

2、添加ontouchEvent

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            changeState(STATE_RECORDING);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isRecording) {
                //根据坐标判断是否要取消
                if (wantToCancel(x, y)) {
                    changeState(STATE_WANT_TO_CANCEL);
                }else {
                    changeState(STATE_RECORDING);
                }
            }

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (curState == STATE_RECORDING) {

            }else if (curState == STATE_WANT_TO_CANCEL) {

            }

            //恢复状态等信息
            reset();
            break;

        default:
            break;
        }
        return true;
    }

    private void reset() {
        isRecording = false;
    }

    private boolean wantToCancel(int x, int y) {
        // TODO Auto-generated method stub
        return false;
    }

    private void changeState(int stateRecording) {
        // TODO Auto-generated method stub

    }

3、实现changeState方法,在这个方法里,根据传入的状态,设置按钮的颜色,背景色等

    private void changeState(int state) {
        if (mCurState != state) {
            mCurState = state;
        }
        switch (state) {
        case STATE_NORMAL:
            setBackgroundResource(R.drawable.button_recordnormal);
            setText(R.string.chat_normal);
            break;
        case STATE_RECORDING:
            setBackgroundResource(R.drawable.button_recording);
            setText(R.string.chat_recording);
            break;
        case STATE_WANT_TO_CANCEL:
            setBackgroundResource(R.drawable.button_recording);
            setText(R.string.chat_want_to_cancel);
            break;
        default:
            break;
        }
    }

4、实现判断是否要取消状态,根据手指的移到范围

private boolean wantToCancel(int x, int y) {
        //如果手指不在按钮的x轴范围内
        if (x < 0 || x > getWidth()) {
            return true;
        }
        //getHeight是按钮的高度
        if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
            return true;
        }

        return false;
    }

四、对话框的实现:

1、从效果图可发现,对话框里有声音图片,背景图片,文字等
所以要定义

public class DialogHelper {
    private Dialog mDialog;
    private ImageView mIcon;
    private ImageView mVoice;
    private TextView mLabel;
    private Context mContext;

    public DialogHelper(Context context){
        this.mContext = context;
    }
}

2、需要实现的方法:

/**
     * 显示
     */
    public void showDialog(){

    }
    /**
     * 准备关闭
     */
    public void wantToCancel(){

    }
    /**
     * 时间太短提示
     */
    public void tooShort(){

    }
    /**
     * 关闭
     */
    public void dimiss(){

    }
    /**
     * 更新声音的大小
     * @param level
     */
    public void updateVoiceLevel(int level){

    }

3、实现showDialog方法
1)首先得定义布局文件

<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/chat_moveUp"
        android:textColor="#ffffffff" />

</LinearLayout>

如图:
这里写图片描述

2)定义样式:

<!-- 设置弹出窗口的属性,frame叠加,isfloat是否浮动,tarnslucent是否半透明,dim是背景是否变暗 -->
    <style name="Theme_audioDialog" parent="@android:style/Theme.Dialog">
        <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>

3)初始化、加载布局并添加样式,显示对话框

/**
     * 显示
     */
    public void showDialog(){
        mDialog = new Dialog(mContext, R.style.Theme_audioDialog);
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        View view = layoutInflater.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);
        mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialogtext);

        mDialog.show();
    }

4、实现recording、wantToCancel,tooShort与dimiss方法
最主要的就是dialog的view的操作,显示隐藏等

/**
     * 录音
     */
    public void recording(){
        if (mDialog != null &&  mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.VISIBLE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.drawable.recorder);
            mLabel.setText(mContext.getResources().getString(R.string.chat_moveUp));
        }
    }
    /**
     * 准备关闭
     */
    public void wantToCancel(){
        if (mDialog != null &&  mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.drawable.cancel);
            mLabel.setText(mContext.getResources().getString(R.string.chat_release));
        }
    }
/**
     * 时间太短提示
     */
    public void tooShort(){
        if (mDialog != null &&  mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.drawable.voice_to_short);
            mLabel.setText(mContext.getResources().getString(R.string.chat_tooShort));
        }
    }
    /**
     * 关闭
     */
    public void dimiss(){
        if (mDialog != null &&  mDialog.isShowing()) {
            mDialog.dismiss();
            mDialog = null;
        }
    }

5、更新声音的大小
这里写图片描述
如图,有七张图片,如何动态获取图片id呢?
int resId = mContext.getResources().getIdentifier(“v” + level, “drawable”, mContext.getPackageName());

/**
     * 更新声音的大小
     * @param level
     */
    public void updateVoiceLevel(int level){
        if (mDialog != null &&  mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.VISIBLE);
            mLabel.setVisibility(View.VISIBLE);

            int resId = mContext.getResources().getIdentifier("v" + level, "drawable", mContext.getPackageName());
            mVoice.setImageResource(resId);
        }
    }

6、在RecordButton里使用DialogHelper

public RecordButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        mdDialogHelper = new DialogHelper(context);
        setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Logger.i(TAG, "onLongClick");
                isRecording = true;
                mdDialogHelper.showDialog();
                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:
            // 按下时,开始播放
            changeState(STATE_RECORDING);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isRecording) {
                // 根据坐标判断是否要取消
                if (wantToCancel(x, y)) {
                    changeState(STATE_WANT_TO_CANCEL);
                } else {
                    changeState(STATE_RECORDING);
                }
            }

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (mCurState == STATE_RECORDING) {
                mdDialogHelper.dimiss();
            } else if (mCurState == STATE_WANT_TO_CANCEL) {
                mdDialogHelper.dimiss();
            }

            // 恢复状态等信息
            reset();
            break;

        default:
            break;
        }
        return super.onTouchEvent(event);
    }

五、声音处理

1、添加权限

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

2、创建声音帮助类,初步实现:

/**
 * @描述         声音处理类,使用单例模式
 * @项目名称      App_imooc
 * @包名         com.android.imooc.chat
 * @类名         AudioHelper
 * @author      chenlin
 * @date        2013年6月15日 下午9:51:21
 */

public class AudioHelper {
    private MediaRecorder mRecorder;
    private String mDir;
    private String mCurrentFilePaht;

    //单例模式--------------------------
    private static AudioHelper instance;
    private AudioHelper(){}
    public static AudioHelper getInstance(){
        if (instance == null) {
            synchronized (AudioHelper.class) {
                if (instance == null) {
                    instance = new AudioHelper();
                }
            }
        }
        return instance;
    }

    //回调函数--------------------------
    public interface AudioStateListener {
        void onPrepared();
    }
    private AudioStateListener mListener;
    public void setAudioStateListener(AudioStateListener listener){
        this.mListener = listener;
    }

    /**
     * 获得声音的等级
     * @return
     */
    public int getVoiceLevel(){
        return 1;
    }
    /**
     * 初始化
     */
    public void prepare(){

    }

    public void record(){

    }

    public void cancel(){

    }

}

3、编写方法prepare:
MediaRecorder:http://developer.android.com/reference/android/media/MediaRecorder.html
MediaRecorder状态转换图:
这里写图片描述

/**
     * 初始化
     */
    public void prepare() {
        // 找到文件路径
        File dir = new File(mDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String fileName = generateFileName();
        File file = new File(dir, fileName);
        // 初始化MediaRecorder
        mMediaRecorder = new MediaRecorder();
        // 设置各种
        mMediaRecorder.setOutputFile(file.getAbsolutePath());
        mMediaRecorder.setOutputFile(file.getAbsolutePath());// 设置输出文件
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);// 设置音频的格式
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置音频的编码为AMR_NB
        // prepare
        try {
            mMediaRecorder.prepare();
        } catch (IllegalStateException e) {
            throw new RuntimeException("MediaRecorder 参数错误 " + e.getMessage());
        } catch (IOException e) {
            throw new RuntimeException("MediaRecorder 不能启动 " + e.getMessage());
        }

        mMediaRecorder.start();
        isPrepared = true;

        if (mListener != null) {
            mListener.onPrepared();
        }

    }

4、其他三种方法:

/**
     * 获得声音的等级
     * 
     * @return
     */
    public int getVoiceLevel(int maxLevel) {
        if (isPrepared) {
            try {
                // getMaxAmplitude(): 1 - 32767
                // mMediaRecorder.getMaxAmplitude() / 32768 : 0 - 1;
                return (int) (maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 0.5f);
            } catch (IllegalStateException e) {
                throw new RuntimeException("不能获得声音的振幅  " + e.getMessage());
            }
        }
        return 1;
    }

    public void release() {
        if (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
            isPrepared = false;
        }
    }

    public void cancel() {
        release();
        if (mCurrentFilePath != null) {
            File file = new File(mCurrentFilePath);
            if (file.exists()) {
                file.delete();
            }
        }
        mCurrentFilePath = null;
    }

六、实现界面

1、创建item布局

<?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/id_recorder_anim"
          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、完善主页:

package com.android.imooc.chat;

import java.util.ArrayList;
import java.util.List;

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.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.android.imooc.R;
import com.android.imooc.chat.RecordButton.AudioFinishRecordListener;

/**
 * @描述 聊天布局
 * @项目名称 App_imooc
 * @包名 com.android.imooc.chat
 * @类名 ChatActivity
 * @author chenlin
 * @date 2013年6月13日 下午10:31:59
 * @version 1.0
 */

public class ChatActivity extends Activity {
    private RecordButton mBtnRecord;

    private ListView mlistview;
    private ArrayAdapter<Recorder> mAdapter;
    private View mView;
    private List<Recorder> mDatas = new ArrayList<Recorder>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        mlistview = (ListView) findViewById(R.id.listview);
        mAdapter = new RecorderAdapter(this, mDatas);
        mlistview.setAdapter(mAdapter);

        mBtnRecord = (RecordButton) findViewById(R.id.recordButton);

        initEvents();


    }

    private void initEvents() {
        mlistview.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //如果在播放第二动画时,判断原来的动画如果正在播放,就停止
                if (mView != null) {
                    mView.setBackgroundResource(R.drawable.adj);
                    mView = null;
                }
                mView = findViewById(R.id.id_recorder_anim);
                mView.setBackgroundResource(R.drawable.play_sound);
                AnimationDrawable drawable = (AnimationDrawable) mView.getBackground();
                drawable.start();

                //开始播放声音
                MediaHelper.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        mView.setBackgroundResource(R.drawable.adj);
                    }
                });
            }
        });
        mBtnRecord.setAudioFinishRecordListener(new AudioFinishRecordListener() {
            @Override
            public void onFinish(float second, String path) {
                Recorder recorder = new Recorder(second, path);
                mDatas.add(recorder);
                mAdapter.notifyDataSetChanged();
                mlistview.setSelection(mDatas.size() - 1);
            }

        });

    }

    @Override
    protected void onResume() {
        MediaHelper.resume();
        super.onResume();
    }

    @Override
    protected void onPause() {
        MediaHelper.pause();
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        MediaHelper.release();
        super.onDestroy();
    }
}

3、实现mediaplayer

package com.android.imooc.chat;

import java.io.IOException;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;

/**
 * @描述 声音控制类
 * @项目名称 App_imooc
 * @包名 com.android.imooc.chat
 * @类名 MediaHelper
 * @author chenlin
 * @date 2013年6月17日 下午10:46:01
 * @version 1.0
 */

public class MediaHelper {
    private static MediaPlayer mPlayer;
    private static boolean isPause = false;

    public static void playSound(String filePath, OnCompletionListener listener) {
        if (mPlayer == null) {
            mPlayer = new MediaPlayer();
        } else {
            mPlayer.reset();
        }
        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mPlayer.setOnCompletionListener(listener);
        mPlayer.setOnErrorListener(new OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                mPlayer.reset();
                return false;
            }
        });
        try {
            mPlayer.setDataSource(filePath);
            mPlayer.prepare();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new RuntimeException("读取文件异常:" + e.getMessage());
        }
        mPlayer.start();
        isPause = false;
    }

    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;
        }
    }

}

4、控制类recordButton全部代码

/**
 * @描述 TODO
 * @项目名称 App_imooc
 * @包名 com.android.imooc.chat
 * @类名 RecordButton
 * @author chenlin
 * @date 2013年6月13日 下午10:42:50
 */

public class RecordButton extends Button implements AudioStateListener {
    private static final String TAG = "RecordButton";
    //手指向上移动的距离50,如果超过,说明要cancel
    private static final int DISTANCE_Y_CANCEL = 50;
    // 按住说话状态
    private static final int STATE_NORMAL = 0x00;
    // 正在录音状态
    private static final int STATE_RECORDING = 0x01;
    // 准备取消状态
    private static final int STATE_WANT_TO_CANCEL = 0x02;

    // 当前状态
    private int mCurState = STATE_NORMAL;
    // 表示已经录音
    private boolean isRecording;
    //是否是长按了
    private boolean isLongClick;

    private DialogHelper mdDialogHelper;
    private AudioHelper mAudioHelper;

    //--录音完成回调------------------------------------------
    public interface AudioFinishRecordListener {
        void onFinish(float second, String path);
    }

    private AudioFinishRecordListener mListener;

    public void setAudioFinishRecordListener(AudioFinishRecordListener listener) {
        this.mListener = listener;
    }

    public RecordButton(Context context) {
        this(context, null);
    }
    public RecordButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        mdDialogHelper = new DialogHelper(context);
        String dir = Environment.getExternalStorageDirectory() + File.separator + "chat";
        mAudioHelper  = AudioHelper.getInstance(dir);
        mAudioHelper.setAudioStateListener(this);

        setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                isLongClick = true;
                mAudioHelper.prepare();
                return false;
            }
        });

    }

    @Override
    public void onPrepared() {
        mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
    }

    private Runnable mGetVoiceLevelRunnable = new Runnable() {
        @Override
        public void run() {
            while(isRecording){
                //SystemClock.sleep(100);
                Logger.i(TAG, "run ========" );
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //每个0.1秒更新声音s
                mTime += 0.1f;
                mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);
            }

        }
    };

    private static final int MSG_AUDIO_PREPARED = 0x11;//准备录制声音
    private static final int MSG_VOICE_CHANGED = 0x12;//声音改变
    private static final int MSG_DIALOG_DIMISS = 0x13;//对话框消失
    private float mTime = 0;
    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_AUDIO_PREPARED:
                isRecording = true;
                //显示声音对话框
                mdDialogHelper.showDialog();
                //开启线程获取音量
                new Thread(mGetVoiceLevelRunnable).start();
                break;
            case MSG_VOICE_CHANGED:
                int level = mAudioHelper.getVoiceLevel(7);
                Logger.i(TAG, "level ========" + level);
                //在对话框里更新声音的大小
                mdDialogHelper.updateVoiceLevel(level);
                break;
            case MSG_DIALOG_DIMISS:
                mdDialogHelper.dimiss();
                break;

            default:
                break;
            }
        }
    };



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 按下时,开始播放
            changeState(STATE_RECORDING);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isRecording) {
                // 根据坐标判断是否要取消
                if (wantToCancel(x, y)) {
                    changeState(STATE_WANT_TO_CANCEL);
                } else {
                    changeState(STATE_RECORDING);
                }
            }

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            //如果没有长按
            if (!isLongClick) {
                reset();
                return super.onTouchEvent(event);
            }
            //如果没有录制
            if (!isRecording || mTime < 0.6f) {
                //显示按的时间太短
                mdDialogHelper.tooShort();
                //关闭录制
                mAudioHelper.cancel();
                //发送信息关闭资源
                mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);
            } else if (mCurState == STATE_RECORDING) {
                mdDialogHelper.dimiss();
                if (mListener != null) {
                    mListener.onFinish(mTime, mAudioHelper.getFilePath());
                }
                mAudioHelper.release();

            } else if (mCurState == STATE_WANT_TO_CANCEL) {
                mdDialogHelper.dimiss();
                mAudioHelper.cancel();
            }


            // 恢复状态等信息
            reset();
            break;

        default:
            break;
        }
        return super.onTouchEvent(event);
    }

    private void reset() {
        isRecording = false;
        changeState(STATE_NORMAL);
        mTime = 0;
        isLongClick = false;
    }

    private boolean wantToCancel(int x, int y) {
        //如果手指不在按钮的x轴范围内
        if (x < 0 || x > getWidth()) {
            return true;
        }
        //getHeight是按钮的高度
        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.button_recordnormal);
            setText(R.string.chat_normal);
            break;
        case STATE_RECORDING:
            setBackgroundResource(R.drawable.button_recording);
            setText(R.string.chat_recording);
            if (isRecording) {
                mdDialogHelper.recording();
            }
            break;
        case STATE_WANT_TO_CANCEL:
            setBackgroundResource(R.drawable.button_recording);
            setText(R.string.chat_want_to_cancel);
            mdDialogHelper.wantToCancel();
            break;
        default:
            break;
        }
    }



}

七、源码下载:

———————————————————————
(java 架构师全套教程,共760G, 让你从零到架构师,每月轻松拿3万)
有需求者请进站查看,非诚勿扰

https://item.taobao.com/item.htm?spm=686.1000925.0.0.4a155084hc8wek&id=555888526201

01.高级架构师四十二个阶段高
02.Java高级系统培训架构课程148课时
03.Java高级互联网架构师课程
04.Java互联网架构Netty、Nio、Mina等-视频教程
05.Java高级架构设计2016整理-视频教程
06.架构师基础、高级片
07.Java架构师必修linux运维系列课程
08.Java高级系统培训架构课程116课时
(送:hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门)
——————————————————————–

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lovoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值