您好,登錄后才能下訂單哦!
自微信出現以來取得了很好的成績,語音對講的實現更加方便了人與人之間的交流。今天來實踐一下微信的語音對講的錄音實現,這個也比較容易實現。在此,我將該按鈕封裝成為一個控件,并通過策略模式的方式實現錄音和界面的解耦合,以方便我們在實際情況中對錄音方法的不同需求(例如想要實現wav格式的編碼時我們也就不能再使用MediaRecorder,而只能使用AudioRecord進行處理)。
效果圖:
實現思路:
1.在微信中我們可以看到實現語音對講的是通過點按按鈕來完成的,因此在這里我選擇重新自己的控件使其繼承自Button并重寫onTouchEvent方法,來實現對錄音的判斷。
2.在onTouchEvent方法中,
當我們按下按鈕時,首先顯示錄音的對話框,然后調用錄音準備方法并開始錄音,接著開啟一個計時線程,每隔0.1秒的時間獲取一次錄音音量的大小,并通過Handler根據音量大小更新Dialog中的顯示圖片;
當我們移動手指時,若手指向上移動距離大于50,在Dialog中顯示松開手指取消錄音的提示,并將isCanceled變量(表示我們最后是否取消了錄音)置為true,上移動距離小于20時,我們恢復Dialog的圖片,并將isCanceled置為false;
當抬起手指時,我們首先關閉錄音對話框,接著調用錄音停止方法并關閉計時線程,然后我們判斷是否取消錄音,若是的話則刪除錄音文件,否則判斷計時時間是否太短,最后調用回調接口中的recordEnd方法。
3.在這里為了適應不同的錄音需求,我使用了策略模式來進行處理,將每一個不同的錄音方法視為一種不同的策略,根據自己的需要去改寫。
注意問題
1.在onTouchEvent的返回值中應該返回true,這樣才能屏蔽之后其他的觸摸事件,否則當手指滑動離開Button之后將不能在響應我們的觸摸方法。
2.不要忘記為自己的App添加權限:
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
代碼參考
RecordButton 類,我們的自定義控件,重新復寫了onTouchEvent方法
package com.example.recordtest; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class RecordButton extends Button { private static final int MIN_RECORD_TIME = 1; // 最短錄音時間,單位秒 private static final int RECORD_OFF = 0; // 不在錄音 private static final int RECORD_ON = 1; // 正在錄音 private Dialog mRecordDialog; private RecordStrategy mAudioRecorder; private Thread mRecordThread; private RecordListener listener; private int recordState = 0; // 錄音狀態 private float recodeTime = 0.0f; // 錄音時長,如果錄音時間太短則錄音失敗 private double voiceValue = 0.0; // 錄音的音量值 private boolean isCanceled = false; // 是否取消錄音 private float downY; private TextView dialogTextView; private ImageView dialogImg; private Context mContext; public RecordButton(Context context) { super(context); // TODO Auto-generated constructor stub init(context); } public RecordButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(context); } public RecordButton(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(context); } private void init(Context context) { mContext = context; this.setText("按住 說話"); } public void setAudioRecord(RecordStrategy record) { this.mAudioRecorder = record; } public void setRecordListener(RecordListener listener) { this.listener = listener; } // 錄音時顯示Dialog private void showVoiceDialog(int flag) { if (mRecordDialog == null) { mRecordDialog = new Dialog(mContext, R.style.Dialogstyle); mRecordDialog.setContentView(R.layout.dialog_record); dialogImg = (ImageView) mRecordDialog .findViewById(R.id.record_dialog_img); dialogTextView = (TextView) mRecordDialog .findViewById(R.id.record_dialog_txt); } switch (flag) { case 1: dialogImg.setImageResource(R.drawable.record_cancel); dialogTextView.setText("松開手指可取消錄音"); this.setText("松開手指 取消錄音"); break; default: dialogImg.setImageResource(R.drawable.record_animate_01); dialogTextView.setText("向上滑動可取消錄音"); this.setText("松開手指 完成錄音"); break; } dialogTextView.setTextSize(14); mRecordDialog.show(); } // 錄音時間太短時Toast顯示 private void showWarnToast(String toastText) { Toast toast = new Toast(mContext); View warnView = LayoutInflater.from(mContext).inflate( R.layout.toast_warn, null); toast.setView(warnView); toast.setGravity(Gravity.CENTER, 0, 0);// 起點位置為中間 toast.show(); } // 開啟錄音計時線程 private void callRecordTimeThread() { mRecordThread = new Thread(recordThread); mRecordThread.start(); } // 錄音Dialog圖片隨錄音音量大小切換 private void setDialogImage() { if (voiceValue < 600.0) { dialogImg.setImageResource(R.drawable.record_animate_01); } else if (voiceValue > 600.0 && voiceValue < 1000.0) { dialogImg.setImageResource(R.drawable.record_animate_02); } else if (voiceValue > 1000.0 && voiceValue < 1200.0) { dialogImg.setImageResource(R.drawable.record_animate_03); } else if (voiceValue > 1200.0 && voiceValue < 1400.0) { dialogImg.setImageResource(R.drawable.record_animate_04); } else if (voiceValue > 1400.0 && voiceValue < 1600.0) { dialogImg.setImageResource(R.drawable.record_animate_05); } else if (voiceValue > 1600.0 && voiceValue < 1800.0) { dialogImg.setImageResource(R.drawable.record_animate_06); } else if (voiceValue > 1800.0 && voiceValue < 2000.0) { dialogImg.setImageResource(R.drawable.record_animate_07); } else if (voiceValue > 2000.0 && voiceValue < 3000.0) { dialogImg.setImageResource(R.drawable.record_animate_08); } else if (voiceValue > 3000.0 && voiceValue < 4000.0) { dialogImg.setImageResource(R.drawable.record_animate_09); } else if (voiceValue > 4000.0 && voiceValue < 6000.0) { dialogImg.setImageResource(R.drawable.record_animate_10); } else if (voiceValue > 6000.0 && voiceValue < 8000.0) { dialogImg.setImageResource(R.drawable.record_animate_11); } else if (voiceValue > 8000.0 && voiceValue < 10000.0) { dialogImg.setImageResource(R.drawable.record_animate_12); } else if (voiceValue > 10000.0 && voiceValue < 12000.0) { dialogImg.setImageResource(R.drawable.record_animate_13); } else if (voiceValue > 12000.0) { dialogImg.setImageResource(R.drawable.record_animate_14); } } // 錄音線程 private Runnable recordThread = new Runnable() { @Override public void run() { recodeTime = 0.0f; while (recordState == RECORD_ON) { { try { Thread.sleep(100); recodeTime += 0.1; // 獲取音量,更新dialog if (!isCanceled) { voiceValue = mAudioRecorder.getAmplitude(); recordHandler.sendEmptyMessage(1); } } catch (InterruptedException e) { e.printStackTrace(); } } } } }; @SuppressLint("HandlerLeak") private Handler recordHandler = new Handler() { @Override public void handleMessage(Message msg) { setDialogImage(); } }; @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 按下按鈕 if (recordState != RECORD_ON) { showVoiceDialog(0); downY = event.getY(); if (mAudioRecorder != null) { mAudioRecorder.ready(); recordState = RECORD_ON; mAudioRecorder.start(); callRecordTimeThread(); } } break; case MotionEvent.ACTION_MOVE: // 滑動手指 float moveY = event.getY(); if (downY - moveY > 50) { isCanceled = true; showVoiceDialog(1); } if (downY - moveY < 20) { isCanceled = false; showVoiceDialog(0); } break; case MotionEvent.ACTION_UP: // 松開手指 if (recordState == RECORD_ON) { recordState = RECORD_OFF; if (mRecordDialog.isShowing()) { mRecordDialog.dismiss(); } mAudioRecorder.stop(); mRecordThread.interrupt(); voiceValue = 0.0; if (isCanceled) { mAudioRecorder.deleteOldFile(); } else { if (recodeTime < MIN_RECORD_TIME) { showWarnToast("時間太短 錄音失敗"); mAudioRecorder.deleteOldFile(); } else { if (listener != null) { listener.recordEnd(mAudioRecorder.getFilePath()); } } } isCanceled = false; this.setText("按住 說話"); } break; } return true; } public interface RecordListener { public void recordEnd(String filePath); } }
Dialog布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:background="@drawable/record_bg" android:padding="20dp" > <ImageView android:id="@+id/record_dialog_img" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/record_dialog_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:layout_marginTop="5dp" /> </LinearLayout>
錄音時間太短的Toast布局:
<?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/record_bg" android:padding="20dp" android:gravity="center" android:orientation="vertical" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/voice_to_short" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="15sp" android:text="時間太短 錄音失敗" /> </LinearLayout>
自定義的Dialogstyle,對話框樣式
<style name="Dialogstyle"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowFrame">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> <!-- 顯示對話框時當前的屏幕是否變暗 --> <item name="android:backgroundDimEnabled">false</item> </style>
RecordStrategy 錄音策略接口
package com.example.recordtest; /** * RecordStrategy 錄音策略接口 * @author acer */ public interface RecordStrategy { /** * 在這里進行錄音準備工作,重置錄音文件名等 */ public void ready(); /** * 開始錄音 */ public void start(); /** * 錄音結束 */ public void stop(); /** * 錄音失敗時刪除原來的舊文件 */ public void deleteOldFile(); /** * 獲取錄音音量的大小 * @return */ public double getAmplitude(); /** * 返回錄音文件完整路徑 * @return */ public String getFilePath(); }
個人寫的一個錄音實踐策略
package com.example.recordtest; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import android.media.MediaRecorder; import android.os.Environment; public class AudioRecorder implements RecordStrategy { private MediaRecorder recorder; private String fileName; private String fileFolder = Environment.getExternalStorageDirectory() .getPath() + "/TestRecord"; private boolean isRecording = false; @Override public void ready() { // TODO Auto-generated method stub File file = new File(fileFolder); if (!file.exists()) { file.mkdir(); } fileName = getCurrentDate(); recorder = new MediaRecorder(); recorder.setOutputFile(fileFolder + "/" + fileName + ".amr"); recorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設置MediaRecorder的音頻源為麥克風 recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 設置MediaRecorder錄制的音頻格式 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 設置MediaRecorder錄制音頻的編碼為amr } // 以當前時間作為文件名 private String getCurrentDate() { SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HHmmss"); Date curDate = new Date(System.currentTimeMillis());// 獲取當前時間 String str = formatter.format(curDate); return str; } @Override public void start() { // TODO Auto-generated method stub if (!isRecording) { try { recorder.prepare(); recorder.start(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } isRecording = true; } } @Override public void stop() { // TODO Auto-generated method stub if (isRecording) { recorder.stop(); recorder.release(); isRecording = false; } } @Override public void deleteOldFile() { // TODO Auto-generated method stub File file = new File(fileFolder + "/" + fileName + ".amr"); file.deleteOnExit(); } @Override public double getAmplitude() { // TODO Auto-generated method stub if (!isRecording) { return 0; } return recorder.getMaxAmplitude(); } @Override public String getFilePath() { // TODO Auto-generated method stub return fileFolder + "/" + fileName + ".amr"; } }
MainActivity
package com.example.recordtest; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { RecordButton button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (RecordButton) findViewById(R.id.btn_record); button.setAudioRecord(new AudioRecorder()); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
源碼下載:Android仿微信語音對講錄音
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。