主要实现录音功能,录音,停止录音,保存录音,然后可以查看录音列表,还用sqlite数据库实现了用户的注册登录功能。
视频
录音fragment,RecordingFragment
package example.com.recording.fragments;
import android.content.DialogInterface;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import de.greenrobot.event.EventBus;
import example.com.recording.R;
import example.com.recording.bean.ListChangeEvent;
import example.com.recording.util.date.DateFormatUtil;
/**
* 录音的fragment
*/
public class RecordingFragment extends Fragment implements View.OnClickListener {
// 开始录音
private Button start;
// 重置
private Button reset;
// 录音类
private MediaRecorder mediaRecorder;
// 以文件的形式保存
// private File recordFile;
private TextView titleTv;
//总的分钟、秒的显示TextView
private TextView timeTv1, timeTv2;
//总秒
private int seconds;
//总分钟
private int minute;
//执行周期性或定时任务类
private ScheduledExecutorService service;
//线程是否停止的判断
private boolean isStart = false;
//是否重置的判断重置了handler就不在接收消息
private boolean isReset = false;
//handler接收线程传过来的消息显示当前定时时间
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
setData();
break;
}
}
};
//文件路径
private String fileName;
//获取sd根目录
public static final String ROOT_FILE = Environment.getExternalStorageDirectory().getAbsolutePath();
private String path;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_recording, null);
initView(view);
Listener();
titleTv.setText("录音");
return view;
}
private void initView(View view) {
titleTv = (TextView) view.findViewById(R.id.base_title_tv);
start = (Button) view.findViewById(R.id.start);
reset = (Button) view.findViewById(R.id.reset);
reset.setEnabled(false);
timeTv1 = (TextView) view.findViewById(R.id.time_tv1);
timeTv2 = (TextView) view.findViewById(R.id.time_tv2);
path = ROOT_FILE + "/" + getActivity().getPackageName() + "/record";
}
private void Listener() {
start.setOnClickListener(this);
reset.setOnClickListener(this);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onClick(View v) {
int Id = v.getId();
switch (Id) {
case R.id.start:
isReset = false;
if (isStart) {
isStart = false;
pauseRecording();
reset.setEnabled(true);
pauseTime();
start.setText("开始");
} else {
isStart = true;
start.setText("暂停");
reset.setEnabled(false);
startRecording();
start();
}
break;
case R.id.reset:
isStop = true;
isReset = true;
isStart = false;
start.setText("开始");
if (service != null) {
resetTime();
}
stopRecord();
break;
}
}
private boolean isStop = true;
//录音完成保存或删除文件
private void recordOver(String s) {
final File recordFile = new File(s);
if (recordFile != null && recordFile.exists()) {
final EditText et = new EditText(getActivity());
et.setText(recordFile.getName().substring(0, recordFile.getName().length() - 4));
new AlertDialog.Builder(getActivity()).setTitle("文件名")
.setView(et)
.setCancelable(false)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String s = et.getText().toString();
//文件名为空就按源文件保存
if (s.isEmpty()) {
Toast.makeText(getActivity(), "文件名不能为空", Toast.LENGTH_SHORT).show();
} else {
recordFile.renameTo(new File(path + "/" + s + ".amr"));
EventBus.getDefault().post(new ListChangeEvent());
}
}
})
.setNegativeButton("删除", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
recordFile.delete();
}
})
.show();
} else {
Toast.makeText(getActivity(), "还未录制", Toast.LENGTH_SHORT).show();
}
}
//暂停计时
private void pauseTime() {
service.shutdown();
}
//重置后的操作
private void resetTime() {
service.shutdown();
service = null;
seconds = 0;
minute = 0;
setText();
}
//设置时间数据
private void setData() {
if (!isReset) {
if (seconds == 59) {
seconds = 0;
minute = minute + 1;
if (minute == 59) {
minute = 0;
Toast.makeText(getActivity(), "已是最大数值", Toast.LENGTH_SHORT).show();
start.setText("开始");
isStart = false;
service.shutdown();
}
} else {
seconds = seconds + 1;
}
setText();
}
}
//显示时间
private void setText() {
timeTv1.setText(getNum(minute));
timeTv2.setText(getNum(seconds));
}
//数据转换成两位String
private String getNum(int num) {
return String.format("%02d", num);
}
//保存每次录制的路径
private List<String> mList = new ArrayList<>();
//开始录音
private void startRecording() {
if(isStop){
isStop = false;
mList.clear();
}
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
fileName = path + "/" + getTime() + ".amr";
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 选择amr格式
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
mediaRecorder.setOutputFile(fileName);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
// 准备好开始录音
mediaRecorder.prepare();
mediaRecorder.start();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//暂停录音
private void pauseRecording() {
try {
mediaRecorder.stop();
mediaRecorder.release();
//录制的文件路径添加到列表
mList.add(fileName);
} catch (Exception e) {
Log.e("Exception", e.getMessage(), e);
Toast.makeText(getActivity(), "录制异常", Toast.LENGTH_SHORT).show();
}
}
// 完成录音
private void stopRecord() {
reset.setEnabled(false);
// 最后合成的音频文件
fileName = path + "/" + getTime() + ".amr";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
FileInputStream fileInputStream = null;
try {
for (int i = 0; i < mList.size(); i++) {
Log.e("stopRecord",mList.get(i));
File file = new File(mList.get(i));
// 把因为暂停所录出的多段录音进行读取
fileInputStream = new FileInputStream(file);
byte[] mByte = new byte[fileInputStream.available()];
int length = mByte.length;
// 第一个录音文件的前六位是不需要删除的
if (i == 0) {
while (fileInputStream.read(mByte) != -1) {
fileOutputStream.write(mByte, 0, length);
}
}
// 之后的文件,去掉前六位
else {
while (fileInputStream.read(mByte) != -1) {
fileOutputStream.write(mByte, 6, length - 6);
}
}
}
recordOver(fileName);
} catch (Exception e) {
// 这里捕获流的IO异常,万一系统错误需要提示用户
e.printStackTrace();
Toast.makeText(getActivity(), "录音合成出错,请重试!", Toast.LENGTH_LONG).show();
} finally {
try {
fileOutputStream.flush();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 不管合成是否成功、删除录音片段
for (int i = 0; i < mList.size(); i++) {
File file = new File(mList.get(i));
if (file.exists()) {
file.delete();
}
}
}
//获取当前时间作为文件名
private String getTime() {
return DateFormatUtil.getDateFormat(System.currentTimeMillis(), "yyyyMMddHHmmss");
}
//启动倒计时
private void start() {
service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage(1);
}
}, 1, 1, TimeUnit.SECONDS);
}
}
相关xml界面
fragment_recording
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<include
android:id="@+id/title"
layout="@layout/base_title" />
<LinearLayout
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="20dp"
android:gravity="center"
android:background="@drawable/shape_oval"
android:orientation="horizontal">
<TextView
android:id="@+id/time_tv1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="center"
android:text="00"
android:textSize="35sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"
android:textSize="35sp" />
<TextView
android:id="@+id/time_tv2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="center"
android:text="00"
android:textSize="35sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/start"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="@string/reset" />
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="@string/start" />
</LinearLayout>
</LinearLayout>
shape_oval
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/base_orange"/>
</shape>