1. 程式人生 > >Android仿微信、錄製音訊併發送功能

Android仿微信、錄製音訊併發送功能


1.首先是主頁面的佈局

    佈局採用線性佈局,上面使用的一個ListView,下面使用的是一個自定義的Button(會在下面進行介紹)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.bwie.liaotian_test2.MainActivity">


    <ListView
        android:id="@+id/lv"
        android:dividerHeight="10dp"
        android:divider="@null"
        android:background="#ebebeb"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="0dp"></ListView>

    <FrameLayout
        android:background="#fff"
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.bwie.liaotian_test2.View.AudioRecorderButton
            android:minHeight="0dp"
            android:id="@+id/audio_button"
            android:layout_marginRight="50dp"
            android:layout_marginLeft="50dp"
            android:layout_marginTop="6dp"
            android:layout_marginBottom="7dp"
            android:layout_width="match_parent"
            android:gravity="center"
            android:padding="6dp"
            android:textSize="20sp"
            android:textColor="#727272"
            android:background="@drawable/btn_normal"
            android:text="@string/str_recorder_normal"
            android:layout_height="wrap_content" />

        <View
            android:background="#ccc"
            android:layout_width="match_parent"
            android:layout_height="1dp"/>
    </FrameLayout>
</LinearLayout>
btn_normal
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#ffffff"/>
    <stroke android:color="#9b9b9b" android:width="1px"/>
    <corners android:radius="3dp"/>
</shape>
btn_recording
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#bbcc00"/>
    <stroke android:color="#9b9b9b" android:width="1px"/>
    <corners android:radius="3dp"/>
</shape>
2.自定義button ---AudioRecorderButton
public class AudioRecorderButton extends android.support.v7.widget.AppCompatButton 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 int mCurState = STATE_NORMAL;//當前狀態
    private boolean isRecording = false;//是否正在錄音
    private static final int DISTANXE_Y_CANCEL = 50;
    private AudioManager mAudioManager;
    private DialogManager mDialogManger;
    private boolean mReady = false;//是否觸發longClick
    private float mTime;//計時
    public AudioRecorderButton(Context context) {
        super(context,null);
    }

    public AudioRecorderButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        String dir = Environment.getExternalStorageDirectory() + "/my_recorder_audios";
        mAudioManager = AudioManager.getmInstance(dir);
        mAudioManager.setAudioStateListener(this);
        mDialogManger=new DialogManager(context);
        setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                mAudioManager.prepareAudio();
                mReady = true;
                return false;
            }
        });
    }

    @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:
                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.dimissDialog();
                    mAudioManager.release();
                    if (mListener != null) {
                        mListener.onFinish(mTime, mAudioManager.getmCurrentFilePath());
                    }
                } else if (mCurState == STATE_WANT_TO_CANCEL) {
                    mDialogManger.dimissDialog();
                    mAudioManager.cancel();
                }
                reset();
                break;
        }
        return super.onTouchEvent(event);
    }
    /**
     * 根據移動後的位置,判斷是否取消錄音
     */
    private boolean isCancelRecorder(int x, int y) {
         if(x<0||x>getWidth()){
             return true;
         }
         if(y<-DISTANXE_Y_CANCEL||y>getHeight()+DISTANXE_Y_CANCEL){
             return true;
         }
        return false;
    }
    /**
     * 根據不同狀態,更改不同的文字和顯示的背景
     */
    private void changeSate(int state) {
        if (mCurState != state) {
            mCurState = state;
            switch (state) {
                case STATE_NORMAL:
                    setBackgroundResource(R.drawable.btn_normal);
                    setText(R.string.str_recorder_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 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.dimissDialog();
                    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;
    }
}
相關使用的string值(需要新增到value/string中)
<string name="str_recorder_normal">按住說話</string>
    <string name="str_recorder_recording">鬆開結束</string>
    <string name="str_recorder_want_cancel">鬆開手指,取消傳送</string>
    <string name="ss">手指上滑,取消傳送</string>
3.Dialog的實現
dialog.xml  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:orientation="vertical"
    android:padding="20dp"
    android:gravity="center"
    android:background="@drawable/dialog_loading_bg"
    android:layout_height="wrap_content">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/iv1"
            android:visibility="visible"
            android:src="@drawable/recorder"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <ImageView
            android:id="@+id/iv2"
            android:visibility="visible"
            android:src="@drawable/v1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <TextView
        android:id="@+id/tv"
        android:textColor="#ffff"
        android:text="@string/ss"
        android:layout_marginTop="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
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>
DialogManager
public class 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.iv1);
        mVoice = (ImageView) mDialog.findViewById(R.id.iv2);
        mLabel = (TextView) mDialog.findViewById(R.id.tv);
        //顯示
        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.drawable.recorder);
            mLabel.setText(R.string.ss);
        }
    }
    /**
     * 取消錄製對話方塊提示
     */
    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("鬆開手指,取消傳送");
        }
    }
    /**
     * 錄音時間過短提示
     */
    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("錄音時間過短");
        }
    }
    /**
     * 取消對話方塊
     */
    public void dimissDialog(){
        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, "drawable", mContext.getPackageName());
            mVoice.setImageResource(resId);
        }
    }
}
4.當手指按住Button時,便開始錄音,所以我們還需要定義一個錄音的管理類AudioManager
來控制錄製狀態。
------AudioManager
public class AudioManager {

    private MediaRecorder mMediaRecorder;
    private String mDir;// 儲存的目錄
    private String mCurrentFilePath;// 儲存音訊檔案的全路徑
    private boolean isPrepared = false;// 是否準備完畢
    private static AudioManager mInstance;

    private AudioManager(String dir) {
        mDir = dir;
    }
    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() + ".amr";
    }
}
5.listview子佈局和介面卡
5.1 bean
public class Recorder {
    private float time;
    private String filePath;

    public Recorder(float time, String filePath) {
        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;
    }
}
//子佈局 item_re.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginTop="5dp"
    android:layout_height="60dp">

    <ImageView
        android:src="@drawable/icon"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="5dp"
        android:id="@+id/icon_iv"
        android:layout_width="40dp"
        android:layout_height="40dp" />


    <FrameLayout
        android:layout_toLeftOf="@+id/icon_iv"
        android:layout_centerVertical="true"
        android:id="@+id/fragme"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="@drawable/chatto_bg_focused">

        <View
            android:background="@drawable/adj"
            android:layout_gravity="center_vertical|right"
            android:id="@+id/icon_v"
            android:layout_width="25dp"
            android:layout_height="25dp"/>
    </FrameLayout>

    <TextView
        android:textColor="#ff777777"
        android:layout_marginRight="3dp"
        android:layout_toLeftOf="@+id/fragme"
        android:layout_centerVertical="true"
        android:id="@+id/icon_time"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>
//介面卡
public class RecorderAdapter extends ArrayAdapter<Recorder> {

    private List<Recorder> mDatas;
    private Context mContext;
    private int mMinItemWidhth;
    private int mMaxItemWidhth;
    public RecorderAdapter(Context context, List<Recorder> datas) {
        super(context, -1, datas);
        mDatas = datas;
        mContext = context;
        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=null;
        if(holder==null) {
            holder=new ViewHolder();
            if (convertView == null) {
                convertView = View.inflate(mContext,R.layout.item_re, null);
                holder.length=convertView.findViewById(R.id.fragme);
                holder.time=convertView.findViewById(R.id.icon_time);
                convertView.setTag(holder);
            }else{
                holder= (ViewHolder) convertView.getTag();
            }
        }
        holder.time.setText(Math.round(getItem(position).getTime())+ "\"");
        ViewGroup.LayoutParams layoutParams = holder.length.getLayoutParams();
        layoutParams.width = (int) (mMinItemWidhth + (mMaxItemWidhth / 60f * getItem(position).getTime()));
        return convertView;
    }

    private class ViewHolder{
       public TextView time;
       private View length;

        public TextView getTime() {
            return time;
        }

        public void setTime(TextView time) {
            this.time = time;
        }

        public View getLength() {
            return length;
        }

        public void setLength(View length) {
            this.length = length;
        }
    }
}
6.定義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;
        }
    }

}
//main
public class MainActivity extends AppCompatActivity {

    private ListView lv;
    private ArrayAdapter<Recorder> mAdapter;
    private List<Recorder> list=new ArrayList<>();
    private AudioRecorderButton audioRecorderButton;
    private View animView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = findViewById(R.id.lv);
        audioRecorderButton=findViewById(R.id.audio_button);
        audioRecorderButton.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {
            @Override
            public void onFinish(float time, String filePath) {
                Recorder recorder=new Recorder(time,filePath);
                list.add(recorder);
                mAdapter.notifyDataSetChanged();
                lv.setSelection(list.size()-1);
            }
        });

        mAdapter=new RecorderAdapter(this,list);
        lv.setAdapter(mAdapter);
        lv.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.icon_v);
                  animView.setBackgroundResource(R.drawable.play_anim);
                  AnimationDrawable anim= (AnimationDrawable) animView.getBackground();
                  anim.start();
                  // 播放音訊
                MediaManager.playSound(list.get(position).getFilePath(), new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        animView.setBackgroundResource(R.drawable.adj);
                    }
                });
            }
        });
    }

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

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

    @Override
    protected void onDestroy() {
        MediaManager.release();
        super.onDestroy();
    }
}
幀動畫play_anim定義在drawable下
<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"/>  
github已上傳,地址奉上.
//https://github.com/Meet-hua/Android--