首页 技术 正文
技术 2022年11月15日
0 收藏 501 点赞 3,996 浏览 15465 个字

原文地址:http://www.2cto.com/kf/201502/378704.html

自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在 此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的 编码时我们也就不能再使用MediaRecorder,而只能使用AudioRecord进行处理)。
效果图:
Android开发–仿微信语音对讲录音

实现思路

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添加权限:

?

123 <code class="hljs" xml="">    <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"></uses-permission></uses-permission></uses-permission></code>

代码参考

RecordButton 类,我们的自定义控件,重新复写了onTouchEvent方法

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 <code class="hljs" java="">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);    }}</code>

Dialog布局:

?

12345678 <code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?--><linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">     <imageview android:id="@+id/record_dialog_img" android:layout_height="wrap_content" android:layout_width="wrap_content">     <textview android:id="@+id/record_dialog_txt" android:layout_height="wrap_content" android:layout_margintop="5dp" android:layout_width="wrap_content" android:textcolor="@android:color/white"> </textview></imageview></linearlayout></code>

录音时间太短的Toast布局:

?

12345678 <code class="hljs" xml=""><!--?xml version=1.0 encoding=utf-8?--><linearlayout android:background="@drawable/record_bg" android:gravity="center" android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" android:padding="20dp" xmlns:android="http://schemas.android.com/apk/res/android">     <imageview android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/voice_to_short">     <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="时间太短" android:textcolor="@android:color/white" android:textsize="15sp"> </textview></imageview></linearlayout></code>

自定义的Dialogstyle,对话框样式

?

12345678 <code applescript="" class="hljs"><style name="Dialogstyle" type="text/css"><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></code>

RecordStrategy 录音策略接口

?

12345678910111213141516171819202122232425262728293031323334353637383940 <code class="hljs" java="">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(); }</code>

个人写的一个录音实践策略

?

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 <code class="hljs" java="">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;    } }</code>

MainActivity

?

12345678910111213141516171819202122232425262728 <code class="hljs" java="">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;    } }</code>
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,084
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,559
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,408
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,181
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,818
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,901