**这一章在处理相片与录音档名称的时候有错误,建议在完成这一章的内容以后,再参考[错误修正:4-1 使用照相机与麦克风](https://github.com/macdidi5/AndroidTutorial/blob/master/AndroidTutorial4_1Update.md)修正错误。**
现在行动装置的硬件设备技术已经越来越好了,萤幕的尺寸不断的增加,提供使用者清楚又美观的画面。触控萤幕也几乎是目前行动装置的标准设备,使用触控的方式操作应用程式快速又方便。Android系统内建的音乐播放应用程式,也可以让行动装置成为随身的音乐播放设备。还有画素也越来越高的照像功能,一台行动装置几乎可以应付所有的需求。
行动装置提供高画质的摄影镜头,让使用者随时可以拍摄照片与录影,也几乎已经是行动装置基本的设备与功能了。使用Android系统内建的API与元件,可以在应用程式需要的时候,让使用者拍摄照片与录影,并且把照片或影片档案储存在指定的位置。例如在记事本应用程式中,可以加入照片与录影备忘的功能。
应用程式需要录音的时候,可以使用内建的API执行录音的工作,并且把录音完成的档案储存在指定的位置,例如在记事本应用程式中,可以加入录制语音备忘的功能,让使用者可以随时查询与播放这些录音资讯。
这一章为记事资料加入照相与录音的功能,让这个应用程式的功能可以更完整,使用者可以在新增或修改记事资料的时候,启动相机拍照,还有使用麦克风录制语音备忘。
## 12-1 使用相机拍摄照片
不论是移动电话或平板电脑,几乎都有高画质的摄录镜头设备,让使用者可以随时拍摄与录影。加入拍摄照片的功能可以让应用程式的功能更完整,例如在记事本应用程式加入拍照的功能,记录影像会比文字更清楚与方便。
应用程式需要执行拍照的功能,可以启动系统相机元件执行拍照的工作,它的系统Action名称变量是“MediaStore.ACTION_IMAGE_CAPTURE”,使用这个Action名称建立好的Intent物件,可以呼叫putExtra方法加入照片档案储存位置的设定资料,资料的名称是“MediaStore.EXTRA_OUTPUT”,如果没有指定的话,会使用系统默认的名称储存在默认的位置。
应用程式要执行拍照的功能,装置必须有摄录镜头的设备才可以正确的执行,所以需要在应用程式设定档中加入硬件设备需求的设定。如果需要储存照片档案到外部储存设备,例如记忆卡,需要在应用程式设定档中加入授权设定:
~~~
<!-- 需要摄录镜头设备 -->
<uses-feature android:name="android.hardware.camera" />
<!-- 写入外部储存设备 -->
<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"/>
~~~
Android模拟装置也可以测试相机的功能,不过要先确认模拟装置的设定,关闭已经启动的模拟装置,在Android Studio选择功能表“Tools -> Android -> AVD Manager”,选择模拟装置的编辑图示:
[![AndroidTutorial5_04_01_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_01-300x171.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_01.png)
在模拟装置编辑视窗选择“Show Advanced Settings”:
[![AndroidTutorial5_04_01_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_02-300x152.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_02.png)
如果你的电脑没有连接WebCam,在“Front”与“Back”选择“Emulated”。如果电已经连接WebCam,就可以选择“Webcam0”。完成设定后选择“Finish”:
[![AndroidTutorial5_04_01_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_03-300x152.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_03.png)
回到AVD Manager视窗后,选择模拟装置的启动图示:
[![AndroidTutorial5_04_01_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_04-300x171.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_04.png)
模拟装置启动以后,开启“照相”应用程式,就可以看到模拟照相机的画面:
[![AndroidTutorial5_04_01_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_05-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_05.png)
因为记事元件的画面加入照片以后,在萤幕比较小的装置运作时,画面会超过萤幕的范围,所以需要调整画面的设计。另外也要加入显示照片用的ImageView元件。开启“res/layout/activity_item.xml”,参考下列的内容修改这个画面配置档:
~~~
<?xml version="1.0" encoding="utf-8"?>
<!-- 使用ScrollView为最外层的元件 -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 删除xmlns:android的设定 -->
<TableLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="1"
tools:context="net.macdidi.myandroidtutorial.ItemActivity">
<TableRow>
...
</TableRow>
<TableRow>
...
</TableRow>
<!-- 显示图片 -->
<ImageView
android:id="@+id/picture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:visibility="invisible" />
<TableLayout ...>
<TableRow>
...
</TableRow>
</TableLayout>
<TableLayout ...>
<TableRow>
...
</TableRow>
</TableLayout>
</TableLayout>
<!-- ScrollView的结束标签 -->
</ScrollView>
~~~
因为需要储存照片与录音档案,所以撰写一个档案公用类别。在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“FileUtil”后选择“OK”。参考下列的内容完成这个程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import android.widget.ImageView;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileUtil {
// 应用程式储存盘案的目录
public static final String APP_DIR = "androidtutorial";
// 外部储存设备是否可写入
public static boolean isExternalStorageWritable() {
// 取得目前外部储存设备的状态
String state = Environment.getExternalStorageState();
// 判断是否可写入
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// 外部储存设备是否可读取
public static boolean isExternalStorageReadable() {
// 取得目前外部储存设备的状态
String state = Environment.getExternalStorageState();
// 判断是否可读取
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
// 建立并传回在公用相簿下参数指定的路径
public static File getPublicAlbumStorageDir(String albumName) {
// 取得公用的照片路径
File pictures = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
// 准备在照片路径下建立一个指定的路径
File file = new File(pictures, albumName);
// 如果建立路径不成功
if (!file.mkdirs()) {
Log.e("getAlbumStorageDir", "Directory not created");
}
return file;
}
// 建立并传回在应用程式专用相簿下参数指定的路径
public static File getAlbumStorageDir(Context context, String albumName) {
// 取得应用程式专用的照片路径
File pictures = context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES);
// 准备在照片路径下建立一个指定的路径
File file = new File(pictures, albumName);
// 如果建立路径不成功
if (!file.mkdirs()) {
Log.e("getAlbumStorageDir", "Directory not created");
}
return file;
}
// 建立并传回外部储存媒体参数指定的路径
public static File getExternalStorageDir(String dir) {
File result = new File(
Environment.getExternalStorageDirectory(), dir);
if (!isExternalStorageWritable()) {
return null;
}
if (!result.exists() && !result.mkdirs()) {
return null;
}
return result;
}
// 读取指定的照片档案名称设定给ImageView元件
public static void fileToImageView(String fileName, ImageView imageView) {
if (new File(fileName).exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(fileName);
imageView.setImageBitmap(bitmap);
}
else {
Log.e("fileToImageView", fileName + " not found.");
}
}
// 产生唯一的档案名称
public static String getUniqueFileName() {
// 使用年月日_时分秒格式为档案名称
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
return sdf.format(new Date());
}
}
~~~
开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,加入照相功能需要的字段变量:
~~~
// 档案名称
private String fileName;
// 照片
private ImageView picture;
~~~
同样在“ItemActivity”类别,找到“processViews”方法,参考下列的程式码,加入取得显示照片ImageView元件的程式码:
~~~
private void processViews() {
title_text = (EditText) findViewById(R.id.title_text);
content_text = (EditText) findViewById(R.id.content_text);
// 取得显示照片的ImageView元件
picture = (ImageView) findViewById(R.id.picture);
}
~~~
同样在“ItemActivity”类别,找到“clickFunction”方法,参考下列的程式码,加入启动相机元件的程式码:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
// 启动相机元件用的Intent物件
Intent intentCamera =
new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 照片档案名称
File pictureFile = configFileName("P", ".jpg");
Uri uri = Uri.fromFile(pictureFile);
// 设定档案名称
intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, uri);
// 启动相机元件
startActivityForResult(intentCamera, START_CAMERA);
break;
...
}
}
private File configFileName(String prefix, String extension) {
// 如果记事资料已经有档案名称
if (item.getFileName() != null && item.getFileName().length() > 0) {
fileName = item.getFileName();
}
// 产生档案名称
else {
fileName = FileUtil.getUniqueFileName();
}
return new File(FileUtil.getExternalStorageDir(FileUtil.APP_DIR),
prefix + fileName + extension);
}
~~~
同样在“ItemActivity”类别,找到“onActivityResult”方法,参考下列的程式码,处理完成照相工作后的程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
// 照像
case START_CAMERA:
// 设定照片档案名称
item.setFileName(fileName);
break;
...
}
}
}
~~~
同样在“ItemActivity”类别,新增覆写“onResume”方法的程式码,执行显示照片的工作:
~~~
@Override
protected void onResume() {
super.onResume();
// 如果有档案名称
if (item.getFileName() != null && item.getFileName().length() > 0) {
// 照片档案物件
File file = configFileName("P", ".jpg");
// 如果照片档案存在
if (file.exists()) {
// 显示照片元件
picture.setVisibility(View.VISIBLE);
// 设定照片
FileUtil.fileToImageView(file.getAbsolutePath(), picture);
}
}
}
~~~
完成照相功能的工作了,执行应用程式,新增一个记事资料后选择照相功能:
[![AndroidTutorial5_04_01_06](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_06-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_06.png)
画面出现像这样的相机模拟画面,选择照像按钮:
[![AndroidTutorial5_04_01_07](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_07-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_07.png)
选择确定按钮:
[![AndroidTutorial5_04_01_08](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_08-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_08.png)
记事资料显示拍好的照片,储存记事资料后也会储存照片:
[![AndroidTutorial5_04_01_09](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_09-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_09.png)
## 12-2 录制语音备忘
在行动装置的应用程式使用录音功能,可以让很多工作变得更方便,例如语音备忘录的功能,可以省掉很多输入文字的时间。如果应用程式需要执行录音的工作,使用宣告在“android.media”套件下的“MediaRecorder”类别,应用程式可以设定录音的来源、输出格式、编码和储存盘案的位置。这些是执行设定与录音的方法,要特别注意在程式码中呼叫它们的顺序:
* setAudioSource(int) – 设定录音来源,必须在setOutputFormat方法之前呼叫。设定为“MediaRecorder.AudioSource.MIC”表示录音来源是麦克风。
* setOutputFormat(int) –设定输出格式,必须在setAudioSource方法之后。设定为“MediaRecorder.OutputFormat.THREE_GPP”表示输出为3GP压缩格式。
* setAudioEncoder(int) –设定编码方式,必须在setOutputFormat方法之后。一般设定为“MediaRecorder.AudioEncoder.AMR_NB”。
* setOutputFile(String) –设定输出的档案名称,必须在setOutputFormat方法之后。
* prepare() – 使用设定的内容准备录音。
* start() – 开始录音。
* stop() – 停止录音。
* release() – 清除录音资源。
如果应用程式需要使用装置的录音设备,必须在应用程式设定档“AndroidManifest.xml”加入授权的设定:
~~~
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
~~~
加入录音功能的记事应用程式,可以让使用者选择录音功能按钮,启动一个负责录音的Activity元件,按下录音按钮就可以开始录音:
[![AndroidTutorial5_04_01_10](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_10-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_10.png)
录音的时候,录音按钮会切换为红色的图示,录音的音量变化会在右侧显示:
[![AndroidTutorial5_04_01_11](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_11-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_11.png)
设计录音元件的画面配置档,使用的图形资源可以在GitHub中取得,需要“record_dard_icon.png”与“record_red_icon.png”两个图示档案。开启“res/values/strings.xml”,加入这个元件需要的文字资源:
~~~
<string name="title_record">语音备忘</string>
~~~
在“res/layout”目录按鼠标右键,选择“New -> Layout resrouce file”,在File name输入“activity_record”候选择“OK”。参考下列的内容,完成这个画面资源的设计:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:layout_margin="6sp"
android:padding="6sp">
<ImageButton
android:id="@+id/record_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/record_sound_icon"
android:onClick="clickRecord" />
<ProgressBar
android:id="@+id/record_volumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:max="15"
style="@android:style/Widget.ProgressBar.Horizontal" />
</LinearLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<Button
android:text="@android:string/cancel"
android:onClick="onSubmit" />
<Button
android:id="@+id/record_ok"
android:text="@android:string/ok"
android:onClick="onSubmit" />
</TableRow>
</TableLayout>
</LinearLayout>
~~~
在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“RecordActivity”后选择“OK”。参考下列的内容完成这个Activity元件的程式码:(这里提供的设计包含显示录音中的音量,你可以考虑移除这个部份的程式码,这个元件的设计就会比较简单一些)
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import java.io.IOException;
public class RecordActivity extends Activity {
private ImageButton record_button;
private boolean isRecording = false;
private ProgressBar record_volumn;
private MyRecoder myRecoder;
private String fileName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);
processViews();
// 读取档案名称
Intent intent = getIntent();
fileName = intent.getStringExtra("fileName");
}
public void onSubmit(View view) {
if (isRecording) {
// 停止录音
myRecoder.stop();
}
// 确定
if (view.getId() == R.id.record_ok) {
Intent result = getIntent();
setResult(Activity.RESULT_OK, result);
}
finish();
}
private void processViews() {
record_button = (ImageButton) findViewById(R.id.record_button);
record_volumn = (ProgressBar) findViewById(R.id.record_volumn);
// 隐藏状态列ProgressBar
setProgressBarIndeterminateVisibility(false);
}
public void clickRecord(View view) {
// 切换
isRecording = !isRecording;
// 开始录音
if (isRecording) {
// 设定按钮图示为录音中
record_button.setImageResource(R.drawable.record_red_icon);
// 建立录音物件
myRecoder = new MyRecoder(fileName);
// 开始录音
myRecoder.start();
// 建立并执行显示麦克风音量的AsyncTask物件
new MicLevelTask().execute();
}
// 停止录音
else {
// 设定按钮图示为停止录音
record_button.setImageResource(R.drawable.record_dark_icon);
// 麦克风音量归零
record_volumn.setProgress(0);
// 停止录音
myRecoder.stop();
}
}
// 在录音过程中显示麦克风音量
private class MicLevelTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... args) {
while (isRecording) {
publishProgress();
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
Log.d("RecordActivity", e.toString());
}
}
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
record_volumn.setProgress((int) myRecoder.getAmplitudeEMA());
}
}
// 执行录音并且可以取得麦克风音量的录音物件
private class MyRecoder {
private static final double EMA_FILTER = 0.6;
private MediaRecorder recorder = null;
private double mEMA = 0.0;
private String output;
// 建立录音物件,参数为录音储存的位置与档名
MyRecoder(String output) {
this.output = output;
}
// 开始录音
public void start() {
if (recorder == null) {
// 建立录音用的MediaRecorder物件
recorder = new MediaRecorder();
// 设定录音来源为麦克风,必须在setOutputFormat方法之前呼叫
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设定输出格式为3GP压缩格式,必须在setAudioSource方法之后,
// 在prepare方法之前呼叫
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
// 设定录音的编码方式,必须在setOutputFormat方法之后,
// 在prepare方法之前呼叫
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 设定输出的档案名称,必须在setOutputFormat方法之后,
// 在prepare方法之前呼叫
recorder.setOutputFile(output);
try {
// 准备执行录音工作,必须在所有设定之后呼叫
recorder.prepare();
}
catch (IOException e) {
Log.d("RecordActivity", e.toString());
}
// 开始录音
recorder.start();
mEMA = 0.0;
}
}
// 停止录音
public void stop() {
if (recorder != null) {
// 停止录音
recorder.stop();
// 清除录音资源
recorder.release();
recorder = null;
}
}
public double getAmplitude() {
if (recorder != null)
return (recorder.getMaxAmplitude() / 2700.0);
else
return 0;
}
// 取得麦克风音量
public double getAmplitudeEMA() {
double amp = getAmplitude();
mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA;
return mEMA;
}
}
}
~~~
开启应用程式设定档“AndroidManifest.xml”,加入录音元件的设定:
~~~
<!-- 录音元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.RecordActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_record"/>
~~~
完成录音元件的设计后,开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“clickFunction”方法,加入启动录音元件的程式码,还有负责录音的goToRecord方法:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
...
case R.id.record_sound:
// 录音档案名称
final File recordFile = configFileName("R", ".mp3");
// 如果已经有录音档,询问播放或重新录制
if (recordFile.exists()) {
// 询问播放还是重新录制的对话框
AlertDialog.Builder d = new AlertDialog.Builder(this);
d.setTitle(R.string.title_record)
.setCancelable(false);
d.setPositiveButton(R.string.record_play,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 播放
// 在后面的说明才会处理
}
});
d.setNeutralButton(R.string.record_new,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
goToRecord(recordFile);
}
});
d.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
d.show();
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
private void goToRecord(File recordFile) {
// 录音
Intent recordIntent = new Intent(this, RecordActivity.class);
recordIntent.putExtra("fileName", recordFile.getAbsolutePath());
startActivityForResult(recordIntent, START_RECORD);
}
~~~
为记事资料完成录音的功能了,在完成后面的播放功能后,再一起执行测试的工作。
## 12-3 播放语音备忘
在前面已经完成的功能,如果使用者选择的记事资料已经录制过语音备忘,应用程式可以选择播放或是重新录制:
[![AndroidTutorial5_04_01_12](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_12-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_12.png)
使用者选择播放功能,应用程式启动播放语音备忘元件,这个元件提供播放、暂停与停止三个功能按钮:
[![AndroidTutorial5_04_01_13](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_13-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_13.png)
现在设计录音元件的画面配置档,使用的图形资源可以在GitHub中取得,需要“play_icon.png”、“pause_icon”与“stop_icon.png”三个图示档案。开启“res/values/strings.xml”,加入这个元件需要的文字资源:
~~~
<string name="title_play">播放语音备忘</string>
<string name="record_play">播放</string>
<string name="record_new">重新录制</string>
~~~
在“res/layout”目录按鼠标右键,选择“New -> Layout resrouce file”,在File name输入“activity_play”候选择“OK”。参考下列的内容,完成这个画面资源的设计:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:layout_margin="6sp"
android:padding="6sp" >
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/play_icon"
android:onClick="clickPlay" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pause_icon"
android:onClick="clickPause" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stop_icon"
android:onClick="clickStop" />
<SeekBar
android:id="@+id/control"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="8sp" />
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/ok_add_teim"
android:text="@android:string/ok"
android:onClick="onSubmit" />
</LinearLayout>
~~~
在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“PlayActivity”后选择“OK”。参考下列的内容完成这个Activity元件的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class PlayActivity extends Activity {
private MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
Intent intent = getIntent();
String fileName = intent.getStringExtra("fileName");
// 建立指定资源的MediaPlayer物件
Uri uri = Uri.parse(fileName);
mediaPlayer = MediaPlayer.create(this, uri);
}
@Override
protected void onStop() {
if (mediaPlayer.isPlaying()) {
// 停止播放
mediaPlayer.stop();
}
// 清除MediaPlayer物件
mediaPlayer.release();
super.onStop();
}
public void onSubmit(View view) {
// 结束Activity元件
finish();
}
public void clickPlay(View view) {
// 开始播放
mediaPlayer.start();
}
public void clickPause(View view) {
// 暂停播放
mediaPlayer.pause();
}
public void clickStop(View view) {
// 停止播放
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
// 回到开始的位置
mediaPlayer.seekTo(0);
}
}
~~~
开启应用程式设定档“AndroidManifest.xml”,加入这个Activity元件的设定:
~~~
<!-- 播放元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.PlayActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_play"/>
~~~
完成元件的设计后,开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“clickFunction”方法,加入启动这个元件的程式码:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
...
case R.id.record_sound:
// 录音档案名称
final File recordFile = configFileName("R", ".mp3");
// 如果已经有录音档,询问播放或重新录制
if (recordFile.exists()) {
// 询问播放还是重新录制的对话框
AlertDialog.Builder d = new AlertDialog.Builder(this);
d.setTitle(R.string.title_record)
.setCancelable(false);
d.setPositiveButton(R.string.record_play,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 播放
Intent playIntent = new Intent(
ItemActivity.this, PlayActivity.class);
playIntent.putExtra("fileName",
recordFile.getAbsolutePath());
startActivity(playIntent);
}
});
d.setNeutralButton(R.string.record_new,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
goToRecord(recordFile);
}
});
d.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
d.show();
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
~~~
同样在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“onActivityResult”方法,加入设定档案名称的程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
...
case START_RECORD:
// 设定录音档案名称
item.setFileName(fileName);
break;
...
}
}
}
~~~
完成这一章所有的工作了,录音与播放的功能比较建议在实体的装置上测试,试试看加入的功能是不是都可以正确的运作。
## 错误修正:4-1 使用照相机与麦克风
这一章加入的照相与录音功能,把照片与录音档案名称储存在同一个字段。在完成这一章的内容后依照下列的步骤修正错误:
1. 开启“Item.java”,加入下列的字段与方法宣告:
~~~
// 录音档案名称
private String recFileName;
public String getRecFileName() {
return recFileName;
}
public void setRecFileName(String recFileName) {
this.recFileName = recFileName;
}
~~~
2. 同样在“Item.java”,为建构子加入录音档案名称参数:
~~~
// 录音档案名称参数:String recFileName
public Item(long id, long datetime, Colors color, String title,
String content, String fileName, String recFileName,
double latitude, double longitude, long lastModify) {
this.id = id;
this.datetime = datetime;
this.color = color;
this.title = title;
this.content = content;
this.fileName = fileName;
// 录音档案名称
this.recFileName = recFileName;
this.latitude = latitude;
this.longitude = longitude;
this.lastModify = lastModify;
}
~~~
3. 开启“ItemDAO.java”,加入与修改下列的字段宣告:
~~~
...
// 录音档案名称
public static final String RECFILENAME_COLUMN = "recfilename";
...
// 在“FILENAME_COLUMN”下方加入录音档案名称字段
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
...
FILENAME_COLUMN + " TEXT, " +
RECFILENAME_COLUMN + " TEXT, " + // 增加录音档案名称
...";
~~~
4. 同样在“ItemDAO.java”,修改“insert”方法:
~~~
public Item insert(Item item) {
ContentValues cv = new ContentValues();
...
cv.put(FILENAME_COLUMN, item.getFileName());
// 录音档案名称
cv.put(RECFILENAME_COLUMN, item.getRecFileName());
...
}
~~~
5. 同样在“ItemDAO.java”,修改“update”方法:
~~~
public boolean update(Item item) {
ContentValues cv = new ContentValues();
...
cv.put(FILENAME_COLUMN, item.getFileName());
// 录音档案名称
cv.put(RECFILENAME_COLUMN, item.getRecFileName());
...
}
~~~
6. 同样在“ItemDAO.java”,修改“getRecord”方法:
~~~
public Item getRecord(Cursor cursor) {
...
result.setFileName(cursor.getString(5));
// 录音档案名称
result.setRecFileName(cursor.getString(6));
// 后续的编号都要加一
result.setLatitude(cursor.getDouble(7));
...
}
~~~
7. 同样在“ItemDAO.java”,修改“sample”方法:
~~~
public void sample() {
// 增加录音档案名称参数“""”
Item item = new Item(0, new Date().getTime(), Colors.RED, "关于Android Tutorial的事情.", "Hello content", "", "", 0, 0, 0);
Item item2 = new Item(0, new Date().getTime(), Colors.BLUE, "一只非常可爱的小狗狗!", "她的名字叫“大热狗”,又叫\n作“奶嘴”,是一只非常可爱\n的小狗。", "", "", 25.04719, 121.516981, 0);
Item item3 = new Item(0, new Date().getTime(), Colors.GREEN, "一首非常好听的音乐!", "Hello content", "", "", 0, 0, 0);
Item item4 = new Item(0, new Date().getTime(), Colors.ORANGE, "储存在数据库的资料", "Hello content", "", "", 0, 0, 0);
...
}
~~~
8. 开启“MyDBHelper.java”,增加数据库版本编号:
~~~
// 数据库版本,资料结构改变的时候要更改这个数字,通常是加一
public static final int VERSION = 2;
~~~
9. 开启“ItemActivity.java”,增加录音档案名称字段变量:
~~~
// 录音档案名称
private String recFileName;
~~~
10. 同样在“ItemActivity.java”,增加取得录音档案名称的方法:
~~~
private File configRecFileName(String prefix, String extension) {
// 如果记事资料已经有档案名称
if (item.getRecFileName() != null && item.getRecFileName().length() > 0) {
recFileName = item.getRecFileName();
}
// 产生档案名称
else {
recFileName = FileUtil.getUniqueFileName();
}
return new File(FileUtil.getExternalStorageDir(FileUtil.APP_DIR),
prefix + recFileName + extension);
}
~~~
11. 同样在“ItemActivity.java”,修改启动录音元件的方法:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
...
case R.id.record_sound:
// 修改呼叫方法的名称为“configRecFileName”
final File recordFile = configRecFileName("R", ".mp3");
if (recordFile.exists()) {
...
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
~~~
12. 同样在“ItemActivity.java”,找到“onActivityResult”方法,修改设定录音档案名称呼叫的方法:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
...
case START_RECORD:
// 修改设定录音档案名称
item.setRecFileName(recFileName);
break;
...
}
}
}
~~~
完成全部的修改以后执行应用程式,测试同一个记事资料照相与录音的功能。
- 第一堂
- 第一堂(1)西游记里的那只猴子
- 第一堂(2)准备 Android Studio 开发环境
- 第一堂(3)开始设计 Android 应用程式
- 第一堂(4)开发 Android 应用程式的准备工作
- 第二堂
- 第二堂(1)规划与建立应用程式需要的资源
- 第二堂(2)设计应用程式使用者界面
- 第二堂(3)应用程式与使用者的互动
- 第二堂(4)建立与使用 Activity 元件
- 第三堂
- 第三堂(1)为ListView元件建立自定画面
- 第三堂(2)储存与读取应用程式资讯
- 第三堂(3)Android 内建的 SQLite 数据库
- 第四堂
- 第四堂(1)使用照相机与麦克风
- 第四堂(2)设计地图应用程式 - Google Maps Android API v2
- 第四堂(3)读取装置目前的位置 - Google Services Location
- 第五堂
- 第五堂(1)建立广播接收元件 - BroadcastReceiver
- 第五堂(2)系统通知服务 - Notification
- 第五堂(3)设计小工具元件 - AppWidget
- 第六堂
- 第六堂(1)Material Design - Theme与Transition
- 第六堂(2)Material Design - RecylerView
- 第六堂(3)Material Design - Shared Element与自定动画效果