楼主本来打算昨晚写的,但是昨天是万圣节,晚上不想动弹,所以推到今天来写了!简单说明一下,由于项目中用到了语音的录制、播放和上传,所以将功能抽取了出来,整理一下,也方便小伙伴们使用。效果就跟微信上语音聊天一样,长按按钮录制,松手就结束录制,并且能播放。录的时候,有个小话筒随着声音的大小而展现上下浮动的一个效果。大概效果如下图:
这是录完的效果,一开始就是一个录音的按钮,录完后才有下面的显示,可以点击播放,录制过程中,会有个小的话筒出现,如下图:
有时间的记录和图标根据声音分贝大小展示上下波动的一个效果。
首先,展示2个工具类,一个是录制的,一个是播放的。
录制:
package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import android.media.MediaRecorder;
import android.media.MediaRecorder.OnInfoListener;
import android.os.Handler;
import java.io.File;
import java.io.IOException;
public class AudioRecorderUtil {
private final String TAG = AudioRecorderUtil.class.getName();
public static final int MAX_LENGTH = 60000;
private String filePath;
private String folderPath;
private MediaRecorder mMediaRecorder;
private int maxLength;
private long startTime;
private long endTime;
private AudioRecorderUtil.OnAudioStatusUpdateListener audioStatusUpdateListener;
private final Handler mHandler = new Handler();
private Runnable mUpdateMicStatusTimer = new Runnable() {
public void run() {
AudioRecorderUtil.this.updateMicStatus();
}
};
private int BASE = 1;
private int SPACE = 100;
public AudioRecorderUtil(String folderPath) {
File path = new File(folderPath);
if(!path.exists()) {
path.mkdirs();
}
this.folderPath = folderPath;
this.maxLength = '\uea60';
}
public void start() {
if(this.mMediaRecorder == null) {
this.mMediaRecorder = new MediaRecorder();
} else {
try {
this.mMediaRecorder.stop();
this.mMediaRecorder.reset();
this.mMediaRecorder.release();
} catch (Exception var2) {
this.mMediaRecorder.reset();
}
}
this.mHandler.removeCallbacks(this.mUpdateMicStatusTimer);
try {
this.mMediaRecorder.setAudioSource(1);
this.mMediaRecorder.setOutputFormat(0);
this.mMediaRecorder.setAudioEncoder(1);
this.filePath = this.folderPath + File.separator + System.currentTimeMillis() + ".aac";
this.mMediaRecorder.setOutputFile(this.filePath);
this.mMediaRecorder.setMaxDuration(this.maxLength);
this.mMediaRecorder.prepare();
this.mMediaRecorder.setOnInfoListener(new OnInfoListener() {
public void onInfo(MediaRecorder mr, int what, int extra) {
if(what == 800) {
AudioRecorderUtil.this.stop();
}
}
});
this.mMediaRecorder.start();
this.startTime = System.currentTimeMillis();
this.updateMicStatus();
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onStart();
}
} catch (IllegalStateException var3) {
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onError(var3);
}
this.cancel();
} catch (IOException var4) {
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onError(var4);
}
this.cancel();
}
}
public long getSumTime() {
return this.startTime == 0L?0L:System.currentTimeMillis() - this.startTime;
}
public long stop() {
if(this.mMediaRecorder == null) {
return 0L;
} else {
this.endTime = System.currentTimeMillis();
try {
this.mMediaRecorder.stop();
this.mMediaRecorder.reset();
this.mMediaRecorder.release();
this.mMediaRecorder = null;
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onStop(this.filePath);
}
} catch (RuntimeException var3) {
this.mMediaRecorder.reset();
this.mMediaRecorder.release();
this.mMediaRecorder = null;
File file = new File(this.filePath);
if(file.exists()) {
file.delete();
}
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onError(var3);
}
}
this.filePath = "";
return this.endTime - this.startTime;
}
}
public void cancel() {
try {
this.mMediaRecorder.stop();
this.mMediaRecorder.reset();
this.mMediaRecorder.release();
this.mMediaRecorder = null;
} catch (RuntimeException var2) {
this.mMediaRecorder.reset();
this.mMediaRecorder.release();
this.mMediaRecorder = null;
}
File file = new File(this.filePath);
if(file.exists()) {
file.delete();
}
this.filePath = "";
if(this.audioStatusUpdateListener != null) {
this.audioStatusUpdateListener.onCancel();
}
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
private void updateMicStatus() {
if(this.mMediaRecorder != null) {
double ratio = (double)this.mMediaRecorder.getMaxAmplitude() / (double)this.BASE;
double db = 0.0D;
if(ratio > 1.0D) {
db = 20.0D * Math.log10(ratio);
if(null != this.audioStatusUpdateListener) {
this.audioStatusUpdateListener.onProgress(db, System.currentTimeMillis() - this.startTime);
}
}
this.mHandler.postDelayed(this.mUpdateMicStatusTimer, (long)this.SPACE);
}
}
public void setOnAudioStatusUpdateListener(AudioRecorderUtil.OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
public interface OnAudioStatusUpdateListener {
void onStart();
void onProgress(double var1, long var3);
void onError(Exception var1);
void onCancel();
void onStop(String var1);
}
}
播放:
package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
public class AudioPlayerUtil {
private static final String TAG = "AudioRecordTest";
private MediaPlayer mPlayer;
public AudioPlayerUtil() {
}
public void start(String mFileName, OnCompletionListener listener) {
if(this.mPlayer == null) {
this.mPlayer = new MediaPlayer();
} else {
this.mPlayer.reset();
}
try {
this.mPlayer.setDataSource(mFileName);
this.mPlayer.prepare();
this.mPlayer.start();
if(listener != null) {
this.mPlayer.setOnCompletionListener(listener);
}
} catch (Exception var4) {
}
}
public void stop() {
if(this.mPlayer != null) {
this.mPlayer.stop();
this.mPlayer.release();
this.mPlayer = null;
}
}
}
下面就是开始录制的MainActivity了:
package com.thtj.demo;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import static android.content.ContentValues.TAG;
/**
* 录制音频按钮,实现按下录制,松开停止
*/
public class MainActivity extends Activity {
private boolean audioRecorder = false;
private AudioPlayerUtil player;
private Button recordBtn;//录音按钮
private String ROOT_PATH;
ImageView mImageView;
TextView mTextView, tv_time;
PopupWindowFactory mPop;
View view;
LinearLayout record_contentLayout;
ImageView recordDetailView;
private String audioFilePath;// 录音文件保存路径
private AnimationDrawable animationDrawable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recordBtn = (Button) findViewById(R.id.recordBtn);
record_contentLayout = (LinearLayout) findViewById(R.id.record_contentLayout);
recordDetailView = (ImageView) findViewById(R.id.record_detailView);
tv_time = (TextView) findViewById(R.id.tv_time);
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
init(this);
initAudioRecorderBtn();
}
public void init(Context context) {
try {
ROOT_PATH = context.getExternalFilesDir(null).getAbsolutePath();
} catch (Exception e) {
Log.e(TAG, e.getMessage() + "");
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + context.getPackageName();
} else {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
} catch (Throwable e) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
}
private void initAudioRecorderBtn() {
view = View.inflate(this, R.layout.layout_microphone, null);
mPop = new PopupWindowFactory(this, view);
//PopupWindow布局文件里面的控件
mImageView = (ImageView) view.findViewById(R.id.iv_recording_icon);
mTextView = (TextView) view.findViewById(R.id.tv_recording_time);
final AudioRecorderUtil audioRecorderUtil = new AudioRecorderUtil(ROOT_PATH + File.separator + "audio");
audioRecorderUtil.setOnAudioStatusUpdateListener(new AudioRecorderUtil.OnAudioStatusUpdateListener() {
@Override
public void onStart() {
}
@Override
public void onProgress(double db, long time) {
//根据分贝值来设置录音时话筒图标的上下波动,同时设置录音时间
mImageView.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
mTextView.setText(TimeUtils.long2String(time));
}
@Override
public void onError(Exception e) {
}
@Override
public void onCancel() {
}
@Override
public void onStop(String filePath) {
mPop.dismiss();
record_contentLayout.setVisibility(View.VISIBLE);
audioFilePath = filePath;
Log.e("===path", audioFilePath);
// TODO 上传音频文件
}
});
recordBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 停止播放
if (player != null) {
player.stop();
}
audioRecorder = true;//正在录音
// 处理动作
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
audioRecorderUtil.start();
mPop.showAtLocation(view.getRootView(), Gravity.CENTER, 0, 0);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
long time = audioRecorderUtil.getSumTime();
if (time < 1000) {
audioRecorderUtil.cancel();
Toast.makeText(MainActivity.this, "录音时间太短!", Toast.LENGTH_SHORT).show();
audioFilePath = "";
} else {
tv_time.setText(time / 1000 + "s");
}
mImageView.getDrawable().setLevel(0);
mTextView.setText(TimeUtils.long2String(0));
audioRecorderUtil.stop();
mPop.dismiss();
break;
}
return true;
}
});
record_contentLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TextUtils.isEmpty(audioFilePath) || !audioRecorder) {
return;
}
if (player == null) {
player = new AudioPlayerUtil();
} else {
player.stop();
}
recordDetailView.setImageResource(R.drawable.play_voice_right_anim);
animationDrawable = (AnimationDrawable) recordDetailView.getDrawable();
animationDrawable.start();
player.start(audioFilePath, new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
animationDrawable.stop();
recordDetailView.setImageResource(R.mipmap.voice_right);
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (player != null) {
player.stop();
}
}
}
说明:话筒那个布局是用PopupWindow来实现的
package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import android.content.Context;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.widget.PopupWindow;
public class PopupWindowFactory {
private Context mContext;
private PopupWindow mPop;
public PopupWindowFactory(Context mContext, View view) {
this(mContext, view, -2, -2);
}
public PopupWindowFactory(Context mContext, View view, int width, int height) {
this.init(mContext, view, width, height);
}
private void init(Context mContext, View view, int width, int height) {
this.mContext = mContext;
view.setFocusable(true);
view.setFocusableInTouchMode(true);
this.mPop = new PopupWindow(view, width, height, true);
this.mPop.setFocusable(true);
view.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(keyCode == 4) {
PopupWindowFactory.this.mPop.dismiss();
return true;
} else {
return false;
}
}
});
view.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(PopupWindowFactory.this.mPop != null && PopupWindowFactory.this.mPop.isShowing()) {
PopupWindowFactory.this.mPop.dismiss();
return true;
} else {
return false;
}
}
});
}
public PopupWindow getPopupWindow() {
return this.mPop;
}
public void showAtLocation(View parent, int gravity, int x, int y) {
if(!this.mPop.isShowing()) {
this.mPop.showAtLocation(parent, gravity, x, y);
}
}
public void showAsDropDown(View anchor) {
this.showAsDropDown(anchor, 0, 0);
}
public void showAsDropDown(View anchor, int xoff, int yoff) {
if(!this.mPop.isShowing()) {
this.mPop.showAsDropDown(anchor, xoff, yoff);
}
}
public void dismiss() {
if(this.mPop.isShowing()) {
this.mPop.dismiss();
}
}
}
也用到了时间转换:
package com.thtj.demo;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import java.text.SimpleDateFormat;
public class TimeUtils {
public TimeUtils() {
}
public static String long2String(long time) {
int sec = (int)time / 1000;
int min = sec / 60;
sec %= 60;
return min < 10?(sec < 10?"0" + min + ":0" + sec:"0" + min + ":" + sec):(sec < 10?min + ":0" + sec:min + ":" + sec);
}
public static String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(Long.valueOf(System.currentTimeMillis()));
}
}
最后上一下布局:
话筒布局:
<?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_microphone_bj"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/iv_recording_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/record_microphone" />
<TextView
android:id="@+id/tv_recording_time"
android:layout_width="114dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:gravity="center"
android:text="00:00"
android:textColor="#FFFFFF"
android:textSize="16sp" />
</LinearLayout>
主activity布局:
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context="com.thtj.demo.MainActivity">
<Button
android:id="@+id/recordBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="录音"/>
<LinearLayout
android:id="@+id/record_contentLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:visibility="gone">
<FrameLayout
android:layout_width="100dp"
android:layout_height="40dp"
android:background="@mipmap/voice_right_bg">
<ImageView
android:id="@+id/record_detailView"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="20dp"
android:src="@mipmap/voice_right" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:textColor="@android:color/white"
android:layout_marginLeft="50dp"
android:text="2s"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
对了!还用到了帧动画:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@mipmap/v_right_anim1" android:duration= "300"></item>
<item android:drawable="@mipmap/v_right_anim2" android:duration= "300"></item>
<item android:drawable="@mipmap/v_right_anim3" android:duration= "300"></item>
</animation-list>
关于图片资源什么的就不上传了,重点是领会代码即可!也不做说明了!你们能看懂吧?!