Android仿微信语音对讲录音功能

自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现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添加权限:

<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);// 起点位置为中间
    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仿微信语音对讲录音

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...
这篇文章主要介绍“Android岛屿数量算法怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android岛屿数量算...
本篇内容主要讲解“Android如何开发MQTT协议的模型及通信”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Andro...