Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save suntong/a9a345d4707c76f69546 to your computer and use it in GitHub Desktop.
Save suntong/a9a345d4707c76f69546 to your computer and use it in GitHub Desktop.
Android开发——实现微信语音聊天功能, http://www.mobile-open.com/2015/84278.html

http://www.mobile-open.com/2015/84273.html

本文重点讲述了如何开发出类似微信语音聊天的功能,包括自定义按钮的实现,现在整理出来分享给广大的Android程序员兄弟们,希望给他们的开发工作带来帮助。

前言

这几天学习了慕课网上的高仿微信语音聊天功能的课程,自己动手实现了一下。在这里将其实现的过程以及代码分享下来。由于我是android的初学者,里面有不成熟的地方欢迎大家指正。

项目中所用的图片可以在这个地方下载:

http://pan.baidu.com/disk/home

项目简单介绍

首先我们来看最终实现的几张效果图吧。如下:

模仿微信语音聊天功能(1)项目介绍及自定义按钮实现 模仿微信语音聊天功能(1)项目介绍及自定义按钮实现 模仿微信语音聊天功能(1)项目介绍及自定义按钮实现 模仿微信语音聊天功能(1)项目介绍及自定义按钮实现

我们长安按钮,就会开始录音,并且会同时弹出一个麦克风的对话框提示正在录音。如果在录音的过程中手指上滑,则会将录音取消。而如果是录音时间太短,则会提示录音时间太短,完成录音。正常录音后,会显示在按钮上方。

从图中,不难看出在整个界面里,上方是是一个 ListView用来显示录音,而下面就是一个按钮。按钮有三种状态,即正常录音,取消录音和无操作时的默认状态。与按钮对应,对话框也有三种状态,即正常录音,录音取消,录音时间太短这三种状态。因此完成这个小项目,需要我们具备ListView的基本知识,熟悉自定义按钮和自定义对话框。同时对android提供的录音器类和音频播放的类也要基本了解。

我打算将这个小项目分成以下几步来做:

(1)完成按钮的交互设计。

(2)完成对话框的设计。

(3)完成录音时的代码部分,并集成到之前的代码里。

(4)完成播放设置,项目也从此结束。

上面只是我为了学习知识,简单实现的UI。并不是很好看,读者有要求,可以自己耐心调一调,让界面好看一点。在这里我不打算讲解所涉及的android中的基础知识,而是直接写项目的代码。好了,我们开始第一步,完成按钮的设计,即实现按钮的三种状态。

按钮的实现

首先把主界面给搭建起来,即上方是LisetView,下面是一个我们自定义的按钮。新建activity_main.xml,代码如下:

接下来,在res文件下新建文件夹drawable,然后在里面新建btn_bg.xml。这是为自定义的按钮提供不同状态下的按钮背景图片。代码如下:

然后在res下的values文件夹下的strings里面,定义按钮需要显示的文本,如下:

接来准备工作都做的差不多了。然后实现我们的自定义按钮。新建类RecoderButton,该类继承自Button。具体代码如下:

最后,我们在主类中加载activity_main布局。如下:

然后我们运行这个android项目即可。就会发现随着我们手指的移动,按钮呈现出我们想要的状态。

当然了,为了好看,我们在AndroidManifest文件里让app全屏显示。只需要加上这么一句即可:

<span style="font-size: medium;"> android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"</span>

好了,我们的第一步总算迈出去了。主要就是一个自定义按钮的实现。下一文章中我们将会实现对话框。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/rec_listview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.fuly.util.RecoderButton
android:id="@+id/btn_recoder"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg"
android:layout_marginTop="5dp"
android:layout_marginBottom="3dp"
android:text="@string/btn_normal"
android:layout_gravity="center"/>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/black"/>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 没有触摸时的图片 -->
<item
android:state_focused="true"
android:state_enabled="true"
android:state_pressed="false"
android:drawable="@drawable/btn_normal"/>
<!-- 触摸时的图片 -->
<item
android:state_enabled="true"
android:state_pressed="true"
android:drawable="@drawable/btn_press"/>
<item
android:state_enabled="true"
android:state_checked="true"
android:drawable="@drawable/btn_press"/>
<!-- 默认时的背景图片-->
<item
android:drawable="@drawable/btn_normal"/>
</selector>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">irecoder</string>
<string name="action_settings">Settings</string>
<string name="btn_normal">按住 录音</string>
<string name="btn_recoding">松开 结束</string>
<string name="btn_cancel">手指上滑,取消录音 录音</string>
</resources>
package com.fuly.util;
import com.fuly.irecoder.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
public class RecoderButton extends Button{
//按钮的三个状态
private static final int STATE_NORMAL = 1;//正常
private static final int STATE_RECODING = 2;//录音状态
private static final int STATE_CACLE = 3;//取消状态
private int mCurState = STATE_NORMAL;//记录当前按钮状态
private int Y = 50;//限定手指移动的上下宽度
public RecoderButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
//捕捉按钮点击事件
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y =(int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
changeState(STATE_RECODING);//按下按钮,改变按钮状态
break;
case MotionEvent.ACTION_MOVE:
if(wantCancel(x,y)){ //如果检测到取消,则改变按钮状态为取消
changeState(STATE_CACLE);
}else{
changeState(STATE_RECODING);
}
break;
case MotionEvent.ACTION_UP:
reset();//各种设置复位
break;
default:
break;
}
return super.onTouchEvent(event);
}
//复位
private void reset() {
mCurState = STATE_NORMAL;
changeState(STATE_NORMAL);
}
//检查手指移动范围,从而确定用户是否想取消录音
private boolean wantCancel(int x, int y) {
if(x<0||x>getWidth()){
return true;
}
if(y<0||y>getHeight()+Y){
return true;
}
return false;
}
//改变状态,包括按钮等操作
private void changeState(int state) {
if(mCurState != state){
mCurState = state;
}
switch(mCurState){
case STATE_NORMAL:
setText(R.string.btn_normal);
break;
case STATE_RECODING:
setText(R.string.btn_recoding);
break;
case STATE_CACLE:
setText(R.string.btn_cancel);
break;
default:
break;
}
}
}
package com.fuly.irecoder;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@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开发——在Android应用中实现微信语音聊天中的对话框控件 http://www.mobile-open.com/2015/84279.html

本文主要讲述了如何在Android应用实现微信语音聊天中的对话框控件,包括录音,取消录音等功能,现在把先关的实现思路和代码整理出来分享给Android程序员兄弟们,希望给他们的开发工作带来帮助。

在本篇文章里,我们做第二步,也就是实现几种状态的对话框:录音状态的对话框,取消录音状态下的对话框,录音时间太短下的对话框。然后将对话框集成到我们点击时的按钮操作中。

 首先我们需要自定义一个对话框的布局。不难想出,布局中的上方需要并排放置两张图片,下方是一个用来提示状态的文本。如果对话框切换到录音时的状态,我们就让其中的一张图片显示,另外一张不显示即可。所以整体的布局是这样的,名称为dialog.xml:
 
 接下来我们定义一下对话框的样式,在这里关键是要求对话框弹出时,不要屏幕掉屏幕,也就是屏幕的其他部分仍然是点击有效的。在res的values下的styles下,加入一下代码:
 
 然后修改strings.xml文件。为什么所有的文字我们非得这么麻烦的放在这个文件里引用的。答案是:这样子做可以有效的防止你的app的内存占用。所以要养成良好的编程习惯。不多说了,代码如下:
 
 然后我们要配置颜色了,在res下的values文件夹下的color.xml(如果没有你就新建一个)里面写上下面的代码:
 
 好了,关于对话框的工作我们基本上算是完成了。下面将其集成到按钮中,我们打开RecoderButton类,修改其中的代码如下:
 
 行了,至此,我们这一阶段的工作算是完成了。下面赶紧运行以下android程序,看看有什么新的效果呢?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/dialog_loading_bg">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_recoder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/recorder"
android:visibility="visible"/>
<ImageView
android:id="@+id/img_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/v1"
android:visibility="visible"/>
</LinearLayout>
<TextView
android:id="@+id/tv_dialog_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_recoding"
android:textColor="@color/red"
android:layout_gravity="center"
android:textSize="20dip"/>
</LinearLayout>
<style name = "dialogStyle">
<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>
package com.fuly.util;
import com.fuly.irecoder.R;
import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
//对话框管理类
public class DialogManager {
private Dialog dialog;
private ImageView imgDialogRecoder;
private ImageView imgVoice;
private TextView tvDialog;
private Context mContext;
public DialogManager(Context context){
mContext = context;
}
public void dialogShow(){
//此时我们选择我们自己的对话框样式
dialog = new Dialog(mContext, R.style.dialogStyle);
View view = LayoutInflater.from(mContext).inflate(R.layout.dialog, null);
dialog.setContentView(view);
dialog.show();
//获取控件,用来在下面的代码中改变他们的状态
imgDialogRecoder = (ImageView) dialog.findViewById(R.id.img_recoder);
imgVoice = (ImageView) dialog.findViewById(R.id.img_voice);
tvDialog = (TextView) dialog.findViewById(R.id.tv_dialog_txt);
}
//录音时的对话框状态
public void dialogRecoding(){
imgDialogRecoder.setVisibility(View.VISIBLE);
imgVoice.setVisibility(View.VISIBLE);
imgDialogRecoder.setImageResource(R.drawable.recorder);
imgVoice.setImageResource(R.drawable.v1);
tvDialog.setText(R.string.dialog_recoding);
}
//录音时,要更新声音等级,即让imgVoice动起来
public void updateVoiceLevel(int level){
//根据字符串和包名来获得所对应的资源文件,在这里获取的R.drawable下的文件
int resId = mContext.getResources().getIdentifier("v"+level,"drawable",mContext.getPackageName());
imgVoice.setImageResource(resId);
}
//录音取消时的对话框状态
public void dialogRecoderCancel(){
imgDialogRecoder.setVisibility(View.VISIBLE);
imgVoice.setVisibility(View.GONE);
imgDialogRecoder.setImageResource(R.drawable.cancel);
tvDialog.setText(R.string.dialog_cacel);
}
public void tooShort(){
imgDialogRecoder.setVisibility(View.VISIBLE);
imgVoice.setVisibility(View.GONE);
imgDialogRecoder.setImageResource(R.drawable.voice_to_short);
tvDialog.setText(R.string.too_short);
}
//取消对话框
public void dialogDismiss(){
if(dialog != null){
dialog.dismiss();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">irecoder</string>
<string name="action_settings">Settings</string>
<string name="btn_normal">按住 录音</string>
<string name="btn_recoding">松开 结束</string>
<string name="btn_cancel">手指上滑,取消录音 </string>
<string name="dialog_recoding">手指上滑,取消录音 </string>
<string name="dialog_cacel">松开手指 取消发送</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#000000</color>
<color name="red">#CC0000</color>
<color name="white">#ffffff</color>
</resources>
package com.fuly.util;
import com.fuly.irecoder.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
//定义我们自己的录音按钮
public class RecoderButton extends Button{
//按钮的三个状态
private static final int STATE_NORMAL = 1;//正常
private static final int STATE_RECODING = 2;//录音状态
private static final int STATE_CACLE = 3;//取消状态
private int mCurState = STATE_NORMAL;//记录当前按钮状态
private int Y = 50;//限定手指移动的上下宽度
private DialogManager mDialogManager;//对话框管理类
public RecoderButton(Context context, AttributeSet attrs) {
super(context, attrs);
mDialogManager = new DialogManager(context);//实例化对话框管理类
}
//捕捉按钮点击事件
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y =(int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
mDialogManager.dialogShow();//按下按钮的同时将对话框显示出来
changeState(STATE_RECODING);//按下按钮,改变按钮状态
break;
case MotionEvent.ACTION_MOVE:
if(wantCancel(x,y)){ //如果检测到取消,则改变按钮状态为取消
changeState(STATE_CACLE);
}else{
changeState(STATE_RECODING);
}
break;
case MotionEvent.ACTION_UP:
mDialogManager.dialogDismiss();
reset();//各种设置复位
break;
default:
break;
}
return super.onTouchEvent(event);
}
//复位
private void reset() {
mCurState = STATE_NORMAL;
changeState(STATE_NORMAL);
}
//检查手指移动范围,从而确定用户是否想取消录音
private boolean wantCancel(int x, int y) {
if(x<0||x>getWidth()){
return true;
}
if(y<0||y>getHeight()+Y){
return true;
}
return false;
}
//改变状态,包括按钮等操作
private void changeState(int state) {
if(mCurState != state){
mCurState = state;
}
switch(mCurState){
case STATE_NORMAL:
setText(R.string.btn_normal);
break;
case STATE_RECODING:
setText(R.string.btn_recoding);
mDialogManager.dialogRecoding();
break;
case STATE_CACLE:
setText(R.string.btn_cancel);
mDialogManager.dialogRecoderCancel();//此时也要将对话框的状态显示出来
break;
default:
break;
}
}
}

Android开发——实现微信语音聊天功能中的录音功能 http://www.mobile-open.com/2015/84278.html

2015年09月29日 ⁄ Android开发经验

本文主要讲述了如何在Android应用中实现微信语音聊天中的录音功能,现在把相关的实现思路和代码整理出来分享给广大的Android程序员兄弟们,希望给他们的开发工作带来帮助。

在这一篇文章中,我们接着往下做,实现核心部分,即录音功能的实现。这里需要读者具备一定的MediaPlayer这个类的一些基础知识。

首先我们要在添加一下权限,切记,这个步骤千万不要忘记了。代码如下:

下面我们就可以痛快的编写实现录音功能的类了。代码如下:

最后,将录音功能集成到按钮中。这个可能要复杂一些,希望你有耐心做下去。具体代码如下:

至此,恭喜你,这个项目基本上完成一半了。下面我们运行一下android程序,然后对着手机麦克风说话,是不是会弹出对话框,而且对话框上的图标会随着你说话声音的大小而跳动呢?快运行一下吧。

<span style="font-size: medium;"><uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
</span>
package com.fuly.util;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import android.media.MediaRecorder;
//一个管理录音的类
public class RecoderManager {
private MediaRecorder mMediaRcoder;//录音器
private static RecoderManager mRecoderManager;
private RecoderManagerListener mListener;
private static String dir;//音频存放的文件夹
private String mCurPath;//用来记录音频即时存放的路径名
private boolean isPrepared ;//判断录音器是否已经准备好了
private RecoderManager(String dir){
this.dir = dir;//传入保存音频的文件夹的地址
}
public static RecoderManager getRecoderMananger(String dir){
if(mRecoderManager == null){
synchronized (RecoderManager.class) {
if(mRecoderManager == null){
mRecoderManager = new RecoderManager(dir);
}
}
}
return mRecoderManager;
}
//提供一个回调接口,当录音准备好了后,调用该接口的方法,录音正式开始,此时就可以获取声音等级等东西了
public interface RecoderManagerListener{
void wellPrepared();//当录音准备好了就会调用这个方法
}
public void setOnRecoderManagerListener(RecoderManagerListener listener){
this.mListener = listener;
}
//录音的准备工作,要准备好录音存取的文件地址,录音器的准备等
public void recoderPrepared(){
isPrepared = false;
File mDir = new File(dir);
if(!mDir.exists()){
mDir.mkdir();//生成文件夹
}
String fileName = generateName(); //录下的声音所输出的文件名
File file = new File(mDir,fileName);//最终在文件夹mDir下面生成文件fileName
mCurPath = file.getAbsolutePath();//记录下即时存放所录音频的文件的完整路径名
try {
/*
* 下面的代码为初始化录音的这个实例,并做录音准备工作
*/
mMediaRcoder = new MediaRecorder();
//设置音频输出到哪个文件中,注意该参数应该是一个完成的路径,最终文件应该是.mar格式的。
mMediaRcoder.setOutputFile(file.getAbsolutePath());
//设置音频源为我们的麦克风
mMediaRcoder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置音频格式
mMediaRcoder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
//设置音频的编码格式为amr
mMediaRcoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRcoder.prepare();
mMediaRcoder.start();
isPrepared = true;
if(isPrepared){
mListener.wellPrepared();//回调,即回调按钮里重写的该方法
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//该方法用来随机生成文件名
private String generateName() {
return UUID.randomUUID().toString()+".amr";
}
//通过音频获得声音的级别,转化为1~maxLevel之间
public int getVoiceLevel(int maxLevel){
if(mMediaRcoder != null){
try {
return maxLevel*mMediaRcoder.getMaxAmplitude()/32768+1;
} catch (IllegalStateException e) {//在这里,我们捕捉一下错误,是为了不让影响程序进行。
//因为就算音频没法捕捉到,也不是什么大事,只要声音录制到了就可以正常进行。
//所以在此忽略掉这个错误
}
}
return 1; //没有捕捉到音频,就默认为等级为1,并返回
}
//释放资源
public void release(){
if(mMediaRcoder != null){
mMediaRcoder.stop();
mMediaRcoder.release();
mMediaRcoder = null;
}
}
//录音取消时的操作
public void cancel(){
//注意此刻一定不要只调用mMediaRecoder.release()方法。除非你调用它之前
//再调用一下它的stop方法。一定注意顺序.不然会除非你release()的时候,录音却没停止。
//但是程序也不报错,就出现闪退。血泪教训啊
release();//调用我们刚刚写好的release()
if(mCurPath != null){
File file = new File(mCurPath);
if(file.exists()){
file.delete();
mCurPath = null;
}
}
}
//提供一个获取录音存放的路径的方法
public String getPath(){
return mCurPath;
}
}
package com.fuly.util;
import com.fuly.irecoder.R;
import com.fuly.util.RecoderManager.RecoderManagerListener;
import com.fuly.util.RecoderManager;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
//定义我们自己的录音按钮
public class RecoderButton extends Button implements RecoderManagerListener{
//按钮的三个状态
private static final int STATE_NORMAL = 1;//正常
private static final int STATE_RECODING = 2;//录音状态
private static final int STATE_CACLE = 3;//取消状态
private int mCurState = STATE_NORMAL;//记录当前按钮状态
private int Y = 50;//限定手指移动的上下宽度
private DialogManager mDialogManager;//对话框管理类
private RecoderManager mRecoderManager;//录音器的管理类
private boolean isRecoding = false;
private boolean isLongClick =false;//是否为长安按钮,默认为没有触发
private float mTime=0;//用来记录录音的时长
private RecoderButtonListener mListener;//用来传递数据的实体
public RecoderButton(Context context) {
this(context,null);
}
public RecoderButton(Context context, AttributeSet attrs) {
super(context, attrs);
mDialogManager = new DialogManager(context);//实例化对话框管理类
String path = Environment.getExternalStorageDirectory()+"//MyAudio";
Log.d("付勇j的文件夹---->",path);
mRecoderManager = RecoderManager.getRecoderMananger(path);//获取一个实例
mRecoderManager.setOnRecoderManagerListener(this);
setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
isLongClick = true;
mRecoderManager.recoderPrepared();
return false;
}
});
}
//定义一个回调接口,用来将数据返回,录音的时长和录音存放的路径
public interface RecoderButtonListener{
void onFinish(int mTime,String filePath);
}
public void setOnRecoderButtonListener( RecoderButtonListener listener){
this.mListener = listener;
}
private static final int CHANGE_VOICE = 0X110;
private static final int DIALOG_DISS = 0X111;
private static final int MEDIA_PREPARED = 0X112;
private Runnable mRunnable = new Runnable(){
public void run() {
while(isRecoding){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
mTime+=0.1f;
mHandler.sendEmptyMessage(CHANGE_VOICE);
}
}
};
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch(msg.what){
case MEDIA_PREPARED:
mDialogManager.dialogShow();
isRecoding = true;
new Thread(mRunnable).start();
break;
case CHANGE_VOICE:
//获取声音等级,并在对话框中改变
mDialogManager.updateVoiceLevel(mRecoderManager.getVoiceLevel(7));
break;
case DIALOG_DISS:
mDialogManager.dialogDismiss();
break;
}
};
};
//回调方法,当该方法在RecoderManager中被调用时,说明录音器已经准备完毕
//可以开始录音了
public void wellPrepared() {
mHandler.sendEmptyMessage(MEDIA_PREPARED);
}
//捕捉按钮点击事件
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y =(int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
changeState(STATE_RECODING);//按下按钮,改变按钮状态
break;
case MotionEvent.ACTION_MOVE:
if(isRecoding){
if(wantCancel(x,y)){ //如果检测到取消,则改变按钮状态为取消
changeState(STATE_CACLE);
mDialogManager.dialogRecoderCancel();
}else{
changeState(STATE_RECODING);
mDialogManager.dialogRecoding();
}
}
break;
case MotionEvent.ACTION_UP:
//手指抬起的几种情况
//(1)正常录音结束后的抬起 (2)取消录音的抬起 (3)迅速抬起,此时会造成录音时间过短
//(2)可能录音器还没准备好,就手指抬起了。 也可看成是录音的时间太短(5)没有触发长安安钮就抬起
if(!isLongClick){ //如果没有触发长安安钮
reset();
return super.onTouchEvent(event);
}
if(!isRecoding||mTime<0.6f){//如果没有准备好录音器或者录音时间太短
mDialogManager.tooShort();
mRecoderManager.cancel();
mHandler.sendEmptyMessageDelayed(DIALOG_DISS, 2000);
}else if(mCurState == STATE_RECODING){//正常录音结束
//在这里应该返回录音的文件路径和时长给播放器
mDialogManager.dialogDismiss();
mRecoderManager.release();
//此时应将录音的时长和路径传递给MainActivity
if(mListener != null){
mListener.onFinish((int)mTime, mRecoderManager.getPath());
}
}else if(mCurState == STATE_CACLE){// 如果为取消录音的抬起
mDialogManager.dialogDismiss();
mRecoderManager.cancel();
}
reset();//各种设置复位
break;
default:
break;
}
return super.onTouchEvent(event);
}
//复位
private void reset() {
isRecoding = false;
isLongClick =false;
mTime = 0;
mCurState = STATE_NORMAL;
setText(R.string.btn_normal);
}
//检查手指移动范围,从而确定用户是否想取消录音
private boolean wantCancel(int x, int y) {
if(x<0||x>getWidth()){
return true;
}
if(y<-Y||y>getHeight()+Y){
return true;
}
return false;
}
//改变状态,包括按钮等
private void changeState(int state) {
if(mCurState != state){
mCurState = state;
switch(mCurState){
case STATE_NORMAL:
setText(R.string.btn_normal);
break;
case STATE_RECODING:
setText(R.string.btn_recoding);
break;
case STATE_CACLE:
setText(R.string.btn_cancel);
break;
default:
break;
}
}
}
}

Android开发——实现微信语音聊天功能中的音频播放功能 http://www.mobile-open.com/2015/84280.html

本文主要讲述了如何在Android应用中实现微信语音聊天功能中的音频播放功能,现在把相关的实现思路和代码整理出来分享给广大的Android程序员兄弟们,希望给他们的开发工作带来帮助。

在这一篇中,我们将实现把录音显示在我们早就设计好的ListView里面,并且点击时,会播放录音。实现过程相对来说比较复杂一些。但是只要有耐心,就能做的好。好了,废话不多说,我们直接看代码。

首先,我们来实现播放器,代码如下:

接下来就要考虑将播放器集成到ListView中了。首先我们需要一个封装音频信息的类,包括录音的时长和录音存放的绝对路径。代码如下:

然后为ListView的子项编写布局:

然后ListView需要一个适配器,我们建立出来,如下:

最后再MainActivity里,我们集成播放器,设置ListView。代码如下:

现在模仿微信中的对话框、录音、播放音频功能都已经实现,通过本项目你已经进入了解了如何开发一个Android应用,下面我们继续努力,争取早日成为一名合格的Android开发工程师。

package com.fuly.util;
import java.io.IOException;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnErrorListener;
//播放录音的类
public class RecoderPlayer {
private static MediaPlayer mMediaPlayer;
private static boolean isPause = false;//是否为暂停播放
//播放音乐
public void playSound(String filePath) {
if(mMediaPlayer == null){
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnErrorListener(new OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
mMediaPlayer.reset();
return false;
}
});
}else{
mMediaPlayer.reset();
}
try {
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(filePath);
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//暂停播放
public static void pause(){
if(mMediaPlayer != null && mMediaPlayer.isPlaying()){
mMediaPlayer.pause();
isPause = true;
}
}
//复位播放
public static void reset(){
if(mMediaPlayer != null && isPause){
mMediaPlayer.start();
isPause = false;
}
}
//释放资源
public static void release(){
if(mMediaPlayer != null){
mMediaPlayer.release();
mMediaPlayer = null;
}
}
}
package com.fuly.util;
//封装录音信息的类
public class Recoder {
public int mTime;
public String filePath;
public Recoder(int mTime, String filePath) {
super();
this.mTime = mTime;
this.filePath = filePath;
}
public int getmTime() {
return mTime;
}
public void setmTime(int mTime) {
this.mTime = mTime;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<ImageView
android:id="@+id/id_icon"
android:layout_width="65dp"
android:layout_height="65dp"
android:src="@drawable/icon"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_alignParentRight="true"/>
<FrameLayout
android:id= "@+id/id_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/id_icon"
android:background="@drawable/chatto_bg_focused"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp">
<ImageView
android:id="@+id/img_voice"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/adj"/>
</FrameLayout>
<TextView
android:id ="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/id_frame"
android:layout_marginRight="3dp"
android:text=""
android:textColor="@color/red"
android:layout_marginTop="20dp"/>
</RelativeLayout>
package com.fuly.util;
import java.util.List;
import com.fuly.irecoder.R;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends ArrayAdapter<Recoder> {
private Context mContext;
private int width;//屏幕宽度
public MyAdapter(Context context, List<Recoder> datas) {
super(context, -1,datas );
mContext = context;
//下面的代码为获得屏幕宽度
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metric = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metric);
width = metric.widthPixels;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh = null;
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false);
vh = new ViewHolder();
vh.tv = (TextView) convertView.findViewById(R.id.tv_time);
vh.fl = (FrameLayout) convertView.findViewById(R.id.id_frame);
convertView.setTag(vh);
}else{
vh = (ViewHolder) convertView.getTag();
}
vh.tv.setText(getItem(position).mTime+"/"");//设定显示的时间
//下面三句为设定vh.fl的宽度
LayoutParams lp = vh.fl.getLayoutParams();
int w = width*getItem(position).mTime/35;
lp.width = w>(width*3/4)?(width*3/4):w;
return convertView;
}
class ViewHolder{
private TextView tv;
private FrameLayout fl;
}
}
package com.fuly.irecoder;
import java.util.ArrayList;
import java.util.List;
import com.fuly.util.MyAdapter;
import com.fuly.util.Recoder;
import com.fuly.util.RecoderButton;
import com.fuly.util.RecoderButton.RecoderButtonListener;
import com.fuly.util.RecoderPlayer;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends Activity {
private ListView mListView ;
private MyAdapter mAdapter;
private List<Recoder> mDatas = new ArrayList<Recoder>();
private RecoderPlayer mPlayer;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.rec_listview);
RecoderButton button = (RecoderButton) findViewById(R.id.btn_recoder);
button.setOnRecoderButtonListener(new RecoderButtonListener() {
public void onFinish(int mTime, String filePath) {
Recoder recoder = new Recoder(mTime,filePath);
mDatas.add(recoder);
mAdapter.notifyDataSetChanged();//通知状态发生改变,即有新数据添加进来
//设置ListView为最后一一项
mListView.setSelection(mDatas.size()-1);
}
});
mAdapter = new MyAdapter(this, mDatas);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//播放音频
mPlayer = new RecoderPlayer();
//OnCompletionListener是播放结束的监听器
mPlayer.playSound(mDatas.get(position).filePath);
}
});
}
@Override
protected void onPause() {
RecoderPlayer.pause();
super.onPause();
}
@Override
protected void onRestart() {
RecoderPlayer.reset();
super.onRestart();
}
@Override
protected void onDestroy() {
RecoderPlayer.release();
mDatas.clear();
super.onDestroy();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment