Android仿微信語音對講錄音功能
自微信出現(xiàn)以來取得了很好的成績,語音對講的實(shí)現(xiàn)更加方便了人與人之間的交流。今天來實(shí)踐一下微信的語音對講的錄音實(shí)現(xiàn),這個(gè)也比較容易實(shí)現(xiàn)。在此,我將該按鈕封裝成為一個(gè)控件,并通過策略模式的方式實(shí)現(xiàn)錄音和界面的解耦合,以方便我們在實(shí)際情況中對錄音方法的不同需求(例如想要實(shí)現(xiàn)wav格式的編碼時(shí)我們也就不能再使用MediaRecorder,而只能使用AudioRecord進(jìn)行處理)。
效果圖:
實(shí)現(xiàn)思路:
1.在微信中我們可以看到實(shí)現(xiàn)語音對講的是通過點(diǎn)按按鈕來完成的,因此在這里我選擇重新自己的控件使其繼承自Button并重寫onTouchEvent方法,來實(shí)現(xiàn)對錄音的判斷。
2.在onTouchEvent方法中,
當(dāng)我們按下按鈕時(shí),首先顯示錄音的對話框,然后調(diào)用錄音準(zhǔn)備方法并開始錄音,接著開啟一個(gè)計(jì)時(shí)線程,每隔0.1秒的時(shí)間獲取一次錄音音量的大小,并通過Handler根據(jù)音量大小更新Dialog中的顯示圖片;
當(dāng)我們移動(dòng)手指時(shí),若手指向上移動(dòng)距離大于50,在Dialog中顯示松開手指取消錄音的提示,并將isCanceled變量(表示我們最后是否取消了錄音)置為true,上移動(dòng)距離小于20時(shí),我們恢復(fù)Dialog的圖片,并將isCanceled置為false;
當(dāng)抬起手指時(shí),我們首先關(guān)閉錄音對話框,接著調(diào)用錄音停止方法并關(guān)閉計(jì)時(shí)線程,然后我們判斷是否取消錄音,若是的話則刪除錄音文件,否則判斷計(jì)時(shí)時(shí)間是否太短,最后調(diào)用回調(diào)接口中的recordEnd方法。
3.在這里為了適應(yīng)不同的錄音需求,我使用了策略模式來進(jìn)行處理,將每一個(gè)不同的錄音方法視為一種不同的策略,根據(jù)自己的需要去改寫。
注意問題
1.在onTouchEvent的返回值中應(yīng)該返回true,這樣才能屏蔽之后其他的觸摸事件,否則當(dāng)手指滑動(dòng)離開Button之后將不能在響應(yīng)我們的觸摸方法。
2.不要忘記為自己的App添加權(quán)限:
<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 類,我們的自定義控件,重新復(fù)寫了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; // 最短錄音時(shí)間,單位秒
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; // 錄音狀態(tài)
private float recodeTime = 0.0f; // 錄音時(shí)長,如果錄音時(shí)間太短則錄音失敗
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;
}
// 錄音時(shí)顯示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("向上滑動(dòng)可取消錄音");
this.setText("松開手指 完成錄音");
break;
}
dialogTextView.setTextSize(14);
mRecordDialog.show();
}
// 錄音時(shí)間太短時(shí)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);// 起點(diǎn)位置為中間
toast.show();
}
// 開啟錄音計(jì)時(shí)線程
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: // 滑動(dòng)手指
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("時(shí)間太短 錄音失敗");
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>
錄音時(shí)間太短的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="時(shí)間太短 錄音失敗" />
</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>
<!-- 顯示對話框時(shí)當(dāng)前的屏幕是否變暗 -->
<item name="android:backgroundDimEnabled">false</item>
</style>
RecordStrategy 錄音策略接口
package com.example.recordtest;
/**
* RecordStrategy 錄音策略接口
* @author acer
*/
public interface RecordStrategy {
/**
* 在這里進(jìn)行錄音準(zhǔn)備工作,重置錄音文件名等
*/
public void ready();
/**
* 開始錄音
*/
public void start();
/**
* 錄音結(jié)束
*/
public void stop();
/**
* 錄音失敗時(shí)刪除原來的舊文件
*/
public void deleteOldFile();
/**
* 獲取錄音音量的大小
* @return
*/
public double getAmplitude();
/**
* 返回錄音文件完整路徑
* @return
*/
public String getFilePath();
}
個(gè)人寫的一個(gè)錄音實(shí)踐策略
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);// 設(shè)置MediaRecorder的音頻源為麥克風(fēng)
recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 設(shè)置MediaRecorder錄制的音頻格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 設(shè)置MediaRecorder錄制音頻的編碼為amr
}
// 以當(dāng)前時(shí)間作為文件名
private String getCurrentDate() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HHmmss");
Date curDate = new Date(System.currentTimeMillis());// 獲取當(dāng)前時(shí)間
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仿微信語音對講錄音
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:Android自定義控件實(shí)現(xiàn)望遠(yuǎn)鏡效果
欄 目:Android
下一篇:Android仿微信鍵盤切換效果
本文標(biāo)題:Android仿微信語音對講錄音功能
本文地址:http://www.jygsgssxh.com/a1/Android/9091.html
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方法詳解
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)登錄
- 01-10android實(shí)現(xiàn)簡單計(jì)算器功能
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代碼
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10Emoji表情在Android JNI中的兼容性問題詳解
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10android開發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解
- 01-10android異步消息機(jī)制 源碼層面徹底解析(1)


閱讀排行
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機(jī)閱讀
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10delphi制作wav文件的方法


