第10章 多媒体应用开发

tb教学录像:1小时36分钟)

随着3G时代的到来,多媒体在手机和平板电脑上广泛应用。Android作为手机和平板电脑的一个操作系统,对于多媒体应用也提供了良好的支持。它不仅支持音频和视频的播放,而且还支持音频录制和摄像头拍照。本章将对Android中的音频、视频以及摄像头拍照等多媒体应用进行详细介绍。

通过阅读本章,您可以:

★ 了解Android支持的音频和视频格式

★ 掌握使用MediaPlayer播放音频的方法

★ 掌握使用SoundPool播放音频的方法

★ 掌握如何使用VideoView播放视频

★ 掌握如何使用MediaPlayer和SurfaceView播放视频

★ 掌握如何控制相机拍照

10.1 播放音频与视频

tb教学录像:光盘\TM\lx\10\播放音频与视频.exe

Android提供了对常用音频和视频格式的支持,它所支持的音频格式有MP3(.mp3)、3GPP(.3gp)、Ogg(.ogg)和WAVE(.ave)等,支持的视频格式有3GPP(.3gp)和MPEG-4(.mp4)等。通过Android API提供的相关方法,在Android中可以实现音频与视频的播放。下面将分别介绍播放音频与视频的不同方法。

10.1.1 使用MediaPlayer播放音频

在Android中,提供了MediaPlayer类来播放音频。使用MediaPlayer类播放音频比较简单,只需要创建该类的对象,并为其指定要播放的音频文件,然后调用该类的start()方法即可,下面进行详细介绍。

  1. 创建MediaPlayer对象,并装载音频文件

创建MediaPlayer对象并装载音频文件,可以使用MediaPayer类提供的静态方法create()来实现,也可以通过其无参构造方法来创建并实例化该类的对象来实现。

MediaPlayer类的静态方法create()常用的语法格式有以下两种。

[√]create(Context context, int resid)

用于从资源ID所对应的资源文件中装载音频,并返回新创建的MediaPlayer对象。例如,要创建装载音频资源(res/raw/d.wav)的MediaPlayer对象,可以使用下面的代码:

  1. MediaPlayer player=MediaPlayer.create(this, R.raw.d);

[√]create(Context context, Uri uri)

用于根据指定的URI来装载音频,并返回新创建的MediaPlayer对象。例如,要创建装载了音频文件(URI地址为http://www.mingribook.com/sound/bg.mp3)的MediaPlayer对象,可以使用下面的代码:

  1. MediaPlayer player=MediaPlayer.create(this, Uri.parse("http://www.mingribook.com/sound/bg.mp3"));

说明:在访问网络中的资源时,要在AndroidManifest.xml文件中授予该程序访问网络的权限,具体的授权代码如下:

  1. <uses-permission android:name="android.permission.INTERNET"/>

在通过MediaPlayer类的静态方法create()来创建MediaPlayer对象时,已经装载了要播放的音频,而使用无参的构造方法来创建MediaPlayer对象时,需要单独指定要装载的资源,这可以使用MediaPlayer类的setDataSource()方法实现。

在使用setDataSource()方法装载音频文件后,实际上MediaPlayer并未真正装载该音频文件,还需要调用MediaPlayer的prepare()方法去真正装载音频文件。使用无参的构造方法来创建MediaPlayer对象并装载指定的音频文件,可以使用下面的代码:

  1. MediaPlayer player=new MediaPlayer();
  2. try {
  3. player.setDataSource("/sdcard/s.wav"); //指定要装载的音频文件
  4. } catch (IllegalArgumentException e1) {
  5. e1.printStackTrace();
  6. } catch (SecurityException e1) {
  7. e1.printStackTrace();
  8. } catch (IllegalStateException e1) {
  9. e1.printStackTrace();
  10. } catch (IOException e1) {
  11. e1.printStackTrace();
  12. }
  13. try {
  14. player.prepare(); //预加载音频
  15. } catch (IllegalStateException e) {
  16. e.printStackTrace();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  1. 开始或恢复播放

在获取到MediaPlayer对象后,就可以使用MediaPlayer类提供的start()方法来开始播放或恢复已经暂停的音频的播放。例如,已经创建了一个名称为player的对象,并且装载了要播放的音频,可以使用下面的代码播放该音频:

  1. player.start(); //开始播放
  1. 停止播放

使用MediaPlayer类提供的stop()方法可以停止正在播放的音频。例如,已经创建了一个名称为player的对象,并且已经开始播放装载的音频,可以使用下面的代码停止播放该音频:

  1. player.stop(); //停止播放
  1. 暂停播放

使用MediaPlayer类提供的pause()方法可以暂停正在播放的音频。例如,已经创建了一个名称为player的对象,并且已经开始播放装载的音频,可以使用下面的代码暂停播放该音频:

  1. player.pause(); //暂停播放

例10.1 在Eclipse中创建Android项目,名称为10.1,实现包括播放、暂停/继续和停止功能的简易音乐播放器。(实例位置:光盘\TM\sl\10\10.1)

(1)将要播放的音频文件上传到SD卡的根目录中,这里要播放的音频文件为ninan.mp3。

(2)修改新建项目的res\layout目录下的布局文件main.xml,在默认添加的线性布局管理器中添加一个水平线性布局管理器,并在其中添加3个按钮控件,分别为“播放”、“暂停/继续”和“停止”按钮,具体代码请参见光盘。

(3)打开默认添加的MainActivity,在该类中,定义所需的成员变量,具体代码如下:

  1. private MediaPlayer player; //MediaPlayer对象
  2. private boolean isPause = false; //是否暂停
  3. private File file; //要播放的音频文件
  4. private TextView hint; //声明显示提示信息的文本框

(4)在onCreate()方法中,首先获取布局管理器中添加的“播放”按钮、“暂停/继续”按钮、“停止”按钮和显示提示信息的文本框,然后获取要播放的文件,最后判断该文件是否存在,如果存在,则创建一个装载该文件的MediaPlayer对象;否则,显示提示信息,并设置“播放”按钮不可用,关键代码如下:

  1. final Button button1 = (Button) findViewById(R.id.button1); //获取“播放”按钮
  2. final Button button2 = (Button) findViewById(R.id.button2); //获取“暂停/继续”按钮
  3. final Button button3 = (Button) findViewById(R.id.button3); //获取“停止”按钮
  4. hint = (TextView) findViewById(R.id.hint); //获取用于显示提示信息的文本框
  5. file = new File("/sdcard/ninan.mp3"); //获取要播放的文件
  6. if (file.exists()) { //如果文件存在
  7. player = MediaPlayer
  8. .create(this, Uri.parse(file.getAbsolutePath())); //创建MediaPlayer对象
  9. } else {
  10. hint.setText("要播放的音频文件不存在!");
  11. button1.setEnabled(false);
  12. return;
  13. }

(5)编写用于播放音乐的play()方法,该方法没有入口参数的返回值。在该方法中,首先调用MediaPlayer对象的reset()方法重置MediaPlayer对象,然后重新为其设置要播放的音频文件,并预加载该音频,最后调用 start()方法开始播放音频,并修改显示提示信息的文本框中的内容,具体代码如下:

  1. private void play() {
  2. try {
  3. player.reset();
  4. player.setDataSource(file.getAbsolutePath()); //重新设置要播放的音频
  5. player.prepare(); //预加载音频
  6. player.start(); //开始播放
  7. hint.setText("正在播放音频...");
  8. } catch (Exception e) {
  9. e.printStackTrace(); //输出异常信息
  10. }
  11. }

(6)为MediaPlayer对象添加完成事件监听器,用于当音乐播放完毕后,重新开始播放音乐,具体代码如下:

  1. player.setOnCompletionListener(new OnCompletionListener() {
  2. @Override
  3. public void onCompletion(MediaPlayer mp) {
  4. play(); //重新开始播放
  5. }
  6. });

(7)为“播放”按钮添加单击事件监听器,在重写的onClick()方法中,首先调用play()方法开始播放音乐,然后对代表是否暂停的标记变量isPause进行设置,最后设置各按钮的可用状态,关键代码如下:

  1. button1.setOnClickListener(new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. play(); //开始播放音乐
  5. if (isPause) {
  6. button2.setText("暂停");
  7. isPause = false; //设置暂停标记变量的值为false
  8. }
  9. button2.setEnabled(true); //“暂停/继续”按钮可用
  10. button3.setEnabled(true); //“停止”按钮可用
  11. button1.setEnabled(false); //“播放”按钮不可用
  12. }
  13. });

(8)为“暂停/继续”按钮添加单击事件监听器,在重写的onClick()方法中,如果MediaPlayer处于播放状态并且标记变量isPause的值为false,则暂停播放音频,并设置相关信息;否则,调用MediaPlayer对象的start()方法继续播放音乐,并设置相关信息,关键代码如下:

  1. button2.setOnClickListener(new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. if (player.isPlaying() && !isPause) {
  5. player.pause(); //暂停播放
  6. isPause = true;
  7. ((Button) v).setText("继续");
  8. hint.setText("暂停播放音频...");
  9. button1.setEnabled(true); //“播放”按钮可用
  10. } else {
  11. player.start(); //继续播放
  12. ((Button) v).setText("暂停");
  13. hint.setText("继续播放音频...");
  14. isPause = false;
  15. button1.setEnabled(false); //“播放”按钮不可用
  16. }
  17. }
  18. });

(9)为“停止”按钮添加单击事件监听器,在重写的onClick()方法中,首先调用MediaPlayer对象的stop()方法停止播放音频,然后设置提示信息及各按钮的可用状态,具体代码如下:

  1. button3.setOnClickListener(new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. player.stop(); //停止播放
  5. hint.setText("停止播放音频...");
  6. button2.setEnabled(false); //“暂停/继续”按钮不可用
  7. button3.setEnabled(false); //“停止”按钮不可用
  8. button1.setEnabled(true); //“播放”按钮可用
  9. }
  10. });

(10)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源,具体代码如下:

  1. @Override
  2. protected void onDestroy() {
  3. if(player.isPlaying()){
  4. player.stop(); //停止音频的播放
  5. }
  6. player.release(); //释放资源
  7. super.onDestroy();
  8. }

运行本实例,将显示一个简易音乐播放器,单击“播放”按钮,将开始播放音乐,同时“播放”按钮变为不可用状态,而“暂停”和“停止”按钮变为可用状态,如图10.1所示;单击“暂停”按钮,将暂停音乐的播放,同时“播放”按钮变为可用;单击“继续”按钮,将继续音乐的播放,同时“继续”按钮变为“暂停”按钮;单击“停止”按钮,将停止音乐的播放,同时“暂停/继续”和“停止”按钮将变为不可用,“播放”按钮可用。

342-1 图10.1 简易音乐播放器

10.1.2 使用SoundPool播放音频

由于MediaPlayer占用资源较多,且不支持同时播放多个音频,所以Android还提供了另一个播放音频的类——SoundPool。SoundPool即音频池,可以同时播放多个短小的音频,而且占用的资源较少。SoundPool适合在应用程序中播放按键音或者消息提示音等,在游戏中播放密集而短暂的声音,如多个飞机的爆炸声等。使用SoundPool播放音频,首先需要创建SoundPool对象,然后加载所要播放的音频,最后调用play()方法播放音频,下面进行详细介绍。

  1. 创建SoundPool对象

SoundPool类提供了一个构造方法,用来创建SoundPool对象,该构造方法的语法格式如下:

  1. SoundPool (int maxStreams, int streamType, int srcQuality)

其中,参数maxStreams用于指定可以容纳多少个音频;参数streamType用于指定声音类型,可以通过AudioManager类提供的常量进行指定,通常使用STREAM_MUSIC;参数srcQuality用于指定音频的品质,默认值为0。

例如,创建一个可以容纳10个音频的SoundPool对象,可以使用下面的代码:

  1. SoundPool soundpool = new SoundPool(10,
  2. AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳10个音频流
  1. 加载所要播放的音频

创建SoundPool对象后,可以调用load()方法来加载要播放的音频。load()方法的语法格式有以下4种。

[√]public int load (Context context, int resId, int priority):用于通过指定的资源ID来加载音频。

[√]public int load (String path, int priority):用于通过音频文件的路径来加载音频。

[√]public int load (AssetFileDescriptor afd, int priority):用于从AssetFileDescriptor所对应的文件中加载音频。

[√]public int load (FileDescriptor fd, long offset, long length, int priority):用于加载FileDescriptor对象中从offset开始,长度为length的音频。

例如,要通过资源ID来加载音频文件ding.wav,可以使用下面的代码:

  1. soundpool.load(this, R.raw.ding, 1);

说明:为了更好地管理所加载的每个音频,一般使用HashMap<Integer, Integer>对象来管理这些音频。这时可以先创建一个HashMap<Integer, Integer>对象,然后应用该对象的put()方法将加载的音频保存到该对象中。例如,创建一个HashMap<Integer, Integer>对象,并应用put()方法添加一个音频,可以使用下面的代码:

  1. HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象
  2. soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
  1. 播放音频

调用SoundPool对象的play()方法可播放指定的音频。play()方法的语法格式如下:

  1. play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)

play()方法各参数的说明如表10.1所示。

表10.1 play()方法的参数说明

参 数 描 述
soundID 用于指定要播放的音频,该音频为通过load()方法返回的音频
leftVolume 用于指定左声道的音量,取值范例为0.0~1.0
rightVolume 用于指定右声道的音量,取值范例为0.0~1.0
priority 用于指定播放音频的优先级,数值越大,优先级越高
loop 用于指定循环次数,0为不循环,-1为循环
rate 用于指定速率,正常为1,最低为0.5,最高为2

例如,要播放音频资源中保存的音频文件notify.wav,可以使用下面的代码:

  1. soundpool.play(soundpool.load(MainActivity.this, R.raw.notify, 1), 1, 1, 0, 0, 1); //播放指定的音频

例10.2 在Eclipse中创建Android项目,名称为10.2,实现通过SoundPool播放音频。(实例位置:光盘\TM\sl\10\10.2)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加4个按钮组件,分别为“风铃声”按钮、“布谷鸟叫声”按钮、“门铃声”按钮和“电话声”按钮,具体代码请参见光盘。

(2)打开默认添加的MainActivity,在该类中,创建两个成员变量,具体代码如下:

  1. private SoundPool soundpool; //声明一个SoundPool对象
  2. private HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象

(3)在onCreate()方法中,首先获取布局管理器中添加的“风铃声”按钮、“布谷鸟叫声”按钮、“门铃声”按钮和“电话声”按钮,然后实例化SoundPool对象,再将要播放的全部音频流保存到HashMap对象中,具体代码如下:

  1. Button chimes = (Button) findViewById(R.id.button1); //获取“风铃声”按钮
  2. Button enter = (Button) findViewById(R.id.button2); //获取“布谷鸟叫声”按钮
  3. Button notify = (Button) findViewById(R.id.button3); //获取“门铃声”按钮
  4. Button ringout = (Button) findViewById(R.id.button4); //获取“电话声”按钮
  5. soundpool = new SoundPool(5,
  6. AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳5个音频流
  7. //将要播放的音频流保存到HashMap对象中
  8. soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
  9. soundmap.put(2, soundpool.load(this, R.raw.enter, 1));
  10. soundmap.put(3, soundpool.load(this, R.raw.notify, 1));
  11. soundmap.put(4, soundpool.load(this, R.raw.ringout, 1));
  12. soundmap.put(5, soundpool.load(this, R.raw.ding, 1));

(4)分别为“风铃声”按钮、“布谷鸟叫声”按钮、“门铃声”按钮和“电话声”按钮添加单击事件监听器,在重写的onClick()方法中播放指定的音频,具体代码如下:

  1. chimes.setOnClickListener(new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. soundpool.play(soundmap.get(1), 1, 1, 0, 0, 1); //播放指定的音频
  5. }
  6. });
  7. enter.setOnClickListener(new OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. soundpool.play(soundmap.get(2), 1, 1, 0, 0, 1); //播放指定的音频
  11. }
  12. });
  13. notify.setOnClickListener(new OnClickListener() {
  14. @Override
  15. public void onClick(View v) {
  16. soundpool.play(soundmap.get(3), 1, 1, 0, 0, 1); //播放指定的音频
  17. }
  18. });
  19. ringout.setOnClickListener(new OnClickListener() {
  20. @Override
  21. public void onClick(View v) {
  22. soundpool.play(soundmap.get(4), 1, 1, 0, 0, 1); //播放指定的音频
  23. }
  24. });

(5)重写键盘按键被按下的onKeyDown()方法,用于实现播放按键音的功能,具体代码如下:

  1. @Override
  2. public boolean onKeyDown(int keyCode, KeyEvent event) {
  3. soundpool.play(soundmap.get(5), 1, 1, 0, 0, 1); //播放按键音
  4. return true;
  5. }

运行本实例,将显示如图10.2所示的运行结果。单击“风铃声”、“布谷鸟叫声”等按钮,将播放相应的音乐;按下键盘上的按键,将播放一个按键音。

345-1 图10.2 应用SoundPool播放音频

10.1.3 使用VideoView播放视频

在Android中,提供了VideoView组件用于播放视频文件。要想使用VideoView组件播放视频,首先需要在布局文件中创建该组件,然后在Activity中获取该组件,并应用其setVideoPath()方法或setVideoURI()方法加载要播放的视频,最后调用start()方法来播放视频。另外,VideoView组件还提供了stop()和pause()方法,用于停止或暂停视频的播放。

在布局文件中创建VideoView组件的基本语法格式如下:

  1. <VideoView
  2. 属性列表>
  3. </VideoView>

VideoView组件支持的XML属性如表10.2所示。

表10.2 VideoView组件支持的XML属性

XML属性 描 述
android:id 用于设置组件的ID
android:background 用于设置背景,可以设置背景图片,也可以设置背景颜色
android:layout_gravity 用于设置对齐方式
android:layout_width 用于设置宽度
android:layout_height 用于设置高度

 

在Android中还提供了一个可以与VideoView组件结合使用的MediaController组件。MediaController组件用于通过图形控制界面来控制视频的播放。

下面通过一个具体的实例来说明如何使用VideoView和MediaController来播放视频。

例10.3 在Eclipse中创建Android项目,名称为10.3,实现通过VideoView和MediaController播放视频。(实例位置:光盘\TM\sl\10\10.3)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加一个VideoView组件用于播放视频文件,关键代码如下:

  1. <VideoView
  2. android:id="@+id/video"
  3. android:background="@drawable/mpbackground"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. android:layout_gravity="center" />

(2)打开默认添加的MainActivity,在该类中,声明一个VideoView对象,具体代码如下:

  1. private VideoView video; //声明VideoView对象

(3)在onCreate()方法中,首先获取布局管理器中添加的VideoView组件,并创建一个要播放视频所对应的File对象,然后创建一个MediaController对象,用于控制视频的播放,最后判断要播放的视频文件是否存在,如果存在,使用VideoView播放该视频,否则弹出消息提示框显示提示信息,具体代码如下:

  1. video=(VideoView) findViewById(R.id.video); //获取VideoView组件
  2. File file=new File("/sdcard/bell.mp4"); //获取SD卡上要播放的文件
  3. MediaController mc=new MediaController(MainActivity.this);
  4. if(file.exists()){ //判断要播放的视频文件是否存在
  5. video.setVideoPath(file.getAbsolutePath()); //指定要播放的视频
  6. video.setMediaController(mc); //设置VideoView与MediaController相关联
  7. video.requestFocus(); //让VideoView获得焦点
  8. try {
  9. video.start(); //开始播放视频
  10. } catch (Exception e) {
  11. e.printStackTrace(); //输出异常信息
  12. }
  13. //为VideoView添加完成事件监听器
  14. video.setOnCompletionListener(new OnCompletionListener() {
  15. @Override
  16. public void onCompletion(MediaPlayer mp) {
  17. //弹出消息提示框显示播放完毕
  18. Toast.makeText(MainActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
  19. }
  20. });
  21. }else{
  22. //弹出消息提示框提示文件不存在
  23. Toast.makeText(this, "要播放的视频文件不存在", Toast.LENGTH_SHORT).show();
  24. }

运行本实例,将显示如图10.3所示的运行结果。

347-1 图10.3 使用VideoView和MediaController组件播放视频

说明:由于本实例是在模拟器上运行的,所以并没有显示视频画面,而在屏幕中间显示的图片是为VideoView设置的背景图片。如果将该程序发布到真机上运行,就可以看到视频画面了。

10.1.4 使用MediaPlayer和SurfaceView播放视频

使用MediaPlayer除可以播放音频外,还可以播放视频文件,只不过使用MediaPlayer播放视频时,没有提供图像输出界面。这时,可以使用SurfaceView组件来显示视频图像。使用MediaPlayer和SurfaceView来播放视频,大致可以分为以下4个步骤。

(1)定义SurfaceView组件。定义SurfaceView组件可以在布局管理器中实现,也可以直接在Java代码中创建,不过推荐在布局管理器中定义SurfaceView组件,其基本语法格式如下:

  1. <SurfaceView
  2. android:id="@+id/ID号"
  3. android:background="背景"
  4. android:keepScreenOn="true|false"
  5. android:layout_width="宽度"
  6. android:layout_height="高度"/>

在上面的语法中,android:keepScreenOn属性用于指定在播放视频时,是否打开屏幕。

例如,在布局管理器中,添加一个ID号为surfaceView1、设置了背景的SurfaceView组件,可以使用下面的代码:

  1. <SurfaceView
  2. android:id="@+id/surfaceView1"
  3. android:background="@drawable/bg"
  4. android:keepScreenOn="true"
  5. android:layout_width="576px"
  6. android:layout_height="432px"/>

(2)创建MediaPlayer对象,并为其加载要播放的视频。与播放音频时创建MediaPlayer对象一样,也可以使用MediaPlayer类的静态方法create()和无参的构造方法两种方式创建MediaPlayer对象,具体方法请参见10.1.1节。

(3)将所播放的视频画面输出到SurfaceView。使用MediaPlayer对象的setDisplay()方法,可以将所播放的视频画面输出到SurfaceView。setDisplay()方法的语法格式如下:

  1. setDisplay(SurfaceHolder sh)

参数sh用于指定SurfaceHolder对象,可以通过SurfaceView对象的getHolder()方法获得。例如,为MediaPlayer对象指定输出视频画面的SurfaceView,可以使用下面的代码:

  1. mediaplayer.setDisplay(surfaceview.getHolder()); //设置将视频画面输出到SurfaceView

(4)调用MediaPlayer对象的相应方法控制视频的播放。使用MediaPlayer对象提供的play()、pause()和stop()方法,可以控制视频的播放、暂停和停止。

下面通过一个具体的实例来说明如何使用MediaPlayer和SurfaceView来播放视频。

例10.4 在Eclipse中创建Android项目,名称为10.4,实现通过MeidaPlayer和SurfaceView播放视频。(实例位置:光盘\TM\sl\10\10.4)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加一个SurfaceView组件,用于显示视频图像;添加一个水平线性布局管理器,并在该水平线性布局管理器中添加3个按钮,分别为“播放”按钮、“暂停/继续”按钮和“停止”按钮,关键代码如下:

  1. <SurfaceView
  2. android:id="@+id/surfaceView1"
  3. android:background="@drawable/bg"
  4. android:keepScreenOn="true"
  5. android:layout_width="576px"
  6. android:layout_height="432px"/>

(2)打开默认添加的MainActivity,在该类中,声明一个MediaPlayer对象和一个SurfaceView对象,具体代码如下:

  1. private MediaPlayer mp; //声明MediaPlayer对象
  2. private SurfaceView sv; //声明SurfaceView对象

(3)在onCreate()方法中,首先实例化MediaPlayer对象,然后获取布局管理器中添加的SurfaceView组件,再分别获取“播放”按钮、“暂停/继续”按钮和“停止”按钮,具体代码如下:

  1. mp=new MediaPlayer();      //实例化MediaPlayer对象
  2. sv=(SurfaceView)findViewById(R.id.surfaceView1); //获取布局管理器中添加的SurfaceView组件
  3. Button play=(Button)findViewById(R.id.play); //获取“播放”按钮
  4. final Button pause=(Button)findViewById(R.id.pause); //获取“暂停/继续”按钮
  5. Button stop=(Button)findViewById(R.id.stop); //获取“停止”按钮

(4)分别为“播放”按钮、“暂停/继续”按钮和“停止”按钮添加单击事件监听器,并在重写的onClick()方法中,实现播放视频、暂停/继续播放视频和停止播放视频等功能,具体代码如下:

  1. //为“播放”按钮添加单击事件监听器
  2. play.setOnClickListener(new OnClickListener() {
  3. @Override
  4. public void onClick(View v) {
  5. mp.reset(); //重置MediaPlayer对象
  6. try {
  7. mp.setDataSource("/sdcard/ccc.mp4"); //设置要播放的视频
  8. mp.setDisplay(sv.getHolder()); //设置将视频画面输出到SurfaceView
  9. mp.prepare(); //预加载视频
  10. mp.start(); //开始播放
  11. sv.setBackgroundResource(R.drawable.bg_playing); //改变SurfaceView的背景图片
  12. pause.setText("暂停");
  13. pause.setEnabled(true); //设置“暂停”按钮可用
  14. } catch (IllegalArgumentException e) {
  15. e.printStackTrace();
  16. } catch (SecurityException e) {
  17. e.printStackTrace();
  18. } catch (IllegalStateException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. });
  25. //为“停止”按钮添加单击事件监听器
  26. stop.setOnClickListener(new OnClickListener() {
  27. @Override
  28. public void onClick(View v) {
  29. if(mp.isPlaying()){
  30. mp.stop(); //停止播放
  31. sv.setBackgroundResource(R.drawable.bg_finish); //改变SurfaceView的背景图片
  32. pause.setEnabled(false); //设置“暂停”按钮不可用
  33. }
  34. }
  35. });
  36. //为“暂停”按钮添加单击事件监听器
  37. pause.setOnClickListener(new OnClickListener() {
  38. @Override
  39. public void onClick(View v) {
  40. if(mp.isPlaying()){
  41. mp.pause(); //暂停视频的播放
  42. ((Button)v).setText("继续");
  43. }else{
  44. mp.start(); //继续视频的播放
  45. ((Button)v).setText("暂停");
  46. }
  47. }
  48. });

(5)为MediaPlayer对象添加完成事件监听器,在重写的onCompletion()方法中改变SurfaceView的背景图片并弹出消息提示框显示视频已经播放完毕,具体代码如下:

  1. mp.setOnCompletionListener(new OnCompletionListener() {
  2. @Override
  3. public void onCompletion(MediaPlayer mp) {
  4. sv.setBackgroundResource(R.drawable.bg_finish); //改变SurfaceView的背景图片
  5. Toast.makeText(MainActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
  6. }
  7. });

(6)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源,具体代码如下:

  1. @Override
  2. protected void onDestroy() {
  3. if(mp.isPlaying()){
  4. mp.stop(); //停止播放视频
  5. }
  6. mp.release(); //释放资源
  7. super.onDestroy();
  8. }

运行本实例,单击“播放”按钮,将开始播放视频,并且“暂停”按钮变为可用,如图10.4所示;单击“暂停”按钮,将暂停视频的播放,同时该按钮变为“继续”按钮;单击“停止”按钮,将停止正在播放的视频。

351-1 图10.4 使用MediaPlayer和SurfaceView播放视频

10.1.5 范例1:播放SD卡上的全部音频文件

例10.5 在Eclipse中创建Android项目,名称为10.5,实现播放SD卡上的全部音频文件。(实例位置:光盘\TM\sl\10\10.5)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加一个ListView组件,用于显示获取到的音频列表;添加一个水平线性布局管理器,并在该水平线性布局管理器中添加5个按钮,分别为“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮,其中“暂停/继续”按钮默认为不可用,关键代码如下:

  1. <ListView
  2. android:id="@+id/list"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:layout_weight="1"
  6. android:drawSelectorOnTop="false"/>

(2)打开默认添加的MainActivity,在该类中,声明程序中所需的成员变量,具体代码如下:

  1. private MediaPlayer mediaPlayer;    //声明MediaPlayer对象
  2. private List<String> audioList = new ArrayList<String>(); //要播放的音频列表
  3. private int currentItem = 0;    //当前播放歌曲的索引
  4. private Button pause;      //声明一个“暂停”按钮对象

(3)在onCreate()方法中,首先实例化MediaPlayer对象,然后获取布局管理器中添加的“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮,再调用audioList()方法在ListView组件上显示全部音频,具体代码如下:

  1. mediaPlayer = new MediaPlayer(); //实例化一个MediaPlayer对象
  2. Button play = (Button) findViewById(R.id.play); //获取“播放”按钮
  3. Button stop = (Button) findViewById(R.id.stop); //获取“停止”按钮
  4. pause = (Button) findViewById(R.id.pause); //获取“暂停/继续”按钮
  5. Button pre = (Button) findViewById(R.id.pre); //获取“上一首”按钮
  6. Button next = (Button) findViewById(R.id.next); //获取“下一首”按钮
  7. audioList(); //使用ListView组件显示SD卡上的全部音频文件

(4)编写audioList()方法,用于使用ListView组件显示SD卡上的全部音频文件。在该方法中,首先调用getFiles()方法获取SD卡上的全部音频文件,然后创建一个适配器,并获取布局管理器中添加的ListView组件,再将适配器与ListView关联,最后为ListView添加列表项单击事件监听器,用于当用户单击列表项时播放音乐。audioList()方法的具体代码如下:

  1. private void audioList() {
  2. getFiles("/sdcard/"); //获取SD卡上的全部音频文件
  3. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
  4. android.R.layout.simple_list_item_1, audioList); //创建一个适配器
  5. ListView listview = (ListView) findViewById(R.id.list); //获取布局管理器中添加的ListView组件
  6. listview.setAdapter(adapter); //将适配器与ListView关联
  7. //当单击列表项时播放音乐
  8. listview.setOnItemClickListener(new OnItemClickListener() {
  9. @Override
  10. public void onItemClick(AdapterView<?> listView, View view,int position, long id) {
  11. currentItem = position; //将当前列表项的索引值赋值给currentItem
  12. playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
  13. }
  14. });
  15. }

(5)定义一个保存合法的音频文件格式的字符串数组,并编写根据文件路径判断文件是否为音频文件的方法,具体代码如下:

  1. private static String[] imageFormatSet = new String[] { "mp3", "wav", "3gp" }; //合法的音频文件格式
  2. //判断是否为音频文件
  3. private static boolean isAudioFile(String path) {
  4. for (String format : imageFormatSet) { //遍历数组
  5. if (path.contains(format)) { //判断是否为合法的音频文件
  6. return true;
  7. }
  8. }
  9. return false;
  10. }

(6)编写getFiles()方法,用于通过递归调用的方式获取SD卡上的全部音频文件,具体代码如下:

  1. private void getFiles(String url) {
  2. File files = new File(url); //创建文件对象
  3. File[] file = files.listFiles();
  4. try {
  5. for (File f : file) { //通过for循环遍历获取到的文件数组
  6. if (f.isDirectory()) { //如果是目录,也就是文件夹
  7. getFiles(f.getAbsolutePath()); //递归调用
  8. } else {
  9. if (isAudioFile(f.getPath())) { //如果是音频文件
  10. audioList.add(f.getPath()); //将文件的路径添加到List集合中
  11. }
  12. }
  13. }
  14. } catch (Exception e) {
  15. e.printStackTrace(); //输出异常信息
  16. }
  17. }

(7)编写用于播放音乐的方法playMusic(),在该方法中,首先判断是否正在播放音乐,如果正在播放音乐,先停止播放,然后重置MediaPlayer,并指定要播放的音频文件,再预加载该音频文件,最后播放音频,并设置“暂停”按钮的显示文字及可用状态。playMusic()方法的具体代码如下:

  1. void playMusic(String path) {
  2. try {
  3. if (mediaPlayer.isPlaying()) {
  4. mediaPlayer.stop(); //停止当前音频的播放
  5. }
  6. mediaPlayer.reset(); //重置MediaPlayer
  7. mediaPlayer.setDataSource(path); //指定要播放的音频文件
  8. mediaPlayer.prepare(); //预加载音频文件
  9. mediaPlayer.start(); //播放音频
  10. pause.setText("暂停");
  11. pause.setEnabled(true); //设置“暂停”按钮可用
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }

(8)编写实现“下一首”功能的方法nextMusic(),在该方法中,首先计算要播放音频的索引,然后调用playMusic()播放音乐。nextMusic()方法的具体代码如下:

  1. void nextMusic() {
  2. if (++currentItem >= audioList.size()) {//当对currentItem进行+1操作后,如果其值大于等于音频文件的总数
  3. currentItem = 0;
  4. }
  5. playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
  6. }

(9)编写实现“上一首”功能的方法preMusic(),在该方法中,首先计算要播放音频的索引,然后调用playMusic()播放音乐。preMusic()方法的具体代码如下:

  1. void preMusic() {
  2. if (--currentItem >= 0) { //当对currentItem进行-1操作后,如果其值大于等于0
  3. if (currentItem >= audioList.size()) { //如果currentItem的值大于等于音频文件的总数
  4. currentItem = 0;
  5. }
  6. } else {
  7. currentItem = audioList.size() - 1; //currentItem的值设置为音频文件总数-1
  8. }
  9. playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
  10. }

(10)为MediaPlayer对象添加完成事件监听器,在重写的onCompletion()方法中调用nextMusic()方法播放下一首音乐,具体代码如下:

  1. mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
  2. @Override
  3. public void onCompletion(MediaPlayer mp) {
  4. nextMusic(); //播放下一首
  5. }
  6. });

(11)分别为“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮添加单击事件监听器,并在重写的onClick()方法中,实现播放上一首、播放、暂停/继续播放、停止播放和播放下一首音频等功能,具体代码如下:

  1. //为“上一首”按钮添加单击事件监听器
  2. pre.setOnClickListener(new OnClickListener() {
  3. @Override
  4. public void onClick(View v) {
  5. preMusic(); //播放上一首
  6. }
  7. });
  8. //为“播放”按钮添加单击事件监听器
  9. play.setOnClickListener(new OnClickListener() {
  10. @Override
  11. public void onClick(View v) {
  12. playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
  13. }
  14. });
  15. //为“暂停”按钮添加单击事件监听器
  16. pause.setOnClickListener(new OnClickListener() {
  17. @Override
  18. public void onClick(View v) {
  19. if (mediaPlayer.isPlaying()) {
  20. mediaPlayer.pause(); //暂停音频的播放
  21. ((Button) v).setText("继续");
  22. } else {
  23. mediaPlayer.start(); //继续播放
  24. ((Button) v).setText("暂停");
  25. }
  26. }
  27. });
  28. //为“停止”按钮添加单击事件监听器
  29. stop.setOnClickListener(new OnClickListener() {
  30. @Override
  31. public void onClick(View v) {
  32. if (mediaPlayer.isPlaying()) {
  33. mediaPlayer.stop(); //停止播放音频
  34. }
  35. pause.setEnabled(false); //设置“暂停”按钮不可用
  36. }
  37. });
  38. //为“下一首”按钮添加单击事件监听器
  39. next.setOnClickListener(new OnClickListener() {
  40. @Override
  41. public void onClick(View v) {
  42. nextMusic(); //播放下一首
  43. }
  44. });

(12)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的音频,并释放MediaPlayer所占用的资源,具体代码如下:

  1. @Override
  2. protected void onDestroy() {
  3. if (mediaPlayer.isPlaying()) {
  4. mediaPlayer.stop(); //停止音乐的播放
  5. }
  6. mediaPlayer.release(); //释放资源
  7. super.onDestroy();
  8. }

运行本实例,在屏幕中将显示获取到的音频列表,单击各列表项,可以播放当前列表项所指定的音乐;单击“播放”按钮,将开始播放音乐,并且“暂停”按钮变为可用,如图10.5所示;单击“暂停”按钮,将暂停音乐的播放,同时该按钮变为“继续”按钮;单击“停止”按钮,将停止播放音乐;单击“上一首”按钮,将播放上一首音乐;单击“下一首”按钮,将播放下一首音乐。

355-1 图10.5 播放SD卡上的全部音频文件

10.1.6 范例2:带音量控制的音乐播放器

例10.6 在Eclipse中创建Android项目,名称为10.6,实现带音量控制功能的音乐播放器。(实例位置:光盘\TM\sl\10\10.6)

说明:本实例是在10.1.1节中的例10.1的基础上开发的,所以与其相同的部分这里就不再赘述。

(1)将要播放的音频文件上传到SD卡的根目录中,这里要播放的音频文件为ninan.mp3。如果已经将ninan.mp3文件上传到SD卡的根目录中,就不需要再重新上传了。

(2)打开res\layout目录下的布局文件main.xml,在水平线性布局管理器的结尾处添加一个TextView组件和一个拖动条组件,分别用于显示当前音量值和调整音量的拖动条,关键代码如下:

  1. <TextView
  2. android:id="@+id/volume"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:padding="10px"
  6. android:text="当前音量:" />
  7. <SeekBar
  8. android:id="@+id/seekBar1"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:layout_weight="1" />

说明:这里的拖动条不用指定最大值和当前值,在后面的Java代码中,我们会为其指定,这样可以让拖动条的值与音量相关联。

(3)在onCreate()方法中,添加使用拖动条控制音量大小的代码。

  1. //获取音频管理器类的对象
  2. final AudioManager am = (AudioManager) MainActivity.this.getSystemService(Context.AUDIO_SERVICE);
  3. //设置当前调整音量只是针对媒体音乐
  4. MainActivity.this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
  5. SeekBar seekbar = (SeekBar) findViewById(R.id.seekBar1); //获取拖动条
  6. seekbar.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); //设置拖动条的最大值
  7. int progress=am.getStreamVolume(AudioManager.STREAM_MUSIC); //获取当前的音量
  8. seekbar.setProgress(progress); //设置拖动条的默认值为当前音量
  9. final TextView tv=(TextView)findViewById(R.id.volume); //获取显示当前音量的TextView组件
  10. tv.setText("当前音量:"+progress); //显示当前音量
  11. //为拖动条组件添加OnSeekBarChangeListener监听器
  12. seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
  13. @Override
  14. public void onStopTrackingTouch(SeekBar seekBar) {}
  15. @Override
  16. public void onStartTrackingTouch(SeekBar seekBar) {}
  17. @Override
  18. public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
  19. tv.setText("当前音量:"+progress); //显示改变后的音量
  20. am.setStreamVolume(AudioManager.STREAM_MUSIC,
  21. progress, AudioManager.FLAG_PLAY_SOUND); //设置改变后的音量
  22. }
  23. });

说明:在上面的代码中,首先获取音频管理器类的对象,并设置当前调整音量只是针对媒体音乐进行,然后获取拖动条,并设置其最大值获取其当前值,再获取显示当前音量的TextView组件,并设置其显示内容为当前音量,最后为拖动条组件添加OnSeekBarChangeListener监听器,在重写的onProgressChanged()方法中,显示改变后的音量,并将改变后音量设置到音频管理器上,用来改变音量的大小。

运行本实例,将显示一个带音量控制的音乐播放器,单击“播放”按钮、“暂停/继续”按钮和“停止”按钮,可以播放音乐、暂停/继续和停止音乐的播放;拖动音量控制拖动条上的滑块,可以调整音量的大小,并及时显示当前音量,如图10.6所示。

357-1 图10.6 带音量控制的音乐播放器

10.2 控制相机拍照

tb教学录像:光盘\TM\lx\10\控制相机拍照.exe

现在的手机和平板电脑一般都会提供相机功能,而且相机功能的应用越来越广泛。在Android中提供了专门用于处理相机相关事件的类,即android.hardware包中的Camera类。Camera类没有构造方法,可以通过其提供的open()方法打开相机。打开相机后,可以通过Camera.Parameters类处理相机的拍照参数。拍照参数设置完成后,可以调用startPreview()方法预览拍照画面,也可以调用takePicture()方法进行拍照。结束程序时,可以调用Camera类的stopPreview()方法结束预览,并调用release()方法释放相机资源。Camera类常用的方法如表10.3所示。

表10.3 Camera类常用的方法

方 法 描 述
getParameters() 用于获取相机参数
Camera.open() 用于打开相机
release() 用于释放相机资源
setParameters(Camera.Parameters params) 用于设置相机的拍照参数
setPreviewDisplay(SurfaceHolder holder) 用于为相机指定一个用来显示相机预览画面的SurfaceView
startPreview() 用于开始预览画面
takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) 用于进行拍照
stopPreview() 用于停止预览

下面通过一个具体的实例来说明控制相机拍照的具体过程。

例10.7 在Eclipse中创建Android项目,名称为10.7,实现控制相机拍照功能。(实例位置:光盘\TM\sl\10\10.7)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,并将默认添加的垂直线性布局管理器修改为水平线性布局管理器,然后在该布局管理器中添加一个垂直线性布局管理器(用于放置控制按钮)和一个SurfaceView组件(用于显示相机预览画面),再在这个垂直线性布局管理器中添加两个按钮:一个是“预览”按钮,id为preview;另一个是“拍照”按钮,id为takephoto。关键代码如下:

  1. <SurfaceView
  2. android:id="@+id/surfaceView1"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" />

(2)打开默认添加的MainActivity,在该类中,声明程序中所需的成员变量,具体代码如下:

  1. private Camera camera; //相机对象
  2. private boolean isPreview = false; //是否为预览模式

(3)设置程序为全屏运行。这里需要将下面的代码添加到onCreate()方法中默认添加的setContentView (R.layout.main);语句之前,否则不能应用全屏的效果。

  1. requestWindowFeature(Window.FEATURE_NO_TITLE); //设置全屏显示

(4)在onCreate()方法中,首先判断是否安装SD卡,因为拍摄的图片需要保存到SD卡上,然后获取用于显示相机预览画面的SurfaceView组件,最后通过SurfaceView对象获取SurfaceHolder对象,并设置该SurfaceHolder不维护缓冲,具体代码如下:

  1. /****************** 判断是否安装SD卡 *********************************/
  2. if (!android.os.Environment.getExternalStorageState().equals(
  3. android.os.Environment.MEDIA_MOUNTED)) {
  4. Toast.makeText(this, "请安装SD卡!", Toast.LENGTH_SHORT).show(); //弹出消息提示框显示提示信息
  5. }
  6. /******************************************************************/
  7. SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView1); //获取SurfaceView组件,用于显示相机预览
  8. final SurfaceHolder sh = sv.getHolder(); //获取SurfaceHolder对象
  9. sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //设置该SurfaceHolder不维护缓冲

(5)获取布局管理器中添加的“预览”按钮,并为其添加单击事件监听器,在重写的onClick()方法中,首先判断相机是否为预览模式,如果不是,则打开相机,然后为相机设置显示预览画面的SurfaceView,并设置相机参数,最后开始预览并设置自动对焦,具体代码如下:

  1. Button preview = (Button) findViewById(R.id.preview); //获取“预览”按钮
  2. preview.setOnClickListener(new View.OnClickListener() {
  3. @Override
  4. public void onClick(View v) {
  5. //如果相机为非预览模式,则打开相机
  6. if (!isPreview) {
  7. camera=Camera.open(); //打开相机
  8. }
  9. try {
  10. camera.setPreviewDisplay(sh); //设置用于显示预览的SurfaceView
  11. Camera.Parameters parameters = camera.getParameters(); //获取相机参数
  12. parameters.setPictureSize(640, 480); //设置预览画面的尺寸
  13. parameters.setPictureFormat(PixelFormat.JPEG); //指定图片为JPEG格式
  14. parameters.set("jpeg-quality", 80); //设置图片的质量
  15. parameters.setPictureSize(640, 480); //设置拍摄图片的尺寸
  16. camera.setParameters(parameters); //重新设置相机参数
  17. camera.startPreview(); //开始预览
  18. camera.autoFocus(null); //设置自动对焦
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. });

(6)获取布局管理器中添加的“拍照”按钮,并为其设置单击事件监听器,在重写的onClick()方法中,如果相机对象不为空,则调用takePicture()方法进行拍照,具体代码如下:

  1. Button takePhoto = (Button) findViewById(R.id.takephoto); //获取“拍照”按钮
  2. takePhoto.setOnClickListener(new View.OnClickListener() {
  3. @Override
  4. public void onClick(View v) {
  5. if(camera!=null){
  6. camera.takePicture(null, null, jpeg); //进行拍照
  7. }
  8. }
  9. });

(7)实现拍照的回调接口,在重写的onPictureTaken()方法中,首先根据拍照所得的数据创建位图,然后实现一个带“保存”和“取消”按钮的对话框,用于保存所拍图片,具体代码如下:

  1. final PictureCallback jpeg = new PictureCallback() {
  2. @Override
  3. public void onPictureTaken(byte[] data, Camera camera) {
  4. //根据拍照所得的数据创建位图
  5. final Bitmap bm = BitmapFactory.decodeByteArray(data, 0,data.length);
  6. //加载layout/save.xml文件对应的布局资源
  7. View saveView = getLayoutInflater().inflate(R.layout.save, null);
  8. final EditText photoName = (EditText) saveView.findViewById(R.id.phone_name);
  9. //获取对话框上的ImageView组件
  10. ImageView show = (ImageView) saveView.findViewById(R.id.show);
  11. show.setImageBitmap(bm); //显示刚刚拍得的照片
  12. camera.stopPreview(); //停止预览
  13. isPreview = false;
  14. //使用对话框显示saveDialog组件
  15. new AlertDialog.Builder(MainActivity.this).setView(saveView)
  16. .setPositiveButton("保存", new DialogInterface.OnClickListener() {
  17. @Override
  18. public void onClick(DialogInterface dialog, int which) {
  19. File file = new File("/sdcard/pictures/" + photoName
  20. .getText().toString() + ".jpg"); //创建文件对象
  21. try {
  22. file.createNewFile(); //创建一个新文件
  23. //创建一个文件输出流对象
  24. FileOutputStream fileOS = new FileOutputStream(file);
  25. //将图片内容压缩为JPEG格式输出到输出流对象中
  26. bm.compress(Bitmap.CompressFormat.JPEG, 100, fileOS);
  27. fileOS.flush(); //将缓冲区中的数据全部写出到输出流中
  28. fileOS.close(); //关闭文件输出流对象
  29. isPreview = true;
  30. resetCamera();
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
  36.  
  37. public void onClick(DialogInterface dialog, int which) {
  38. isPreview = true;
  39. resetCamera(); //重新预览
  40. }
  41. }).show();
  42. }
  43. };

(8)编写保存对话框所需要的布局文件,名称为save.xml,在该文件中,添加一个垂直线性布局管理器,并在该布局管理器中添加一个水平线性布局管理器(用于添加输入相片名称的文本框和编辑框)和一个ImageView组件(用于显示相片预览),具体代码请参见光盘。

(9)编写实现重新预览的方法resetCamera(),在该方法中,当isPreview变量的值为真时,调用相机的startPreview()方法开启预览,具体代码如下:

  1. private void resetCamera(){
  2. if(isPreview){
  3. camera.startPreview(); //开启预览
  4. }
  5. }

(10)重写Activity的onPause()方法,用于当暂停Activity时,停止预览并释放相机资源,具体代码如下:

  1. @Override
  2. protected void onPause() {
  3. if(camera!=null){
  4. camera.stopPreview(); //停止预览
  5. camera.release(); //释放资源
  6. }
  7. super.onPause();
  8. }

(11)由于本程序需要访问SD卡和控制相机,所以需要在AndroidManifest.xml文件中赋予程序访问SD卡和控制相机的权限,关键代码如下:

  1. <!-- 授予程序可以向SD卡中保存文件的权限 -->
  2. <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
  3. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  4. <!-- 授予程序使用摄像头的权限 -->
  5. <uses-permission android:name="android.permission.CAMERA" />
  6. <uses-feature android:name="android.hardware.camera" />
  7. <uses-feature android:name="android.hardware.camera.autofocus" />

运行本实例后,单击“预览”按钮,在屏幕的右侧将显示如图10.7所示的相机预览画面,单击“拍照”按钮,即可进行拍照,并显示保存图片对话框,输入文件名(不包括扩展名),如图10.8所示,单击“保存”按钮,即可将所拍的画面保存到SD卡的pictures目录中。

361-1 图10.7 相机预览画面

362-1 图10.8 保存图片对话框

10.3 经典范例

10.3.1 为游戏界面添加背景音乐和按键音

例10.8 在Eclipse中创建Android项目,名称为10.8,实现为游戏界面添加背景音乐和按键音。(实例位置:光盘\TM\sl\10\10.8)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的布局代码删除,然后添加一个FrameLayout帧布局管理器,并在该布局管理器中添加一个ImageView组件,用于显示小兔子图像,另外,还需要为添加的帧布局管理器设置背景图片,具体代码请参见光盘。

(2)打开默认添加的MainActivity,在该类中,创建程序中所需的成员变量,具体代码如下:

  1. private SoundPool soundpool; //声明一个SoundPool对象
  2. private HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象
  3. private ImageView rabbit;
  4. private int x=0; //兔子在X轴的位置
  5. private int y=0; //兔子在Y轴的位置
  6. private int width=0; //屏幕的宽度
  7. private int height=0; //屏幕的高度

(3)在onCreate()方法中,首先实例化SoundPool对象,并将要播放的全部音频流保存到HashMap对象中,然后获取布局管理器中添加的小兔子,并获取屏幕的宽度和高度,再计算小兔子在X轴和Y轴的位置,最后通过setX()和setY()方法设置兔子的默认位置,具体代码如下:

  1. soundpool = new SoundPool(5,
  2. AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳5个音频流
  3. //将要播放的音频流保存到HashMap对象中
  4. soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
  5. soundmap.put(2, soundpool.load(this, R.raw.enter, 1));
  6. soundmap.put(3, soundpool.load(this, R.raw.notify, 1));
  7. soundmap.put(4, soundpool.load(this, R.raw.ringout, 1));
  8. soundmap.put(5, soundpool.load(this, R.raw.ding, 1));
  9. rabbit=(ImageView)findViewById(R.id.rabbit);
  10. width= MainActivity.this.getResources().getDisplayMetrics().widthPixels;
  11. height=MainActivity.this.getResources().getDisplayMetrics().heightPixels;
  12. x=width/2-44; //计算兔子在X轴的位置
  13. y=height/2-35; //计算兔子在Y轴的位置
  14. rabbit.setX(x); //设置兔子在X轴的位置
  15. rabbit.setY(y); //设置兔子在Y轴的位置

(4)重写键盘的按键被按下的onKeyDown()方法,在该方法中,应用switch()语句分别为上、下、左、右方向键和其他按键指定不同的按键音,同时,在按下上、下、左和右方向键时,还会控制小兔子在相应方向上移动,具体代码如下:

  1. @Override
  2. public boolean onKeyDown(int keyCode, KeyEvent event) {
  3. switch(keyCode){
  4. case KeyEvent.KEYCODE_DPAD_LEFT: //向左方向键
  5. soundpool.play(soundmap.get(1), 1, 1, 0, 0, 1); //播放指定的音频
  6. if(x>0){
  7. x-=10;
  8. rabbit.setX(x); //移动小兔子
  9. }
  10. break;
  11. case KeyEvent.KEYCODE_DPAD_RIGHT: //向右方向键
  12. soundpool.play(soundmap.get(2), 1, 1, 0, 0, 1); //播放指定的音频
  13. if(x<width-88){
  14. x+=10;
  15. rabbit.setX(x); //移动小兔子
  16. }
  17. break;
  18. case KeyEvent.KEYCODE_DPAD_UP: //向上方向键
  19. soundpool.play(soundmap.get(3), 1, 1, 0, 0, 1); //播放指定的音频
  20. if(y>0){
  21. y-=10;
  22. rabbit.setY(y); //移动小兔子
  23. }
  24. break;
  25. case KeyEvent.KEYCODE_DPAD_DOWN: //向下方向键
  26. soundpool.play(soundmap.get(4), 1, 1, 0, 0, 1); //播放指定的音频
  27. if(y
  28. <height-70){
  29. y+=10;
  30. rabbit.setY(y); //移动小兔子
  31. }
  32. break;
  33. default:
  34. soundpool.play(soundmap.get(5), 1, 1, 0, 0, 1); //播放默认按键音
  35. }
  36. return super.onKeyDown(keyCode, event);
  37. }

(5)在res目录下,创建一个menu子目录,并在该目录中创建一个名称为setting.xml的菜单资源文件,在该文件中,添加一个控制是否播放背景音乐的多选菜单组,默认为选中状态,setting.xml文件具体代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <menu xmlns:android="http://schemas.android.com/apk/res/android" >
  3. <group android:id="@+id/setting" android:checkableBehavior="all">
  4. <item android:id="@+id/bgsound" android:title="播放背景音乐" android:checked="true"></item>
  5. </group>
  6. </menu>

(6)重写onCreateOptionsMenu()方法,应用步骤(5)中添加的菜单文件,创建一个选项菜单,并重写onOptionsItemSelected()方法,对菜单项的选取状态进行处理,主要用于根据菜单项的选取状态控制是否播放背景音乐。具体代码如下:

  1. @Override
  2. public boolean onCreateOptionsMenu(Menu menu) {
  3. MenuInflater inflater=new MenuInflater(this); //实例化一个MenuInflater对象
  4. inflater.inflate(R.menu.setting, menu); //解析菜单文件
  5. return super.onCreateOptionsMenu(menu);
  6. }
  7. @Override
  8. public boolean onOptionsItemSelected(MenuItem item) {
  9. if(item.getGroupId()==R.id.setting){ //判断是否选择了参数设置菜单组
  10. if(item.isChecked()){ //若菜单项已经被选中
  11. item.setChecked(false); //设置菜单项不被选中
  12. Music.stop(this);
  13. }else{
  14. item.setChecked(true); //设置菜单项被选中
  15. Music.play(this, R.raw.jasmine);
  16. }
  17. }
  18. return true;
  19. }

(7)编写Music类,在该类中,首先声明一个MediaPlayer对象,然后编写用于播放背景音乐的play()方法,最后编写用于停止播放背景音乐的stop()方法,关键代码如下:

  1. public class Music {
  2. private static MediaPlayer mp = null; //声明一个MediaPlayer对象
  3. public static void play(Context context, int resource) {
  4. stop(context);
  5. if (SettingsActivity.getBgSound(context)) { //判断是否播放背景音乐
  6. mp = MediaPlayer.create(context, resource);
  7. mp.setLooping(true); //是否循环播放
  8. mp.start(); //开始播放
  9. }
  10. }
  11. public static void stop(Context context) {
  12. if (mp != null) {
  13. mp.stop(); //停止播放
  14. mp.release(); //释放资源
  15. mp = null;
  16. }
  17. }
  18. }

说明:在上面的代码中,加粗的代码SettingsActivity.getBgSound(context)用于获取选项菜单存储的首选值,这样可以实现通过选项菜单控制是否播放背景音乐。

(8)编写SettingsActivity类,该类继承PreferenceActivity类,用于实现自动存储首选项的值。在SettingsActivity类中,首先重写onCreate()方法,在该方法中调用addPreferencesFromResource()方法加载首选项资源文件,然后编写获取是否播放背景音乐的首选项的值的getBgSound()方法,在该方法中返回获取到的值,关键代码如下:

  1. public class SettingsActivity extends PreferenceActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4.  
  5. super.onCreate(savedInstanceState);
  6. addPreferencesFromResource(R.xml.setting);
  7. }
  8. //获取是否播放背景音乐的首选项的值
  9. public static boolean getBgSound(Context context){
  10. return PreferenceManager.getDefaultSharedPreferences(context)
  11. .getBoolean("bgsound",true);
  12. }
  13. }

说明:PreferenceActivity类用于实现对程序设置参数的存储。在该Activity中,设置参数的存储是完全自动的,不需要手动保存,非常方便。

(9)在res目录下,创建一个xml目录,在该目录中添加一个名称为setting.xml的首选项资源文件,具体代码如下:

  1. <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  2. <CheckBoxPreference
  3. android:key="bgsound"
  4. android:title="播放背景音乐"
  5. android:summary="选中为播放背景音乐"
  6. android:defaultValue="true"/>
  7. </PreferenceScreen>

(10)在MainActivity中,重写onPause()方法,在该方法中,调用Music类的stop()方法停止播放背景音乐,具体代码如下:

  1. @Override
  2. protected void onPause() {
  3. Music.stop(this); //停止播放背景音乐
  4. super.onPause();
  5. }

(11)在MainActivity中,重写onResume()方法,在该方法中,调用Music类的play()方法开始播放背景音乐,具体代码如下:

  1. @Override
  2. protected void onResume() {
  3. Music.play(this, R.raw.jasmine); //播放背景音乐
  4. super.onResume();
  5. }

运行本实例,将显示如图10.9所示的运行结果。

366-1 图10.9 为游戏界面添加背景音乐和按键音

10.3.2 制作开场动画

例10.9 在Eclipse中创建Android项目,名称为10.9,制作开场动画。(实例位置:光盘\TM\sl\ 10\10.9)

(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的布局代码删除,然后添加一个FrameLayout帧布局管理器,并在该布局管理器中添加一个ImageView控件,用于显示小兔子图像,另外,还需要为添加的帧布局管理器设置背景图片,具体代码请参见光盘。

(2)在res\layout目录下创建一个布局文件start.xml,在该文件中添加一居中显示的线性布局管理器,并在该布局管理器中添加一个VideoView组件,用于播放开场动画视频文件,关键代码如下:

  1. <VideoView
  2. android:id="@+id/video"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content" />

(3)创建一个名称为StartActivity的Activity,并重写其onCreate()方法,在该方法中,首先获取VideoView组件,并获取要播放的文件对应的URI,然后为VideoView组件指定要播放的视频,并让其获得焦点,再调用start()方法开始播放视频,最后为VideoView添加完成事件监听器,在重写的onCompletion()方法中调用startMain()方法进入到游戏主界面,具体代码如下:

  1. video = (VideoView) findViewById(R.id.video); //获取VideoView组件
  2. Uri uri = Uri.parse("android.resource://com.mingrisoft/"+R.raw.mingrisoft); //获取要播放的文件对应的URI
  3. video.setVideoURI(uri); //指定要播放的视频
  4. video.requestFocus(); //让VideoView获得焦点
  5. try {
  6. video.start(); //开始播放视频
  7. } catch (Exception e) {
  8. e.printStackTrace(); //输出异常信息
  9. }
  10. //为VideoView添加完成事件监听器
  11. video.setOnCompletionListener(new OnCompletionListener() {
  12. @Override
  13. public void onCompletion(MediaPlayer mp) {
  14. startMain(); //进入游戏主界面
  15. }
  16. });

(4)编写进入游戏主界面的startMain()方法,在该方法中创建一个新的Intent,以启动游戏主界面的Activity,具体代码如下:

  1. //进入游戏主界面
  2. private void startMain(){
  3. Intent intent = new Intent(StartActivity.this, MainActivity.class); //创建Intent
  4. startActivity(intent); //启动新的Activity
  5. StartActivity.this.finish(); //结束当前Activity
  6. }

(5)打开AndroidManifest.xml文件,在该文件中配置项目中应用的Activity。这里首先将主Activity设置为StartActivity,然后再配置MainActivity,关键代码如下:

  1. <activity
  2. android:label="@string/app_name"
  3. android:name=".StartActivity" >
  4. <intent-filter >
  5. <action android:name="android.intent.action.MAIN" />
  6. <category android:name="android.intent.category.LAUNCHER" />
  7. </intent-filter>
  8. </activity>
  9. <activity android:name=".MainActivity"/>

运行本实例,首先播放指定的视频,视频播放完毕后,将进入到如图10.10所示的游戏主界面。

368-1 图10.10 游戏主界面

10.4 小 结

本章主要介绍了在Android中,如何播放音频与视频,以及如何控制相机拍照等内容。需要重点说明的是两种播放音频方法的区别。本章共介绍了两种播放音频的方法,一种是使用MediaPlayer播放,另一种是使用SoundPool播放。这两种方法的区别是:使用MediaPlayer每次只能播放一个音频,适用于播放长音乐或是背景音乐;使用SoundPool可以同时播放多个短小的音频,适用于播放按键音或者消息提示音等,希望读者根据实际情况选择合适的方法。

10.5 实践与练习

  1. 编写Android项目,使用MediaPlayer和SurfaceView实现带音量控制的视频播放器。(答案位置:光盘\TM\sl\10\10.10)

  2. 编写Android项目,实现控制是否播放按键音。(答案位置:光盘\TM\sl\10\10.11)