第四堂(1)使用照相机与麦克风

这一章在处理相片与录音档名称的时候有错误,建议在完成这一章的内容以后,再参考错误修正:4-1 使用照相机与麦克风修正错误。

现在行动装置的硬件设备技术已经越来越好了,萤幕的尺寸不断的增加,提供使用者清楚又美观的画面。触控萤幕也几乎是目前行动装置的标准设备,使用触控的方式操作应用程式快速又方便。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

在模拟装置编辑视窗选择“Show Advanced Settings”:

AndroidTutorial5_04_01_02

如果你的电脑没有连接WebCam,在“Front”与“Back”选择“Emulated”。如果电已经连接WebCam,就可以选择“Webcam0”。完成设定后选择“Finish”:

AndroidTutorial5_04_01_03

回到AVD Manager视窗后,选择模拟装置的启动图示:

AndroidTutorial5_04_01_04

模拟装置启动以后,开启“照相”应用程式,就可以看到模拟照相机的画面:

AndroidTutorial5_04_01_05

因为记事元件的画面加入照片以后,在萤幕比较小的装置运作时,画面会超过萤幕的范围,所以需要调整画面的设计。另外也要加入显示照片用的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

画面出现像这样的相机模拟画面,选择照像按钮:

AndroidTutorial5_04_01_07

选择确定按钮:

AndroidTutorial5_04_01_08

记事资料显示拍好的照片,储存记事资料后也会储存照片:

AndroidTutorial5_04_01_09

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

录音的时候,录音按钮会切换为红色的图示,录音的音量变化会在右侧显示:

AndroidTutorial5_04_01_11

设计录音元件的画面配置档,使用的图形资源可以在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

使用者选择播放功能,应用程式启动播放语音备忘元件,这个元件提供播放、暂停与停止三个功能按钮:

AndroidTutorial5_04_01_13

现在设计录音元件的画面配置档,使用的图形资源可以在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, "一只非常可爱的小狗狗!", "她的名字叫“大热狗”,又叫
    作“奶嘴”,是一只非常可爱
    的小狗。", "", "", 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);
}

~~~
  1. 同样在“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;
        ...
    }

}

~~~
  1. 同样在“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;
            ...
        }
    }
}

~~~

完成全部的修改以后执行应用程式,测试同一个记事资料照相与录音的功能。

文章导航