第10章 多媒体应用开发
(教学录像:1小时36分钟)
随着3G时代的到来,多媒体在手机和平板电脑上广泛应用。Android作为手机和平板电脑的一个操作系统,对于多媒体应用也提供了良好的支持。它不仅支持音频和视频的播放,而且还支持音频录制和摄像头拍照。本章将对Android中的音频、视频以及摄像头拍照等多媒体应用进行详细介绍。
通过阅读本章,您可以:
★ 了解Android支持的音频和视频格式
★ 掌握使用MediaPlayer播放音频的方法
★ 掌握使用SoundPool播放音频的方法
★ 掌握如何使用VideoView播放视频
★ 掌握如何使用MediaPlayer和SurfaceView播放视频
★ 掌握如何控制相机拍照
10.1 播放音频与视频
教学录像:光盘\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()方法即可,下面进行详细介绍。
- 创建MediaPlayer对象,并装载音频文件
创建MediaPlayer对象并装载音频文件,可以使用MediaPayer类提供的静态方法create()来实现,也可以通过其无参构造方法来创建并实例化该类的对象来实现。
MediaPlayer类的静态方法create()常用的语法格式有以下两种。
[√]create(Context context, int resid)
用于从资源ID所对应的资源文件中装载音频,并返回新创建的MediaPlayer对象。例如,要创建装载音频资源(res/raw/d.wav)的MediaPlayer对象,可以使用下面的代码:
- MediaPlayer player=MediaPlayer.create(this, R.raw.d);
[√]create(Context context, Uri uri)
用于根据指定的URI来装载音频,并返回新创建的MediaPlayer对象。例如,要创建装载了音频文件(URI地址为http://www.mingribook.com/sound/bg.mp3)的MediaPlayer对象,可以使用下面的代码:
- MediaPlayer player=MediaPlayer.create(this, Uri.parse("http://www.mingribook.com/sound/bg.mp3"));
说明:在访问网络中的资源时,要在AndroidManifest.xml文件中授予该程序访问网络的权限,具体的授权代码如下:
- <uses-permission android:name="android.permission.INTERNET"/>
在通过MediaPlayer类的静态方法create()来创建MediaPlayer对象时,已经装载了要播放的音频,而使用无参的构造方法来创建MediaPlayer对象时,需要单独指定要装载的资源,这可以使用MediaPlayer类的setDataSource()方法实现。
在使用setDataSource()方法装载音频文件后,实际上MediaPlayer并未真正装载该音频文件,还需要调用MediaPlayer的prepare()方法去真正装载音频文件。使用无参的构造方法来创建MediaPlayer对象并装载指定的音频文件,可以使用下面的代码:
- MediaPlayer player=new MediaPlayer();
- try {
- player.setDataSource("/sdcard/s.wav"); //指定要装载的音频文件
- } catch (IllegalArgumentException e1) {
- e1.printStackTrace();
- } catch (SecurityException e1) {
- e1.printStackTrace();
- } catch (IllegalStateException e1) {
- e1.printStackTrace();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- try {
- player.prepare(); //预加载音频
- } catch (IllegalStateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- 开始或恢复播放
在获取到MediaPlayer对象后,就可以使用MediaPlayer类提供的start()方法来开始播放或恢复已经暂停的音频的播放。例如,已经创建了一个名称为player的对象,并且装载了要播放的音频,可以使用下面的代码播放该音频:
- player.start(); //开始播放
- 停止播放
使用MediaPlayer类提供的stop()方法可以停止正在播放的音频。例如,已经创建了一个名称为player的对象,并且已经开始播放装载的音频,可以使用下面的代码停止播放该音频:
- player.stop(); //停止播放
- 暂停播放
使用MediaPlayer类提供的pause()方法可以暂停正在播放的音频。例如,已经创建了一个名称为player的对象,并且已经开始播放装载的音频,可以使用下面的代码暂停播放该音频:
- 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,在该类中,定义所需的成员变量,具体代码如下:
- private MediaPlayer player; //MediaPlayer对象
- private boolean isPause = false; //是否暂停
- private File file; //要播放的音频文件
- private TextView hint; //声明显示提示信息的文本框
(4)在onCreate()方法中,首先获取布局管理器中添加的“播放”按钮、“暂停/继续”按钮、“停止”按钮和显示提示信息的文本框,然后获取要播放的文件,最后判断该文件是否存在,如果存在,则创建一个装载该文件的MediaPlayer对象;否则,显示提示信息,并设置“播放”按钮不可用,关键代码如下:
- final Button button1 = (Button) findViewById(R.id.button1); //获取“播放”按钮
- final Button button2 = (Button) findViewById(R.id.button2); //获取“暂停/继续”按钮
- final Button button3 = (Button) findViewById(R.id.button3); //获取“停止”按钮
- hint = (TextView) findViewById(R.id.hint); //获取用于显示提示信息的文本框
- file = new File("/sdcard/ninan.mp3"); //获取要播放的文件
- if (file.exists()) { //如果文件存在
- player = MediaPlayer
- .create(this, Uri.parse(file.getAbsolutePath())); //创建MediaPlayer对象
- } else {
- hint.setText("要播放的音频文件不存在!");
- button1.setEnabled(false);
- return;
- }
(5)编写用于播放音乐的play()方法,该方法没有入口参数的返回值。在该方法中,首先调用MediaPlayer对象的reset()方法重置MediaPlayer对象,然后重新为其设置要播放的音频文件,并预加载该音频,最后调用 start()方法开始播放音频,并修改显示提示信息的文本框中的内容,具体代码如下:
- private void play() {
- try {
- player.reset();
- player.setDataSource(file.getAbsolutePath()); //重新设置要播放的音频
- player.prepare(); //预加载音频
- player.start(); //开始播放
- hint.setText("正在播放音频...");
- } catch (Exception e) {
- e.printStackTrace(); //输出异常信息
- }
- }
(6)为MediaPlayer对象添加完成事件监听器,用于当音乐播放完毕后,重新开始播放音乐,具体代码如下:
- player.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- play(); //重新开始播放
- }
- });
(7)为“播放”按钮添加单击事件监听器,在重写的onClick()方法中,首先调用play()方法开始播放音乐,然后对代表是否暂停的标记变量isPause进行设置,最后设置各按钮的可用状态,关键代码如下:
- button1.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- play(); //开始播放音乐
- if (isPause) {
- button2.setText("暂停");
- isPause = false; //设置暂停标记变量的值为false
- }
- button2.setEnabled(true); //“暂停/继续”按钮可用
- button3.setEnabled(true); //“停止”按钮可用
- button1.setEnabled(false); //“播放”按钮不可用
- }
- });
(8)为“暂停/继续”按钮添加单击事件监听器,在重写的onClick()方法中,如果MediaPlayer处于播放状态并且标记变量isPause的值为false,则暂停播放音频,并设置相关信息;否则,调用MediaPlayer对象的start()方法继续播放音乐,并设置相关信息,关键代码如下:
- button2.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (player.isPlaying() && !isPause) {
- player.pause(); //暂停播放
- isPause = true;
- ((Button) v).setText("继续");
- hint.setText("暂停播放音频...");
- button1.setEnabled(true); //“播放”按钮可用
- } else {
- player.start(); //继续播放
- ((Button) v).setText("暂停");
- hint.setText("继续播放音频...");
- isPause = false;
- button1.setEnabled(false); //“播放”按钮不可用
- }
- }
- });
(9)为“停止”按钮添加单击事件监听器,在重写的onClick()方法中,首先调用MediaPlayer对象的stop()方法停止播放音频,然后设置提示信息及各按钮的可用状态,具体代码如下:
- button3.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- player.stop(); //停止播放
- hint.setText("停止播放音频...");
- button2.setEnabled(false); //“暂停/继续”按钮不可用
- button3.setEnabled(false); //“停止”按钮不可用
- button1.setEnabled(true); //“播放”按钮可用
- }
- });
(10)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源,具体代码如下:
- @Override
- protected void onDestroy() {
- if(player.isPlaying()){
- player.stop(); //停止音频的播放
- }
- player.release(); //释放资源
- super.onDestroy();
- }
运行本实例,将显示一个简易音乐播放器,单击“播放”按钮,将开始播放音乐,同时“播放”按钮变为不可用状态,而“暂停”和“停止”按钮变为可用状态,如图10.1所示;单击“暂停”按钮,将暂停音乐的播放,同时“播放”按钮变为可用;单击“继续”按钮,将继续音乐的播放,同时“继续”按钮变为“暂停”按钮;单击“停止”按钮,将停止音乐的播放,同时“暂停/继续”和“停止”按钮将变为不可用,“播放”按钮可用。
图10.1 简易音乐播放器
10.1.2 使用SoundPool播放音频
由于MediaPlayer占用资源较多,且不支持同时播放多个音频,所以Android还提供了另一个播放音频的类——SoundPool。SoundPool即音频池,可以同时播放多个短小的音频,而且占用的资源较少。SoundPool适合在应用程序中播放按键音或者消息提示音等,在游戏中播放密集而短暂的声音,如多个飞机的爆炸声等。使用SoundPool播放音频,首先需要创建SoundPool对象,然后加载所要播放的音频,最后调用play()方法播放音频,下面进行详细介绍。
- 创建SoundPool对象
SoundPool类提供了一个构造方法,用来创建SoundPool对象,该构造方法的语法格式如下:
- SoundPool (int maxStreams, int streamType, int srcQuality)
其中,参数maxStreams用于指定可以容纳多少个音频;参数streamType用于指定声音类型,可以通过AudioManager类提供的常量进行指定,通常使用STREAM_MUSIC;参数srcQuality用于指定音频的品质,默认值为0。
例如,创建一个可以容纳10个音频的SoundPool对象,可以使用下面的代码:
- SoundPool soundpool = new SoundPool(10,
- AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳10个音频流
- 加载所要播放的音频
创建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,可以使用下面的代码:
- soundpool.load(this, R.raw.ding, 1);
说明:为了更好地管理所加载的每个音频,一般使用HashMap<Integer, Integer>对象来管理这些音频。这时可以先创建一个HashMap<Integer, Integer>对象,然后应用该对象的put()方法将加载的音频保存到该对象中。例如,创建一个HashMap<Integer, Integer>对象,并应用put()方法添加一个音频,可以使用下面的代码:
- HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象
- soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
- 播放音频
调用SoundPool对象的play()方法可播放指定的音频。play()方法的语法格式如下:
- 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,可以使用下面的代码:
- 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,在该类中,创建两个成员变量,具体代码如下:
- private SoundPool soundpool; //声明一个SoundPool对象
- private HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象
(3)在onCreate()方法中,首先获取布局管理器中添加的“风铃声”按钮、“布谷鸟叫声”按钮、“门铃声”按钮和“电话声”按钮,然后实例化SoundPool对象,再将要播放的全部音频流保存到HashMap对象中,具体代码如下:
- Button chimes = (Button) findViewById(R.id.button1); //获取“风铃声”按钮
- Button enter = (Button) findViewById(R.id.button2); //获取“布谷鸟叫声”按钮
- Button notify = (Button) findViewById(R.id.button3); //获取“门铃声”按钮
- Button ringout = (Button) findViewById(R.id.button4); //获取“电话声”按钮
- soundpool = new SoundPool(5,
- AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳5个音频流
- //将要播放的音频流保存到HashMap对象中
- soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
- soundmap.put(2, soundpool.load(this, R.raw.enter, 1));
- soundmap.put(3, soundpool.load(this, R.raw.notify, 1));
- soundmap.put(4, soundpool.load(this, R.raw.ringout, 1));
- soundmap.put(5, soundpool.load(this, R.raw.ding, 1));
(4)分别为“风铃声”按钮、“布谷鸟叫声”按钮、“门铃声”按钮和“电话声”按钮添加单击事件监听器,在重写的onClick()方法中播放指定的音频,具体代码如下:
- chimes.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- soundpool.play(soundmap.get(1), 1, 1, 0, 0, 1); //播放指定的音频
- }
- });
- enter.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- soundpool.play(soundmap.get(2), 1, 1, 0, 0, 1); //播放指定的音频
- }
- });
- notify.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- soundpool.play(soundmap.get(3), 1, 1, 0, 0, 1); //播放指定的音频
- }
- });
- ringout.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- soundpool.play(soundmap.get(4), 1, 1, 0, 0, 1); //播放指定的音频
- }
- });
(5)重写键盘按键被按下的onKeyDown()方法,用于实现播放按键音的功能,具体代码如下:
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- soundpool.play(soundmap.get(5), 1, 1, 0, 0, 1); //播放按键音
- return true;
- }
运行本实例,将显示如图10.2所示的运行结果。单击“风铃声”、“布谷鸟叫声”等按钮,将播放相应的音乐;按下键盘上的按键,将播放一个按键音。
图10.2 应用SoundPool播放音频
10.1.3 使用VideoView播放视频
在Android中,提供了VideoView组件用于播放视频文件。要想使用VideoView组件播放视频,首先需要在布局文件中创建该组件,然后在Activity中获取该组件,并应用其setVideoPath()方法或setVideoURI()方法加载要播放的视频,最后调用start()方法来播放视频。另外,VideoView组件还提供了stop()和pause()方法,用于停止或暂停视频的播放。
在布局文件中创建VideoView组件的基本语法格式如下:
- <VideoView
- 属性列表>
- </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组件用于播放视频文件,关键代码如下:
- <VideoView
- android:id="@+id/video"
- android:background="@drawable/mpbackground"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
(2)打开默认添加的MainActivity,在该类中,声明一个VideoView对象,具体代码如下:
- private VideoView video; //声明VideoView对象
(3)在onCreate()方法中,首先获取布局管理器中添加的VideoView组件,并创建一个要播放视频所对应的File对象,然后创建一个MediaController对象,用于控制视频的播放,最后判断要播放的视频文件是否存在,如果存在,使用VideoView播放该视频,否则弹出消息提示框显示提示信息,具体代码如下:
- video=(VideoView) findViewById(R.id.video); //获取VideoView组件
- File file=new File("/sdcard/bell.mp4"); //获取SD卡上要播放的文件
- MediaController mc=new MediaController(MainActivity.this);
- if(file.exists()){ //判断要播放的视频文件是否存在
- video.setVideoPath(file.getAbsolutePath()); //指定要播放的视频
- video.setMediaController(mc); //设置VideoView与MediaController相关联
- video.requestFocus(); //让VideoView获得焦点
- try {
- video.start(); //开始播放视频
- } catch (Exception e) {
- e.printStackTrace(); //输出异常信息
- }
- //为VideoView添加完成事件监听器
- video.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- //弹出消息提示框显示播放完毕
- Toast.makeText(MainActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
- }
- });
- }else{
- //弹出消息提示框提示文件不存在
- Toast.makeText(this, "要播放的视频文件不存在", Toast.LENGTH_SHORT).show();
- }
运行本实例,将显示如图10.3所示的运行结果。
图10.3 使用VideoView和MediaController组件播放视频
说明:由于本实例是在模拟器上运行的,所以并没有显示视频画面,而在屏幕中间显示的图片是为VideoView设置的背景图片。如果将该程序发布到真机上运行,就可以看到视频画面了。
10.1.4 使用MediaPlayer和SurfaceView播放视频
使用MediaPlayer除可以播放音频外,还可以播放视频文件,只不过使用MediaPlayer播放视频时,没有提供图像输出界面。这时,可以使用SurfaceView组件来显示视频图像。使用MediaPlayer和SurfaceView来播放视频,大致可以分为以下4个步骤。
(1)定义SurfaceView组件。定义SurfaceView组件可以在布局管理器中实现,也可以直接在Java代码中创建,不过推荐在布局管理器中定义SurfaceView组件,其基本语法格式如下:
- <SurfaceView
- android:id="@+id/ID号"
- android:background="背景"
- android:keepScreenOn="true|false"
- android:layout_width="宽度"
- android:layout_height="高度"/>
在上面的语法中,android:keepScreenOn属性用于指定在播放视频时,是否打开屏幕。
例如,在布局管理器中,添加一个ID号为surfaceView1、设置了背景的SurfaceView组件,可以使用下面的代码:
- <SurfaceView
- android:id="@+id/surfaceView1"
- android:background="@drawable/bg"
- android:keepScreenOn="true"
- android:layout_width="576px"
- android:layout_height="432px"/>
(2)创建MediaPlayer对象,并为其加载要播放的视频。与播放音频时创建MediaPlayer对象一样,也可以使用MediaPlayer类的静态方法create()和无参的构造方法两种方式创建MediaPlayer对象,具体方法请参见10.1.1节。
(3)将所播放的视频画面输出到SurfaceView。使用MediaPlayer对象的setDisplay()方法,可以将所播放的视频画面输出到SurfaceView。setDisplay()方法的语法格式如下:
- setDisplay(SurfaceHolder sh)
参数sh用于指定SurfaceHolder对象,可以通过SurfaceView对象的getHolder()方法获得。例如,为MediaPlayer对象指定输出视频画面的SurfaceView,可以使用下面的代码:
- 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个按钮,分别为“播放”按钮、“暂停/继续”按钮和“停止”按钮,关键代码如下:
- <SurfaceView
- android:id="@+id/surfaceView1"
- android:background="@drawable/bg"
- android:keepScreenOn="true"
- android:layout_width="576px"
- android:layout_height="432px"/>
(2)打开默认添加的MainActivity,在该类中,声明一个MediaPlayer对象和一个SurfaceView对象,具体代码如下:
- private MediaPlayer mp; //声明MediaPlayer对象
- private SurfaceView sv; //声明SurfaceView对象
(3)在onCreate()方法中,首先实例化MediaPlayer对象,然后获取布局管理器中添加的SurfaceView组件,再分别获取“播放”按钮、“暂停/继续”按钮和“停止”按钮,具体代码如下:
- mp=new MediaPlayer(); //实例化MediaPlayer对象
- sv=(SurfaceView)findViewById(R.id.surfaceView1); //获取布局管理器中添加的SurfaceView组件
- Button play=(Button)findViewById(R.id.play); //获取“播放”按钮
- final Button pause=(Button)findViewById(R.id.pause); //获取“暂停/继续”按钮
- Button stop=(Button)findViewById(R.id.stop); //获取“停止”按钮
(4)分别为“播放”按钮、“暂停/继续”按钮和“停止”按钮添加单击事件监听器,并在重写的onClick()方法中,实现播放视频、暂停/继续播放视频和停止播放视频等功能,具体代码如下:
- //为“播放”按钮添加单击事件监听器
- play.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mp.reset(); //重置MediaPlayer对象
- try {
- mp.setDataSource("/sdcard/ccc.mp4"); //设置要播放的视频
- mp.setDisplay(sv.getHolder()); //设置将视频画面输出到SurfaceView
- mp.prepare(); //预加载视频
- mp.start(); //开始播放
- sv.setBackgroundResource(R.drawable.bg_playing); //改变SurfaceView的背景图片
- pause.setText("暂停");
- pause.setEnabled(true); //设置“暂停”按钮可用
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (SecurityException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- });
- //为“停止”按钮添加单击事件监听器
- stop.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if(mp.isPlaying()){
- mp.stop(); //停止播放
- sv.setBackgroundResource(R.drawable.bg_finish); //改变SurfaceView的背景图片
- pause.setEnabled(false); //设置“暂停”按钮不可用
- }
- }
- });
- //为“暂停”按钮添加单击事件监听器
- pause.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if(mp.isPlaying()){
- mp.pause(); //暂停视频的播放
- ((Button)v).setText("继续");
- }else{
- mp.start(); //继续视频的播放
- ((Button)v).setText("暂停");
- }
- }
- });
(5)为MediaPlayer对象添加完成事件监听器,在重写的onCompletion()方法中改变SurfaceView的背景图片并弹出消息提示框显示视频已经播放完毕,具体代码如下:
- mp.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- sv.setBackgroundResource(R.drawable.bg_finish); //改变SurfaceView的背景图片
- Toast.makeText(MainActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
- }
- });
(6)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源,具体代码如下:
- @Override
- protected void onDestroy() {
- if(mp.isPlaying()){
- mp.stop(); //停止播放视频
- }
- mp.release(); //释放资源
- super.onDestroy();
- }
运行本实例,单击“播放”按钮,将开始播放视频,并且“暂停”按钮变为可用,如图10.4所示;单击“暂停”按钮,将暂停视频的播放,同时该按钮变为“继续”按钮;单击“停止”按钮,将停止正在播放的视频。
图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个按钮,分别为“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮,其中“暂停/继续”按钮默认为不可用,关键代码如下:
- <ListView
- android:id="@+id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:drawSelectorOnTop="false"/>
(2)打开默认添加的MainActivity,在该类中,声明程序中所需的成员变量,具体代码如下:
- private MediaPlayer mediaPlayer; //声明MediaPlayer对象
- private List<String> audioList = new ArrayList<String>(); //要播放的音频列表
- private int currentItem = 0; //当前播放歌曲的索引
- private Button pause; //声明一个“暂停”按钮对象
(3)在onCreate()方法中,首先实例化MediaPlayer对象,然后获取布局管理器中添加的“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮,再调用audioList()方法在ListView组件上显示全部音频,具体代码如下:
- mediaPlayer = new MediaPlayer(); //实例化一个MediaPlayer对象
- Button play = (Button) findViewById(R.id.play); //获取“播放”按钮
- Button stop = (Button) findViewById(R.id.stop); //获取“停止”按钮
- pause = (Button) findViewById(R.id.pause); //获取“暂停/继续”按钮
- Button pre = (Button) findViewById(R.id.pre); //获取“上一首”按钮
- Button next = (Button) findViewById(R.id.next); //获取“下一首”按钮
- audioList(); //使用ListView组件显示SD卡上的全部音频文件
(4)编写audioList()方法,用于使用ListView组件显示SD卡上的全部音频文件。在该方法中,首先调用getFiles()方法获取SD卡上的全部音频文件,然后创建一个适配器,并获取布局管理器中添加的ListView组件,再将适配器与ListView关联,最后为ListView添加列表项单击事件监听器,用于当用户单击列表项时播放音乐。audioList()方法的具体代码如下:
- private void audioList() {
- getFiles("/sdcard/"); //获取SD卡上的全部音频文件
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, audioList); //创建一个适配器
- ListView listview = (ListView) findViewById(R.id.list); //获取布局管理器中添加的ListView组件
- listview.setAdapter(adapter); //将适配器与ListView关联
- //当单击列表项时播放音乐
- listview.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> listView, View view,int position, long id) {
- currentItem = position; //将当前列表项的索引值赋值给currentItem
- playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
- }
- });
- }
(5)定义一个保存合法的音频文件格式的字符串数组,并编写根据文件路径判断文件是否为音频文件的方法,具体代码如下:
- private static String[] imageFormatSet = new String[] { "mp3", "wav", "3gp" }; //合法的音频文件格式
- //判断是否为音频文件
- private static boolean isAudioFile(String path) {
- for (String format : imageFormatSet) { //遍历数组
- if (path.contains(format)) { //判断是否为合法的音频文件
- return true;
- }
- }
- return false;
- }
(6)编写getFiles()方法,用于通过递归调用的方式获取SD卡上的全部音频文件,具体代码如下:
- private void getFiles(String url) {
- File files = new File(url); //创建文件对象
- File[] file = files.listFiles();
- try {
- for (File f : file) { //通过for循环遍历获取到的文件数组
- if (f.isDirectory()) { //如果是目录,也就是文件夹
- getFiles(f.getAbsolutePath()); //递归调用
- } else {
- if (isAudioFile(f.getPath())) { //如果是音频文件
- audioList.add(f.getPath()); //将文件的路径添加到List集合中
- }
- }
- }
- } catch (Exception e) {
- e.printStackTrace(); //输出异常信息
- }
- }
(7)编写用于播放音乐的方法playMusic(),在该方法中,首先判断是否正在播放音乐,如果正在播放音乐,先停止播放,然后重置MediaPlayer,并指定要播放的音频文件,再预加载该音频文件,最后播放音频,并设置“暂停”按钮的显示文字及可用状态。playMusic()方法的具体代码如下:
- void playMusic(String path) {
- try {
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.stop(); //停止当前音频的播放
- }
- mediaPlayer.reset(); //重置MediaPlayer
- mediaPlayer.setDataSource(path); //指定要播放的音频文件
- mediaPlayer.prepare(); //预加载音频文件
- mediaPlayer.start(); //播放音频
- pause.setText("暂停");
- pause.setEnabled(true); //设置“暂停”按钮可用
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
(8)编写实现“下一首”功能的方法nextMusic(),在该方法中,首先计算要播放音频的索引,然后调用playMusic()播放音乐。nextMusic()方法的具体代码如下:
- void nextMusic() {
- if (++currentItem >= audioList.size()) {//当对currentItem进行+1操作后,如果其值大于等于音频文件的总数
- currentItem = 0;
- }
- playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
- }
(9)编写实现“上一首”功能的方法preMusic(),在该方法中,首先计算要播放音频的索引,然后调用playMusic()播放音乐。preMusic()方法的具体代码如下:
- void preMusic() {
- if (--currentItem >= 0) { //当对currentItem进行-1操作后,如果其值大于等于0
- if (currentItem >= audioList.size()) { //如果currentItem的值大于等于音频文件的总数
- currentItem = 0;
- }
- } else {
- currentItem = audioList.size() - 1; //currentItem的值设置为音频文件总数-1
- }
- playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
- }
(10)为MediaPlayer对象添加完成事件监听器,在重写的onCompletion()方法中调用nextMusic()方法播放下一首音乐,具体代码如下:
- mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- nextMusic(); //播放下一首
- }
- });
(11)分别为“上一首”按钮、“播放”按钮、“暂停/继续”按钮、“停止”按钮和“下一首”按钮添加单击事件监听器,并在重写的onClick()方法中,实现播放上一首、播放、暂停/继续播放、停止播放和播放下一首音频等功能,具体代码如下:
- //为“上一首”按钮添加单击事件监听器
- pre.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- preMusic(); //播放上一首
- }
- });
- //为“播放”按钮添加单击事件监听器
- play.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- playMusic(audioList.get(currentItem)); //调用playMusic()方法播放音乐
- }
- });
- //为“暂停”按钮添加单击事件监听器
- pause.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.pause(); //暂停音频的播放
- ((Button) v).setText("继续");
- } else {
- mediaPlayer.start(); //继续播放
- ((Button) v).setText("暂停");
- }
- }
- });
- //为“停止”按钮添加单击事件监听器
- stop.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.stop(); //停止播放音频
- }
- pause.setEnabled(false); //设置“暂停”按钮不可用
- }
- });
- //为“下一首”按钮添加单击事件监听器
- next.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- nextMusic(); //播放下一首
- }
- });
(12)重写Activity的onDestroy()方法,用于在当前Activity销毁时,停止正在播放的音频,并释放MediaPlayer所占用的资源,具体代码如下:
- @Override
- protected void onDestroy() {
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.stop(); //停止音乐的播放
- }
- mediaPlayer.release(); //释放资源
- super.onDestroy();
- }
运行本实例,在屏幕中将显示获取到的音频列表,单击各列表项,可以播放当前列表项所指定的音乐;单击“播放”按钮,将开始播放音乐,并且“暂停”按钮变为可用,如图10.5所示;单击“暂停”按钮,将暂停音乐的播放,同时该按钮变为“继续”按钮;单击“停止”按钮,将停止播放音乐;单击“上一首”按钮,将播放上一首音乐;单击“下一首”按钮,将播放下一首音乐。
图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组件和一个拖动条组件,分别用于显示当前音量值和调整音量的拖动条,关键代码如下:
- <TextView
- android:id="@+id/volume"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10px"
- android:text="当前音量:" />
- <SeekBar
- android:id="@+id/seekBar1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
说明:这里的拖动条不用指定最大值和当前值,在后面的Java代码中,我们会为其指定,这样可以让拖动条的值与音量相关联。
(3)在onCreate()方法中,添加使用拖动条控制音量大小的代码。
- //获取音频管理器类的对象
- final AudioManager am = (AudioManager) MainActivity.this.getSystemService(Context.AUDIO_SERVICE);
- //设置当前调整音量只是针对媒体音乐
- MainActivity.this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
- SeekBar seekbar = (SeekBar) findViewById(R.id.seekBar1); //获取拖动条
- seekbar.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); //设置拖动条的最大值
- int progress=am.getStreamVolume(AudioManager.STREAM_MUSIC); //获取当前的音量
- seekbar.setProgress(progress); //设置拖动条的默认值为当前音量
- final TextView tv=(TextView)findViewById(R.id.volume); //获取显示当前音量的TextView组件
- tv.setText("当前音量:"+progress); //显示当前音量
- //为拖动条组件添加OnSeekBarChangeListener监听器
- seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {}
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {}
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
- tv.setText("当前音量:"+progress); //显示改变后的音量
- am.setStreamVolume(AudioManager.STREAM_MUSIC,
- progress, AudioManager.FLAG_PLAY_SOUND); //设置改变后的音量
- }
- });
说明:在上面的代码中,首先获取音频管理器类的对象,并设置当前调整音量只是针对媒体音乐进行,然后获取拖动条,并设置其最大值获取其当前值,再获取显示当前音量的TextView组件,并设置其显示内容为当前音量,最后为拖动条组件添加OnSeekBarChangeListener监听器,在重写的onProgressChanged()方法中,显示改变后的音量,并将改变后音量设置到音频管理器上,用来改变音量的大小。
运行本实例,将显示一个带音量控制的音乐播放器,单击“播放”按钮、“暂停/继续”按钮和“停止”按钮,可以播放音乐、暂停/继续和停止音乐的播放;拖动音量控制拖动条上的滑块,可以调整音量的大小,并及时显示当前音量,如图10.6所示。
图10.6 带音量控制的音乐播放器
10.2 控制相机拍照
教学录像:光盘\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。关键代码如下:
- <SurfaceView
- android:id="@+id/surfaceView1"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
(2)打开默认添加的MainActivity,在该类中,声明程序中所需的成员变量,具体代码如下:
- private Camera camera; //相机对象
- private boolean isPreview = false; //是否为预览模式
(3)设置程序为全屏运行。这里需要将下面的代码添加到onCreate()方法中默认添加的setContentView (R.layout.main);语句之前,否则不能应用全屏的效果。
- requestWindowFeature(Window.FEATURE_NO_TITLE); //设置全屏显示
(4)在onCreate()方法中,首先判断是否安装SD卡,因为拍摄的图片需要保存到SD卡上,然后获取用于显示相机预览画面的SurfaceView组件,最后通过SurfaceView对象获取SurfaceHolder对象,并设置该SurfaceHolder不维护缓冲,具体代码如下:
- /****************** 判断是否安装SD卡 *********************************/
- if (!android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED)) {
- Toast.makeText(this, "请安装SD卡!", Toast.LENGTH_SHORT).show(); //弹出消息提示框显示提示信息
- }
- /******************************************************************/
- SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView1); //获取SurfaceView组件,用于显示相机预览
- final SurfaceHolder sh = sv.getHolder(); //获取SurfaceHolder对象
- sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //设置该SurfaceHolder不维护缓冲
(5)获取布局管理器中添加的“预览”按钮,并为其添加单击事件监听器,在重写的onClick()方法中,首先判断相机是否为预览模式,如果不是,则打开相机,然后为相机设置显示预览画面的SurfaceView,并设置相机参数,最后开始预览并设置自动对焦,具体代码如下:
- Button preview = (Button) findViewById(R.id.preview); //获取“预览”按钮
- preview.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //如果相机为非预览模式,则打开相机
- if (!isPreview) {
- camera=Camera.open(); //打开相机
- }
- try {
- camera.setPreviewDisplay(sh); //设置用于显示预览的SurfaceView
- Camera.Parameters parameters = camera.getParameters(); //获取相机参数
- parameters.setPictureSize(640, 480); //设置预览画面的尺寸
- parameters.setPictureFormat(PixelFormat.JPEG); //指定图片为JPEG格式
- parameters.set("jpeg-quality", 80); //设置图片的质量
- parameters.setPictureSize(640, 480); //设置拍摄图片的尺寸
- camera.setParameters(parameters); //重新设置相机参数
- camera.startPreview(); //开始预览
- camera.autoFocus(null); //设置自动对焦
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- });
(6)获取布局管理器中添加的“拍照”按钮,并为其设置单击事件监听器,在重写的onClick()方法中,如果相机对象不为空,则调用takePicture()方法进行拍照,具体代码如下:
- Button takePhoto = (Button) findViewById(R.id.takephoto); //获取“拍照”按钮
- takePhoto.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if(camera!=null){
- camera.takePicture(null, null, jpeg); //进行拍照
- }
- }
- });
(7)实现拍照的回调接口,在重写的onPictureTaken()方法中,首先根据拍照所得的数据创建位图,然后实现一个带“保存”和“取消”按钮的对话框,用于保存所拍图片,具体代码如下:
- final PictureCallback jpeg = new PictureCallback() {
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- //根据拍照所得的数据创建位图
- final Bitmap bm = BitmapFactory.decodeByteArray(data, 0,data.length);
- //加载layout/save.xml文件对应的布局资源
- View saveView = getLayoutInflater().inflate(R.layout.save, null);
- final EditText photoName = (EditText) saveView.findViewById(R.id.phone_name);
- //获取对话框上的ImageView组件
- ImageView show = (ImageView) saveView.findViewById(R.id.show);
- show.setImageBitmap(bm); //显示刚刚拍得的照片
- camera.stopPreview(); //停止预览
- isPreview = false;
- //使用对话框显示saveDialog组件
- new AlertDialog.Builder(MainActivity.this).setView(saveView)
- .setPositiveButton("保存", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- File file = new File("/sdcard/pictures/" + photoName
- .getText().toString() + ".jpg"); //创建文件对象
- try {
- file.createNewFile(); //创建一个新文件
- //创建一个文件输出流对象
- FileOutputStream fileOS = new FileOutputStream(file);
- //将图片内容压缩为JPEG格式输出到输出流对象中
- bm.compress(Bitmap.CompressFormat.JPEG, 100, fileOS);
- fileOS.flush(); //将缓冲区中的数据全部写出到输出流中
- fileOS.close(); //关闭文件输出流对象
- isPreview = true;
- resetCamera();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- isPreview = true;
- resetCamera(); //重新预览
- }
- }).show();
- }
- };
(8)编写保存对话框所需要的布局文件,名称为save.xml,在该文件中,添加一个垂直线性布局管理器,并在该布局管理器中添加一个水平线性布局管理器(用于添加输入相片名称的文本框和编辑框)和一个ImageView组件(用于显示相片预览),具体代码请参见光盘。
(9)编写实现重新预览的方法resetCamera(),在该方法中,当isPreview变量的值为真时,调用相机的startPreview()方法开启预览,具体代码如下:
- private void resetCamera(){
- if(isPreview){
- camera.startPreview(); //开启预览
- }
- }
(10)重写Activity的onPause()方法,用于当暂停Activity时,停止预览并释放相机资源,具体代码如下:
- @Override
- protected void onPause() {
- if(camera!=null){
- camera.stopPreview(); //停止预览
- camera.release(); //释放资源
- }
- super.onPause();
- }
(11)由于本程序需要访问SD卡和控制相机,所以需要在AndroidManifest.xml文件中赋予程序访问SD卡和控制相机的权限,关键代码如下:
- <!-- 授予程序可以向SD卡中保存文件的权限 -->
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <!-- 授予程序使用摄像头的权限 -->
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-feature android:name="android.hardware.camera" />
- <uses-feature android:name="android.hardware.camera.autofocus" />
运行本实例后,单击“预览”按钮,在屏幕的右侧将显示如图10.7所示的相机预览画面,单击“拍照”按钮,即可进行拍照,并显示保存图片对话框,输入文件名(不包括扩展名),如图10.8所示,单击“保存”按钮,即可将所拍的画面保存到SD卡的pictures目录中。
图10.7 相机预览画面
图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,在该类中,创建程序中所需的成员变量,具体代码如下:
- private SoundPool soundpool; //声明一个SoundPool对象
- private HashMap<Integer, Integer> soundmap = new HashMap<Integer, Integer>(); //创建一个HashMap对象
- private ImageView rabbit;
- private int x=0; //兔子在X轴的位置
- private int y=0; //兔子在Y轴的位置
- private int width=0; //屏幕的宽度
- private int height=0; //屏幕的高度
(3)在onCreate()方法中,首先实例化SoundPool对象,并将要播放的全部音频流保存到HashMap对象中,然后获取布局管理器中添加的小兔子,并获取屏幕的宽度和高度,再计算小兔子在X轴和Y轴的位置,最后通过setX()和setY()方法设置兔子的默认位置,具体代码如下:
- soundpool = new SoundPool(5,
- AudioManager.STREAM_SYSTEM, 0); //创建一个SoundPool对象,该对象可以容纳5个音频流
- //将要播放的音频流保存到HashMap对象中
- soundmap.put(1, soundpool.load(this, R.raw.chimes, 1));
- soundmap.put(2, soundpool.load(this, R.raw.enter, 1));
- soundmap.put(3, soundpool.load(this, R.raw.notify, 1));
- soundmap.put(4, soundpool.load(this, R.raw.ringout, 1));
- soundmap.put(5, soundpool.load(this, R.raw.ding, 1));
- rabbit=(ImageView)findViewById(R.id.rabbit);
- width= MainActivity.this.getResources().getDisplayMetrics().widthPixels;
- height=MainActivity.this.getResources().getDisplayMetrics().heightPixels;
- x=width/2-44; //计算兔子在X轴的位置
- y=height/2-35; //计算兔子在Y轴的位置
- rabbit.setX(x); //设置兔子在X轴的位置
- rabbit.setY(y); //设置兔子在Y轴的位置
(4)重写键盘的按键被按下的onKeyDown()方法,在该方法中,应用switch()语句分别为上、下、左、右方向键和其他按键指定不同的按键音,同时,在按下上、下、左和右方向键时,还会控制小兔子在相应方向上移动,具体代码如下:
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch(keyCode){
- case KeyEvent.KEYCODE_DPAD_LEFT: //向左方向键
- soundpool.play(soundmap.get(1), 1, 1, 0, 0, 1); //播放指定的音频
- if(x>0){
- x-=10;
- rabbit.setX(x); //移动小兔子
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT: //向右方向键
- soundpool.play(soundmap.get(2), 1, 1, 0, 0, 1); //播放指定的音频
- if(x<width-88){
- x+=10;
- rabbit.setX(x); //移动小兔子
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP: //向上方向键
- soundpool.play(soundmap.get(3), 1, 1, 0, 0, 1); //播放指定的音频
- if(y>0){
- y-=10;
- rabbit.setY(y); //移动小兔子
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN: //向下方向键
- soundpool.play(soundmap.get(4), 1, 1, 0, 0, 1); //播放指定的音频
- if(y
- <height-70){
- y+=10;
- rabbit.setY(y); //移动小兔子
- }
- break;
- default:
- soundpool.play(soundmap.get(5), 1, 1, 0, 0, 1); //播放默认按键音
- }
- return super.onKeyDown(keyCode, event);
- }
(5)在res目录下,创建一个menu子目录,并在该目录中创建一个名称为setting.xml的菜单资源文件,在该文件中,添加一个控制是否播放背景音乐的多选菜单组,默认为选中状态,setting.xml文件具体代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <menu xmlns:android="http://schemas.android.com/apk/res/android" >
- <group android:id="@+id/setting" android:checkableBehavior="all">
- <item android:id="@+id/bgsound" android:title="播放背景音乐" android:checked="true"></item>
- </group>
- </menu>
(6)重写onCreateOptionsMenu()方法,应用步骤(5)中添加的菜单文件,创建一个选项菜单,并重写onOptionsItemSelected()方法,对菜单项的选取状态进行处理,主要用于根据菜单项的选取状态控制是否播放背景音乐。具体代码如下:
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater=new MenuInflater(this); //实例化一个MenuInflater对象
- inflater.inflate(R.menu.setting, menu); //解析菜单文件
- return super.onCreateOptionsMenu(menu);
- }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if(item.getGroupId()==R.id.setting){ //判断是否选择了参数设置菜单组
- if(item.isChecked()){ //若菜单项已经被选中
- item.setChecked(false); //设置菜单项不被选中
- Music.stop(this);
- }else{
- item.setChecked(true); //设置菜单项被选中
- Music.play(this, R.raw.jasmine);
- }
- }
- return true;
- }
(7)编写Music类,在该类中,首先声明一个MediaPlayer对象,然后编写用于播放背景音乐的play()方法,最后编写用于停止播放背景音乐的stop()方法,关键代码如下:
- public class Music {
- private static MediaPlayer mp = null; //声明一个MediaPlayer对象
- public static void play(Context context, int resource) {
- stop(context);
- if (SettingsActivity.getBgSound(context)) { //判断是否播放背景音乐
- mp = MediaPlayer.create(context, resource);
- mp.setLooping(true); //是否循环播放
- mp.start(); //开始播放
- }
- }
- public static void stop(Context context) {
- if (mp != null) {
- mp.stop(); //停止播放
- mp.release(); //释放资源
- mp = null;
- }
- }
- }
说明:在上面的代码中,加粗的代码SettingsActivity.getBgSound(context)用于获取选项菜单存储的首选值,这样可以实现通过选项菜单控制是否播放背景音乐。
(8)编写SettingsActivity类,该类继承PreferenceActivity类,用于实现自动存储首选项的值。在SettingsActivity类中,首先重写onCreate()方法,在该方法中调用addPreferencesFromResource()方法加载首选项资源文件,然后编写获取是否播放背景音乐的首选项的值的getBgSound()方法,在该方法中返回获取到的值,关键代码如下:
- public class SettingsActivity extends PreferenceActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.setting);
- }
- //获取是否播放背景音乐的首选项的值
- public static boolean getBgSound(Context context){
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean("bgsound",true);
- }
- }
说明:PreferenceActivity类用于实现对程序设置参数的存储。在该Activity中,设置参数的存储是完全自动的,不需要手动保存,非常方便。
(9)在res目录下,创建一个xml目录,在该目录中添加一个名称为setting.xml的首选项资源文件,具体代码如下:
- <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
- <CheckBoxPreference
- android:key="bgsound"
- android:title="播放背景音乐"
- android:summary="选中为播放背景音乐"
- android:defaultValue="true"/>
- </PreferenceScreen>
(10)在MainActivity中,重写onPause()方法,在该方法中,调用Music类的stop()方法停止播放背景音乐,具体代码如下:
- @Override
- protected void onPause() {
- Music.stop(this); //停止播放背景音乐
- super.onPause();
- }
(11)在MainActivity中,重写onResume()方法,在该方法中,调用Music类的play()方法开始播放背景音乐,具体代码如下:
- @Override
- protected void onResume() {
- Music.play(this, R.raw.jasmine); //播放背景音乐
- super.onResume();
- }
运行本实例,将显示如图10.9所示的运行结果。
图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组件,用于播放开场动画视频文件,关键代码如下:
- <VideoView
- android:id="@+id/video"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
(3)创建一个名称为StartActivity的Activity,并重写其onCreate()方法,在该方法中,首先获取VideoView组件,并获取要播放的文件对应的URI,然后为VideoView组件指定要播放的视频,并让其获得焦点,再调用start()方法开始播放视频,最后为VideoView添加完成事件监听器,在重写的onCompletion()方法中调用startMain()方法进入到游戏主界面,具体代码如下:
- video = (VideoView) findViewById(R.id.video); //获取VideoView组件
- Uri uri = Uri.parse("android.resource://com.mingrisoft/"+R.raw.mingrisoft); //获取要播放的文件对应的URI
- video.setVideoURI(uri); //指定要播放的视频
- video.requestFocus(); //让VideoView获得焦点
- try {
- video.start(); //开始播放视频
- } catch (Exception e) {
- e.printStackTrace(); //输出异常信息
- }
- //为VideoView添加完成事件监听器
- video.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- startMain(); //进入游戏主界面
- }
- });
(4)编写进入游戏主界面的startMain()方法,在该方法中创建一个新的Intent,以启动游戏主界面的Activity,具体代码如下:
- //进入游戏主界面
- private void startMain(){
- Intent intent = new Intent(StartActivity.this, MainActivity.class); //创建Intent
- startActivity(intent); //启动新的Activity
- StartActivity.this.finish(); //结束当前Activity
- }
(5)打开AndroidManifest.xml文件,在该文件中配置项目中应用的Activity。这里首先将主Activity设置为StartActivity,然后再配置MainActivity,关键代码如下:
- <activity
- android:label="@string/app_name"
- android:name=".StartActivity" >
- <intent-filter >
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity android:name=".MainActivity"/>
运行本实例,首先播放指定的视频,视频播放完毕后,将进入到如图10.10所示的游戏主界面。
图10.10 游戏主界面
10.4 小 结
本章主要介绍了在Android中,如何播放音频与视频,以及如何控制相机拍照等内容。需要重点说明的是两种播放音频方法的区别。本章共介绍了两种播放音频的方法,一种是使用MediaPlayer播放,另一种是使用SoundPool播放。这两种方法的区别是:使用MediaPlayer每次只能播放一个音频,适用于播放长音乐或是背景音乐;使用SoundPool可以同时播放多个短小的音频,适用于播放按键音或者消息提示音等,希望读者根据实际情况选择合适的方法。
10.5 实践与练习
编写Android项目,使用MediaPlayer和SurfaceView实现带音量控制的视频播放器。(答案位置:光盘\TM\sl\10\10.10)
编写Android项目,实现控制是否播放按键音。(答案位置:光盘\TM\sl\10\10.11)