Android学习记录——8.多媒体

文章目录

  • 1.将程序运行到手机上
  • 2.使用通知
    • 2.1 通知的基本用法
    • 2.2 通知的进阶技巧
    • 2.3 通知的高级功能
  • 3.调用摄像头和相册
    • 3.1 调用摄像头拍照
    • 3.2 从相册中选择图片
  • 4.播放多媒体文件
    • 4.1 播放音频
    • 4.2 播放视频

1.将程序运行到手机上

前面的一些内容,我们一直都是使用模拟器来运行程序的,不过本篇博客涉及的一些功能必须要在真正的Android手机上运行才看得到效果。因此,首先我们就来学习一下,如何使用Android 手机来运行程序。
不必我多说,首先你需要拥有一部Android手机。现在Android手机早就不是什么稀罕物, 几乎已经是人手一部了,如果你还没有的话,赶紧去购买吧。
想要将程序运行到手机上,我们需要先通过数据线把手机连接到电脑上。然后进入到设置-> 开发者选项界面,并在这个界面中勾选中USB调试选项(每部手机可能因为配置不同而选项各异,具体可参考网上)
注意从Android 4.2系统开始,开发者选项默认是隐藏的,你需要先进入到“关于手机”界 面,然后对着最下面的版本号那一栏连续点击,就会让开发者选项显示出来。
然后如果你使用的是Windows操作系统,还需要在电脑上安装手机的驱动。一般借助360 手机助手或豌豆荚等工具都可以快速地进行安装,安装完成后就可以看到手机已经连接到电脑上了。
现在观察Android Monitor,你会发现当前是有两个设备在线的,一个是我们一直使用的模拟器,另外一个则是刚刚连接上的手机了。
然后运行一下当前项目,这时不会直接将程序运行到模拟器或者手机上,而是会弹出一个对话框让你进行选择。这时候选择已经连接上的手机即可。

2.使用通知

通知(Notification)是Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知来实现。发出一条通知后, 手机最上方的状态栏中会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。Android的通知功能获得了大量用户的认可和喜爱,就连iOS系统也在5.0版本之后加入了类似的功能。

2.1 通知的基本用法

了解了通知的基本概念,下面我们就来看一下通知的使用方法吧。通知的用法还是比较灵活的,既可以在活动里创建,也可以在广播接收器里创建,当然还可以在后面我们即将学习的服务里创建。相比于广播接收器和服务,在活动里创建通知的场景还是比较少的,因为一般只有当程序进入到后台的时候我们才需要使用通知。
不过,无论是在哪里创建通知,整体的步骤都是相同的,下面我们就来学习一下创建通知的详细步骤。首先需要一个NotificationManager来对通知进行管理,可以调用Context的getSystemService()方法获取到。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入 Context.NOTIFICATION_SERVICE 即可。因此,获取 NotificationManager 的实例就可以写成:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

接下来需要使用一个Builder构造器来创建Notification对象,但问题在于,几乎Android系统的每一个版本都会对通知这部分功能进行或多或少的修改,API不稳定性问题在通知上面突显得尤其严重。那么该如何解决这个问题呢?其实解决方案我们之前已经见过好几回了,就是使用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用这个类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作了,代码如下所示:

Notification notification = new NotificationCompat.Builder(context).build();

当然,上述代码只是创建了一个空的Notification对象,并没有什么实际作用,我们可以 在最终的build()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,先来看一些最基本的设置:

Notification notification = new NotificationCompat.Builder(context)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawab'le. smallicon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.largeicon))
.build();

上述代码中一共调用了 5个设置方法,下面我们来一一解析一下。setContentTitle()方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。setContentText()方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。setWhen()方法用于指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知 上。setSmalllcon()方法用于设置通知的小图标,注意只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。setLargeicon ()方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。
以上工作都完成之后,只需要调用NotificationManager的notify()方法就可以让通知显示出来了。notify()方法接收两个参数,第一个参数是id,要保证为每个通知所指定的id都是不同的。第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification对象传入即可。因此,显示一个通知就可以写成:

manager.notify(l, notification);

到这里就已经把创建通知的每一个步骤都分析完了,下面就让我们通过一个具体的例子来看一看通知到底是长什么样的。
新建一个项目,并修改activity_main.xml中的代码,如下所示:

<?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">
    <Button
        android:id="@+id/send_notice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送通知"/>
</LinearLayout>

布局文件非常简单,里面只有一个按钮,用于发岀一条通知。接下来修改 MainActivity中的代码,如下所示:

package com.example.administrator.notificationtest;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.NotificationCompat;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendNotice = (Button)findViewById(R.id.send_notice);
        sendNotice.setOnClickListener(this);
    }

    @Override
    public void onClick(View v){
     
        switch (v.getId()){
     
            case R.id.send_notice:
                NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
                Notification notification = new NotificationCompat.Builder(this)
                        .setContentTitle("这是一个通知")
                        .setContentText("这确实是一个通知")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                        .build();
                manager.notify(1,notification);
                break;
            default:
                break;
        }
    }
}

可以看到,我们在按钮的点击事件里面完成了通知的创建工作,创建的过程正如前面所描述的一样。不过这里简单起见,我将通知栏的大小图都直接设置成了ic_launcher这张图,这样就不用再去专门准备图标了,而在实际项目中千万不要这样偷懒。
现在可以来运行一下程序了,点击按钮,你会在系统状态栏的最左边看到一个小图标,如图所示:
在这里插入图片描述
如果你使用过Android手机,此时应该会下意识地认为这条通知是可以点击的。但是当你去点击它的时候,你会发现没有任何效果。不对啊,好像每条通知点击之后都应该会有反应的呀? 其实要想实现通知的点击效果,我们还需要在代码中进行相应的设置,这就涉及了一个新的概念: Pendingintent。
Pendingintent从名字上看起来就和Intent有些类似,它们之间也确实存在着不少共同点。比如它们都可以去指明某一个“意图”,都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而Pendingintent更加倾向于在某个合适的时机去执行某个动作。所以,也可以把Pendingintent简单地理解为延迟执行的Intent。
Pendingintent的用法同样很简单,它主要提供了几个静态方法用于获取Pendingintent的实例, 可以根据需求来选择是使用getActivity()方法、getBroadcast ()方法,还是getService()方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context,不用多做解释。第二 个参数一般用不到,通常都是传入0即可。第三个参数是一个Intent对象,我们可以通过这个对象构建出Pendingintent的“意图”。第四个参数用于确定Pendingintent的行为,有FLAG_ONE_SHOT、FLAG_NO_CREATE. FLAG_CANCEL_CURRENT 和 FLAG_UPDATE_CURRENT这 4 种值可选,每种值的具体含义你可以查看文档,通常情况下这个参数传入0就可以了。
对Pendingintent有了一定的了解后,我们再回过头来看一下NotificationCompat.Builder。这个构造器还可以再连缀一个setContentlntent()方法,接收的参数正是一个 Pendingintent对象。因此,这里就可以通过Pendingintent构建出一个延迟执行的“意图”,当 用户点击这条通知时就会执行相应的逻辑。
现在我们来优化一下这个项目,给刚才的通知加上点击功能,让用户点击它的时 候可以启动另一个活动。
首先需要准备好另一个活动,右击 《项目名》——>New——>Activity——>Empty Activity,新建 NotificationActivity,布局起名为 notification_layout。然后修改 notification layout.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="24sp"
        android:text="这是一个Notification布局"/>
</RelativeLayout>·

这样就把NotificationActivity这个活动准备好了,下面我们修改MainActivity中的代码,给通知加入点击功能,如下所示:

package com.example.administrator.notificationtest;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.NotificationCompat;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendNotice = (Button)findViewById(R.id.send_notice);
        sendNotice.setOnClickListener(this);
    }

    @Override
    public void onClick(View v){
     
        switch (v.getId()){
     
            case R.id.send_notice:
                Intent intent = new Intent(this,NotificationActivity.class);
                PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
                NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
                Notification notification = new NotificationCompat.Builder(this)
                        .setContentTitle("这是一个通知")
                        .setContentText("这确实是一个通知")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                        .setContentIntent(pi)
                        .build();
                manager.notify(1,notification);
                break;
            default:
                break;
        }
    }
}

可以看到,这里先是使用Intent表达出我们想要启动NotificationActivity的“意图”,然后将构建好的Intent对象传入到Pendingintent的getActivity()方法里,以到Pendingintent的实例,接着在 NotificationCompat.Builder 中调用 setcontentintent()方法,把它作为参数传入即可。
现在重新运行一下程序,并点击按钮,依旧会发出一条通知。然后下拉系统状态栏,点击一下该通知,就会看到NotificationActivity这个活动的界面了,如图所示:
Android学习记录——8.多媒体_第1张图片
咦?怎么系统状态上的通知图标还没有消失呢?是这样的,如果我们没有在代码中对该通知进 行取消,它就会一直显示在系统的状态栏上。解决的方法有两种,一种是在NotificationCompat.Builder中再连缀一个setAutoCancel()方法,一种是显式地调用NotificationManager的cancel()方法将它取消,两种方法我们都学习一下。
第一种写法如下:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setAutoCancel(true)
.build();

可以看到,setAutoCancel()方法传入true,就表示当点击了这个通知的时候,通知会自动取消掉。
第二种方法写法如下:

public class NotificationActivity extends AppCompatActivity {
     

@Override
protected void onCreate(Bundle savedlnstanceState) {
     
    super.onCreate(savedlnstanceState); 
    setContentView(R.layout.notificationlayout);
    NotificationManager manager = (NotificationManager) getSystemService
(NOTIFICATION_SERVICE); 
    manager.cancel(1);
    }
}   

这里我们在cancel()方法中传入了1,这个1是什么意思呢?还记得在创建通知的时候给每条通知指定的id吗?当时我们给这条通知设置的id就是1。因此,如果你想取消哪条通知,在cancel ()方法中传入该通知的id就行了。

2.2 通知的进阶技巧

现在你已经掌握了创建和取消通知的方法,并且知道了如何去响应通知的点击事件。不过通知的用法并不仅仅是这些呢,下面我们就来探究一下通知的更多技巧。
上一小节中创建的通知属于最基本的通知,实际上,NotificationCompat.Builder中提 供了非常丰富的API来让我们创建出更加多样的通知效果。当然,每一个API都详细地讲一遍不太可能,我们只能从中选一些比较常用的API来进行学习。先来看看setSound()方法吧,它可以在通知发出的时候播放一段音频,这样就能够更好地告知用户有通知到来。setSound ()方法接收一个参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。比如说, 每个手机的/system/media/audio/ringtones目录下都有很多的音频文件,我们可以从中随便选一个音频文件,那么在代码中就可以这样指定:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
.build();

除了允许播放音频外,我们还可以在通知到来的时候让手机进行振动,使用的是vibrate这个属性。它是一个长整型的数组,用于设置手机静止和振动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时长,以此类推。所以,如果想要让手机在通知到来的时候立刻振动1秒,然后静止1秒,再振动1秒,代码就可以写成:

Notification notification = new NotificationCompat.Builder(this)
.setVibrate(new long[] {
     0, 1000, 1000, 1006 ))
.build();

不过,想要控制手机振动还需要声明权限。因此,我们还得编辑AndroidManifest.xml文件, 加入如下声明:
不过,想要控制手机振动还需要声明权限。因此,我们还得编辑AndroidManifest.xml文件, 加入如下声明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.notificationtest"
android :versionCode="l"
android:versionName="1.0" >
<uses-peemission android:name="android.permission.VIBRATE" />
</manifest>

学会了控制通知的声音和振动,下面我们来看一下如何在通知到来时控制手机LED灯的显示。
现在的手机基本上都会前置一个LED灯,当有未接电话或未读短信,而此时手机又处于锁屏状态时,LED灯就会不停地闪烁,提醒用户去查看。我们可以使用setLights()方法来实现 这种效果,setLights()方法接收3个参数,第一个参数用于指定LED灯的颜色,第二个参数用于指定LED灯亮起的时长,以毫秒为单位,第三个参数用于指定LED灯暗去的时长,也是以毫秒为单位。所以,当通知到来时,如果想要实现LED灯以绿色的灯光一闪一闪的效果,就可 以写成:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setLights(Color.GREEN, 1000, 1000)
.build();

当然,如果你不想进行那么多繁杂的设置,也可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何振动,写法如下:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();

注意,以上所涉及的这些进阶技巧都要在手机上运行才能看得到效果,模拟器是无法表现出振动以及LED灯闪烁等功能的。

2.3 通知的高级功能

继续观察NotificationCompat.Builder这个类,你会发现里面还有很多API是我们没有 使用过的。那么下面我们就来学习一些更加强大的API的用法,从而构建岀更加丰富的通知效果。
先来看看setStyle()方法,这个方法允许我们构建出富文本的通知内容。也就是说通知中不光可以有文字和图标,还可以包含更多的东西。setStyle()方法接收一个NotificationCompat.Style 参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。
在开始使用setStyle()方法之前,我们先来做一个试验吧,之前的通知内容都比较短,如果设置成很长的文字会是什么效果呢?比如这样写:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setContentText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android.")
.build();

现在重新运行程序并触发通知,可以看到,通知内容是无法显示完整的,多余的部分会用省略号来代替。其实这也很正常, 因为通知的内容本来就应该言简意赅,详细内容放到点击后打开的活动当中会更加合适。
但是如果你真的非常需要在通知当中显示一段长文字,Android也是支持的,通过setStyle()方法就可以做到,具体写法如下:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android"))
.build();

我们在 setStyle()方法中创建了一个 NotificationCompat.BigTextStyle 对象,这个对 象就是用于封装长文字信息的,我们调用它的bigText()方法并将文字内容传入就可以了。
再次重新运行程序并触发通知,文字就会显示正常了
除了显示长文字之外,通知里还可以显示一张大图片,具体用法也是基本相似的:

Notification notification = new NotificationCompat.Builder(this)
 ...
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture
(BitmapFactory.decodeResource(getResources(), R.drawable・big_image))) 
.build(); 

可以看到,这里仍然是调用的setStyle()方法,这次我们在参数中创建了一个 NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它 的bigPicture()方法并将图片传入。这里我事先准备好了一张图片,通过BitmapFactory的decodeResource()方法将图片解析成Bitmap对象,再传入到bigPicture()方法中就可以了。
现在重新运行一下程序并触发通知,效果如图所示:
Android学习记录——8.多媒体_第2张图片
这样我们就把setstyle ()方法中的重要内容基本都掌握了。
接下来再学习一下setPriority()方法,它可以用于设置通知的重要程度。setPriority()方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:PRIORITY,_DEFAULT表示默认的重要程度,和不设置效果是一样的;PRIORITY_MIN表示最低的重要程度, 系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;PRIORITY_LOW表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之 后;PRIORITY_HIGH表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序, 将其排在比较靠前的位置;PRIORITY_MAX表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应操作。具体写法如下:

Notification notification = new NotificationCompat.Builder(this...
.setPriority(NotificationCompat.PRIORITY_MAX)
.build(); 

这里我们将通知的重要程度设置成了最高,表示这是一条非常重要的通知,要求用户必须立刻看到。现在重新运行一下程序,并点击Send notice按钮,效果如图所示:
在这里插入图片描述
可以看到,这次的通知不是在系统状态栏显示一个小图标了,而是弹出了一个横幅,并附带了通知的详细内容,表示这是一条非常重要的通知。不管用户现在是在玩游戏还是看电影, 这条通知都会显示在最上方,以此引起用户的注意。当然,使用这类通知时一定要小心,确保你的通知内容的确是至关重要的,不然如果让用户产生反感的话,很可能会导致我们的应用程序被卸载。

3.调用摄像头和相册

我们平时在使用QQ或微信的时候经常要和别人分享图片,这些图片可以是用手机摄像头拍的,也可以是从相册中选取的。类似这样的功能实在是太常见了,几乎在每一个应用程序中都会有,那么本节我们就学习一下调用摄像头和相册方面的知识。

3.1 调用摄像头拍照

先来看看摄像头方面的知识,现在很多的应用都会要求用户上传一张图片来作为头像,这时打开摄像头拍张照是最简单快捷的。下面就让我们通过一个例子来学习一下,如何才能在应用程 序里调用手机的摄像头进行拍照。
新建一个项目,然后修改activity_main.xml中的代码,如下所示:

<?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">

    <Button
        android:id="@+id/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照"/>
    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>

可以看到,布局文件中只有两个控件,一个Button和一个ImageView。Button是用于打开摄像头进行拍照的,而ImageView则是用于将拍到的图片显示出来。
然后开始编写调用摄像头的具体逻辑,修改MainActivity中的代码,如下所示:

package com.example.administrator.cameraalbumtest;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
     
    public static final int TAKE_PHOTO = 1;
    private ImageView picture;
    private Uri imageUri;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = (Button)findViewById(R.id.take_photo);
        picture = (ImageView)findViewById(R.id.picture);
        takePhoto.setOnClickListener(new View.OnClickListener() {
     
            @Override
            public void onClick(View v) {
     
                File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
                try{
     
                    if(outputImage.exists()){
     
                        outputImage.delete();
                    }
                    outputImage.createNewFile();
                }catch (IOException e){
     
                    e.printStackTrace();
                }
                if(Build.VERSION.SDK_INT >= 24){
     
                    imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
                }else {
     
                    imageUri = Uri.fromFile(outputImage);
                }
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
                startActivityForResult(intent,TAKE_PHOTO);
            }
        });


    @Override
    protected void onActivityResult(int requestCode,int resultCode,Intent data){
     
        switch (requestCode){
     
            case TAKE_PHOTO:
                if(resultCode == RESULT_OK){
     
                    try{
     
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        picture.setImageBitmap(bitmap);
                    }catch (FileNotFoundException e){
     
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                if(requestCode == RESULT_OK){
     
                    if(Build.VERSION.SDK_INT >= 19){
     
                        handleImageOnKitKat(data);/* 4.4及以上系统使用这个方法处理图片 */
                    }else{
     
                        handleImageBeforeKitKat(data);/* 4.4及以下系统使用这个方法处理图片 */
                    }
                }
                break;
            default:
                break;
        }
    }

上述代码稍微有点复杂,我们来仔细地分析一下。在MainActivity中要做的第一件事自然是分别获取到Button和ImageView的实例,并给Button注册上点击事件,然后在Button的点击事件里开始处理调用摄像头的逻辑,我们重点看一下这部分代码。
首先这里创建了一个File对象,用于存放摄像头拍下的图片,这里我们把图片命名为output_image.jpg,并将它存放在手机SD卡的应用关联缓存目录下。什么叫作应用关联缓存目录呢?就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法可以得到这个目录,具体的路径是/sdcard/Android/data//cache。那么为什么要使用应用关联缓目录来存放图片呢?因为从Android6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。
接着会进行一个判断,如果运行设备的系统版本低于Android7.0,就调用Uri的fromFile()方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。否则,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。getUriForFile()方法接收3个参数,第一个参数要求传入Context对象,第二个参数可以是任意唯一的字符串,第三个参数则是我们刚刚创建的File对象。之所以要进行这样一层转换,是因为从Android 7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而FileProvider则是一种特殊的内容提供器,它使用了和内 提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。
接下来构建出了一个Intent对象,并将这个Intent的action指定为android.media. action.IMAGE_CAPTURE,再调用Intent的putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象,最后调用startActivityForResult()来启动活动。由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的活动去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中。
注意,刚才我们是使用startActivityForResult()来启动活动的,因此拍完照后会有结果返回到onActivityResult()方法中。如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到Imagelew中显示出来。
不过现在还没结束,刚才提到了内容提供器,那么我们自然要在AndroidManifest.xml中对内容提供器进行注册了,如下所示:

<provider
    android:authorities="com.example.cameraalbumtest.fileprovider"
    android:name="android.support.v4.content.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>

其中,android:name属性的值是固定的,android:authorities属性的值必须要和刚才 FileProvider.getUriForFile()方法中的第二个参数一致。另外,这里还在标签的内部使用来指定Uri的共享路径,并引用了一个@xml/file_paths资源。当然,,这个资源现在还是不存在的,下面我们就来创建它。
右击res目录——>New——>Directory,创建一个xml目录,接着右击xml目录一>New—>File,创建一个file_paths.xml文件。然后修改file_paths.xml文件中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<path xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path=""/>
</path>

其中,external-path就是用来指定Uri共享的,name属性的值可以随便填,path属性的值表示共享的具体路径。这里设置空值就表示将整个SD卡进行共享,当然你也可以仅共享我们存放output_image.jpg这张图片的路径。
另外还有一点要注意,在Android4.4系统之前,访问SD卡的应用关联目录也是要声明权限的,从4.4系统开始不再需要权限声明。那么我们为了能够兼容老版本系统的手机,还需要在 AndroidManifest.xml中声明一下访问SD卡的权限:

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

这样代码就都编写完了,现在将程序运行到手机上,然后点击拍照按钮就可以进行拍照了,如图所示:
Android学习记录——8.多媒体_第3张图片
拍照完成后,点击中间按钮就会回到我们程序的界面。同时,拍摄的照 片也会显示出来了,如图所示:
Android学习记录——8.多媒体_第4张图片

3.2 从相册中选择图片

虽然调用摄像头拍照既方便又快捷,但我们并不是每次都需要去当场拍一张照片的。因为每个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照片会比打开相机拍一张照片更加常用。一个优秀的应用程序应该将这两种选择方式都提供给用户,由用户来决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择照片的功能。
还是在该项目的基础上进行修改,编辑activity_main.xml文件,在布局中添加一个按钮用于从相册中选择照片,代码如下所示:

<?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">

    <Button
        android:id="@+id/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照"/>
    <Button
        android:id="@+id/choose_from_album"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="从相册里选择一张照片"/>
    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>

然后修改MainActivity中的代码,加入从相册选择照片的逻辑,代码如下所示:

package com.example.administrator.cameraalbumtest;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
     
    public static final int TAKE_PHOTO = 1;
    public static final int CHOOSE_PHOTO = 2;
    private ImageView picture;
    private Uri imageUri;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = (Button)findViewById(R.id.take_photo);
        Button chooseFromAlbum = (Button)findViewById(R.id.choose_from_album);
        picture = (ImageView)findViewById(R.id.picture);
        takePhoto.setOnClickListener(new View.OnClickListener() {
     
            @Override
            public void onClick(View v) {
     
                File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
                try{
     
                    if(outputImage.exists()){
     
                        outputImage.delete();
                    }
                    outputImage.createNewFile();
                }catch (IOException e){
     
                    e.printStackTrace();
                }
                if(Build.VERSION.SDK_INT >= 24){
     
                    imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
                }else {
     
                    imageUri = Uri.fromFile(outputImage);
                }
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
                startActivityForResult(intent,TAKE_PHOTO);
            }
        });
        chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
     
            @Override
            public void onClick(View v) {
     
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
     
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{
     Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else{
     
                    openAlbum();
                }
            }
        });
    }
    private void openAlbum(){
     
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent,CHOOSE_PHOTO);
    }
    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
     
        switch (requestCode){
     
            case 1:
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
     
                    openAlbum();
                }else{
     
                    Toast.makeText(this,"你拒绝了授权",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }

    @Override
    protected void onActivityResult(int requestCode,int resultCode,Intent data){
     
        switch (requestCode){
     
            case TAKE_PHOTO:
                if(resultCode == RESULT_OK){
     
                    try{
     
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        picture.setImageBitmap(bitmap);
                    }catch (FileNotFoundException e){
     
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                if(requestCode == RESULT_OK){
     
                    if(Build.VERSION.SDK_INT >= 19){
     
                        handleImageOnKitKat(data);/* 4.4及以上系统使用这个方法处理图片 */
                    }else{
     
                        handleImageBeforeKitKat(data);/* 4.4及以下系统使用这个方法处理图片 */
                    }
                }
                break;
            default:
                break;
        }
    }

    @TargetApi(19)
    private void handleImageOnKitKat(Intent data){
     
        String imagePath = null;
        Uri uri = data.getData();
        if(DocumentsContract.isDocumentUri(this,uri)){
     
            String docId = DocumentsContract.getDocumentId(uri);
            if("com.android.providers.media.documents".equals(uri.getAuthority())){
     
                String id = docId.split(":")[1];
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
            }else if ("com.android.provideres.downloads.documents".equals(uri.getAuthority())){
     
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                imagePath = getImagePath(contentUri,null);
            }
        }else if ("content".equalsIgnoreCase(uri.getScheme())){
     
            imagePath = getImagePath(uri,null);
        }else if ("file".equalsIgnoreCase(uri.getScheme())){
     
            imagePath = uri.getPath();
        }
        displayImage(imagePath);
    }
    private void handleImageBeforeKitKat(Intent data){
     
        Uri uri = data.getData();
        String imagePath = getImagePath(uri,null);
        displayImage(imagePath);
    }
    private String getImagePath(Uri uri,String selection){
     
        String path = null;
        Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
        if(cursor != null){
     
            if(cursor.moveToFirst()){
     
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }

    private void displayImage(String imagePath){
     
        if(imagePath != null){
     
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            picture.setImageBitmap(bitmap);
        }else{
     
            Toast.makeText(this,"获取图片失败",Toast.LENGTH_SHORT).show();
        }
    }
}

可以看到,在Choose From Album按钮的点击事件里我们先是进行了一个运行时权限处理, 动态申请WRITE_EXTERNAL_STORAGE这个危险权限。为什么需要申请这个权限呢?因为相册中的照片都是存储在SD卡上的,我们要从SD卡中读取照片就需要申请这个权限。WRITE_ EXTERNAL_STORAGE表示同时授予程序对SD卡读和写的能力。
当用户授权了权限申请之后会调用openAlbum()方法,这里我们先是构建出了一个Intent 对象,并将它的action 指定为 android. intent. action.GET_CONTENT。接着给这个 Intent 对象设置一些必要的参数,然后调用startActivityForResult()方法就可以打开相册程序选择照片了。注意在调用startActivityForResult()方法的时候,我们给第二个参数传入的值变成了 CH00SE_PHOTO,这样当从相册选择完图片回到onActivityResult ()方法时,就会进入 CH00SE_PH0TO的case来处理图片。接下来的逻辑就比较复杂了,首先为了兼容新老版本的手机,我们做了一个判断,如果是4.4及以上系统的手机就调用handlelmageOnKitKat ()方法来处理图片,否则就调用handleImageBeforeKitKat()方法来处理图片。之所以要这样做,是因为Android系统从4.4版本开始,选取相册中的图片不再返回图片真实的Uri 了,而是一个封装过的Uri,因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。
那么handlelmageOnKitKat()方法中的逻辑就基本是如何解析这个封装过的Uri 了。这里有好几种判断情况,如果返回的Uri是document类型的话,那就取岀document id进行处理,如果不是的话,那就使用普通的方式处理。另外,如果Uri的authority是media格式的话,document id还需要再进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字id。取出的id用于构建新的Uri和条件语句,然后把这些值作为参数传入到getlmagePath()方法当中, 就可以获取到图片的真实路径了。拿到图片的路径之后,再调用displaylmage()方法将图片显 示到界面上。
相比于 handlelmageOnKitKat()方法,handlelmageBeforeKitKat ()方法中的逻辑就要简单得多了,因为它的Uri是有封装过的,不需要任何解析,直接将Uri传入到getlmagePath()方法当中就能获取到图片的真实路径了,最后同样是调用displaylmage()方法来让图片显示到界面上。
现在将程序重新运行到手机上,然后点击一下从相册里选择一张图片按钮,首先会弹岀权限申请框,如图所示:
Android学习记录——8.多媒体_第5张图片
点击允许之后就会打开手机相册,如图所示:
Android学习记录——8.多媒体_第6张图片

然后随意选择一张照片,回到我们程序的界面,选中的照片应该就会显示出来了。
调用摄像头拍照以及从相册中选择照片是很多Android应用都会带有的功能,现在你已经将这两种技术都学会了,将来在工作中如果需要开发类似的功能,相信你一定能轻松完成的。不过目前我们的实现还不算完美,因为某些照片即使经过裁剪后体积仍然很大,直接加载到内存中有可能会导致程序崩溃。更好的做法是根据项目的需求先对照片进行适当的压缩,然后再加载到内存中。至于如何对照片进行压缩,就要考验你查阅资料的能力了,这里就不再展开进行讲解了。

4.播放多媒体文件

手机上最常见的休闲方式毫无疑问就是听音乐和看电影了,随着移动设备的普及,越来越多的人都可以随时享受优美的音乐,以及观看精彩的电影。而Android在播放音频和视频方面也是做了相当不错的支持,它提供了一套较为完整的API,使得开发者可以很轻松地编写岀一个简易的音频或视频播放器,下面我们就来具体地学习一下。

4.1 播放音频

在Android中播放音频文件一般都是使用MediaPlayer类来实现的,它对多种格式的音频文件提供了非常全面的控制方法,从而使得播放音乐的工作变得十分简单。下表列出了MediaPlayer类中一些较为常用的控制方法。
Android学习记录——8.多媒体_第7张图片
简单了解了上述方法后,我们再来梳理一下MediaPlayer的工作流程。首先需要创建岀一个MediaPlayer对象,然后调用setDataSource()方法来设置音频文件的路径,再调用 prepare()方法使MediaPlayer进入到准备状态,接下来调用start()方法就可以开始播放音频,调用pause()方法就会暂停播放,调用reset()方法就会停止播放。
下面就让我们通过一个具体的例子来学习一下吧,新建一个项目,然后修改 activity_main.xml中的代码,如下所示:

<?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">

    <Button
        android:id="@+id/play"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="播放"/>
    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="暂停"/>
    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止"/>
</LinearLayout>

布局文件中放置了 3个按钮,分别用于对音频文件进行播放、暂停和停止操作。然后修改 MainActivity中的代码,如下所示:

package com.example.administrator.playaudiotest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
     

    private MediaPlayer mediaPlayer = new MediaPlayer();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button play = (Button)findViewById(R.id.play);
        Button pause = (Button)findViewById(R.id.pause);
        Button stop = (Button)findViewById(R.id.stop);
        play.setOnClickListener(this);
        pause.setOnClickListener(this);
        stop.setOnClickListener(this);
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
     
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{
     Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }else{
     
            initMediaPlayer();
        }
    }
    private void initMediaPlayer(){
     
        try {
     
            File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"music.mp3");
            Log.i("音乐文件路径", file.getPath());
            mediaPlayer.setDataSource(file.getPath());
            mediaPlayer.prepare();
        }catch (Exception e){
     
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
     
        switch (requestCode){
     
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
     
                    initMediaPlayer();
                }else {
     
                    Toast.makeText(this,"拒绝授权将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    @Override
    public void onClick(View v){
     
        switch (v.getId()){
     
            case R.id.play:
                if(!mediaPlayer.isPlaying()){
     
                    mediaPlayer.start();
                }
                break;
            case R.id.pause:
                if(mediaPlayer.isPlaying()){
     
                    mediaPlayer.pause();
                }
                break;
            case R.id.stop:
                if (mediaPlayer.isPlaying()){
     
                    mediaPlayer.reset();
                    initMediaPlayer();
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy(){
     
        super.onDestroy();
        if (mediaPlayer != null){
     
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
}

可以看到,在类初始化的时候我们就先创建了一个MediaPlayer的实例,然后在onCreate() 方法中进行了运行时权限处理,动态申请WRITE_EXTERMAL_STORAGE权限。这是由于待会我们会在SD卡中放置一个音频文件,程序为了播放这个音频文件必须拥有访问SD卡的权限才行。
注意,在onRequestPermissionsResult()方法中,如果用户拒绝了权限申请,那么就调用 finish()方法将程序直接关掉,因为如果没有SD卡的访问权限,我们这个程序将什么都干不了。
用户同意授权之后就会调用initMediaPlayer()方法为MediaPlayer对象进行初始化操作。在initMediaPlayer()方法中,首先是通过创建一个File对象来指定音频文件的路径,从这里可以看出,我们需要事先在SD卡的根目录下放置一个名为music.mp3的音频文件。后面依次调用了 setDataSource()方法和prepare()方法,为MediaPlayer做好了播放前的准备。
接下来我们看一下各个按钮的点击事件中的代码。当点击Play按钮时会进行判断,如果当前MediaPlayer没有正在播放音频,则调用start ()方法开始播放。当点击Pause按钮时会判断, 如果当前MediaPlayer正在播放音频,则调用pause()方法暂停播放。当点击Stop按钮时会判断,如果当前MediaPlayer正在播放音频,则调用reset ()方法将MediaPlayer重置为刚刚创建的状态, 然后重新调用一遍initMediaPlayer()方法。
最后在onDestroy()方法中,我们还需要分别调用stop()方法和release()方法,将与 MediaPlayer相关的资源释放掉。
另外,千万不要忘记在AndroidManifest.xml文件中声明用到的权限,如下所示:

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

这样一个简易版的音乐播放器就完成了,现在将程序运行到手机上会先弹出权限申请框,如图所示:
Android学习记录——8.多媒体_第8张图片
同意授权之后就可以开始播放音乐了,点击一下Play按钮,优美的音乐就会响起,然后点击Pause按钮,音乐就会停住,再次点击Play按钮,会接着暂停之前的位置继续播放。这时如果点击一下Stop按钮,音乐也会停住,但是当再次点击Play按钮时,音乐就会从头开始播放了。

4.2 播放视频

播放视频文件其实并不比播放音频文件复杂,主要是使用VideoView类来实现的。这个类将 视频的显示和控制集于一身,使得我们仅仅借助它就可以完成一个简易的视频播放器。VideoView的用法和MediaPlayer也比较类似,主要有以下常用方法:
Android学习记录——8.多媒体_第9张图片

那么我们还是通过一个实际的例子来学习一下吧,新建项目,然后修改 activity_main.xml中的代码,如下所示:

<?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:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/play"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="播放"/>

        <Button
            android:id="@+id/pause"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="暂停"/>

        <Button
            android:id="@+id/replay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="重放"/>

    </LinearLayout>

    <VideoView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

在这个布局文件中,首先放置了 3个按钮,分别用于控制视频的播放、暂停和重新播放。然后在按钮下面又放置了一个VideoView,稍后的视频就将在这里显示。
接下来修改MainActivity中的代码,如下所示:

package com.example.administrator.playvideotest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     

    private VideoView videoView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        videoView = (VideoView)findViewById(R.id.video_view);
        Button play = (Button)findViewById(R.id.play);
        Button pause = (Button)findViewById(R.id.pause);
        Button replay = (Button)findViewById(R.id.replay);
        play.setOnClickListener(this);
        pause.setOnClickListener(this);
        replay.setOnClickListener(this);
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
     
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{
     Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }else{
     
            initVideopath();
        }
    }

    private void initVideopath(){
     
        File file = new File(Environment.getExternalStorageDirectory(),"movie.mp4");
        videoView.setVideoPath(file.getPath());
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
     
        switch (requestCode){
     
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
     
                    initVideopath();
                }else{
     
                    Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    @Override
    public void onClick(View v){
     
        switch (v.getId()){
     
            case R.id.play:
                if (!videoView.isPlaying()){
     
                    videoView.start();
                }
                break;
            case R.id.pause:
                if (videoView.isPlaying()){
     
                    videoView.pause();
                }
                break;
            case R.id.replay:
                if (videoView.isPlaying()){
     
                    videoView.resume();
                }
                break;
        }
    }

    @Override
    protected void onDestroy(){
     
        super.onDestroy();
        if (videoView != null){
     
            videoView.suspend();
        }
    }
}

这部分代码相信你理解起来会很轻松,因为它和前面播放音频的代码非常类似。首先在 onCreate()方法中同样进行了一个运行时权限处理,因为视频文件将会放在SD卡上。当用户同 意授权了之后就会调用initVideoPath()方法来设置视频文件的路径,这里我们需要事先在SD卡的根目录下放置一个名为movie.mp4的视频文件。
下面看一下各个按钮的点击事件中的代码。当点击Play按钮时会进行判断,如果当前并没有正在播放视频,则调用start()方法开始播放。当点击Pause按钮时会判断,如果当前视频正 在播放,则调用pause()方法暂停播放。当点击Replay按钮时会判断,如果当前视频正在播放,则调用resume()方法从头播放视频。
最后在onDestroy()方法中,我们还需要调用一下suspend()方法,将VideoView所占用的资源释放掉。
另外,仍然始终要记得在AndroidManifest.xml文件中声明用到的权限,如下所示:

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

现在将程序运行到手机上,会先弹出一个权限申请对话框,同意授权之后点击一下Play按钮,就可以看到视频已经开始播放了,如图所示:
Android学习记录——8.多媒体_第10张图片
点击Pause按钮可以暂停视频的播放,点击Replay按钮可以从头播放视频。
这样的话,你就已经将VideoView的基本用法掌握得差不多了。不过,为什么它的用法和MediaPlayer这么相似呢?其实VideoView只是帮我们做了一个很好的封装而已,它的背后仍然是使用MediaPlayer来对视频文件进行控制的。另外需要注意,VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在着较大的不足。所以,如果想要仅仅使用VideoView就编写出一个功能非常强大的视频播放器是不太现实的。但是如果只是用于播放一些游戏的片头动画,或者某个应用的视频宣传,使用VideoView还是绰绰有余的。

你可能感兴趣的:(Android,Android)