第一行代码学习笔记第八章——运用手机多媒体
知识点目录
- 8.1 将程序运行到手机上
- 8.2 使用通知
* 8.2.1 通知的基本使用
* 8.2.2 通知的进阶技巧
* 8.2.3 通知的高级功能
- 8.3 调用摄像头和相册
* 8.3.1 调用摄像头拍照
* 8.3.2 从相册中选择照片
- 8.4 播放多媒体文件
* 8.4.1 播放音频
* 8.4.2 播放视频
- 8.5 小结与点评
知识点回顾
8.1 将程序运行到手机上
前面的章节我们都是使用模拟器来运行程序的,但这章的Demo都需要在真正的Android手机上运行。
打开开发者选项:
从Android4.2系统开始,开发者选项是默认隐藏的,需要先进入到“关于手机”界面,然后对连续点击版本号,直到让开发者选项显示出来。
手机连接电脑
打开开发者选项后,我们还需要通过数据线把手机连接到电脑上。然后进入到设置—>开发者选项界面,并在这个界面中勾选中USB调试选项。
如果是Windows操作系统,还需要在电脑上安装手机驱动。一般借助360手机助手或豌豆荚等工具进行快速安装,安装完成后就可以看到手机已经连接到电脑上了:
8.2 使用通知
通知的主要的作用是:当应用程序不是在前台运行时,向用户发出一些提示信息。
发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。
8.2.1 通知的基本使用
通知可以在活动、广播接收器或服务里创建。但无论在哪创建使用步骤都是一样的。
获取NotificationManager来对通知进行管理
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
创建Notification对象
Notification notification = new NotificationCompat.Builder(this).setContentTitle("This is content title").setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).build();
几乎每个Android版本都会对通知这部分进行修改,为了更好的兼容性,我们使用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用这个类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作。
setContentTitle()方法指定通知的标题内容
setContentText()方法指定通知的正文内容
setWhen()方法指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知上
setSmallIcon()方法设置通知的小图标,只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上
setLargeIcon()方法设置通知的大图标,下拉系统状态栏时,就可以看到设置的大图标
调用NotificationManager的notify()方法
manager.notify(1,notification);
参数一:id,要保证为每个通知所指定的id都是不同的
参数二:创建的Notification对象
示例代码:
定义一个notification触发点
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/send_notice"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textAllCaps="false"android:text="Send notice"/></LinearLayout>
创建通知
public class MainActivity extends AppCompatActivity implements View.OnClickListener {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button sendNotice = (Button) findViewById(R.id.send_notice);sendNotice.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.send_notice:String id = "my_channel_01";String name = "我是渠道名字";NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);Notification notification = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//Android 8.0以上Toast.makeText(this, "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT, Toast.LENGTH_SHORT).show();NotificationChannel channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_DEFAULT);manager.createNotificationChannel(channel);notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.setContentTitle("This is content title").setContentText("heheda").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).build();} else {//Android8.0以下Toast.makeText(this, "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT, Toast.LENGTH_SHORT).show();notification = new NotificationCompat.Builder(this).setContentTitle("This is content title").setContentText("hahaha").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).build();}manager.notify(1, notification);break;default:break;}}}
备注:在Android8.0以上发送通知的稍有不同,需要NotificationChannel。
PendingIntent
PendingIntent跟Intent有许多相似之处,都可以指明一个"意图",都可以启动活动、启动服务以及发送广播。不同的是Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,可以把PendingIntent理解为延迟执行的Intent
PendingIntent的基本用法:
- 获取PendingIntent实例
根据具体的需求来选择使用getActivity()方法、getBroadcast()方法或getService()方法。
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
getActivity()方法、getBroadcast()方法或getService()方法接收的参数都是相同的:
参数一:上下文
参数二:请求码,一般传入0即可
参数三:Intent对象
参数四:用于确定PendingIntent的行为。有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4个值可选,通常传入0即可。
2.连缀在setContentIntent()方法中
notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.setContentTitle("This is content title").setContentText("heheda").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setContentIntent(pendingIntent) //连缀PendingIntent.build();
这样下拉系统状态栏,点击通知后,就会进入到NotificationActivity界面。
让状态栏上通知栏消失:
上面进入到NotificationActivity界面后,发现系统状态栏上的通知栏图标还没有消失。
让状态栏上通知栏消失的方法有两种:
在NotificationCompat.Builder中再连缀一个
notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.setContentTitle("This is content title").setContentText("heheda").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setContentIntent(pendingIntent).setAutoCancel(true) //点击这个通知时,通知会自动取消掉.build();
显示地调用NotificationManager的cancel()方法
public class NotificationActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_notification);NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);manager.cancel(1);//这里的1,就是创建通知的时候给每条通知指定的id}}
8.2.2 通知的进阶技巧
NotificationCompat.Builder中提供了非常丰富的API来让我们创建出更加多样的通知效果。下面我们学习一些常用的API。
setSound()
在通知发出的时候播放一段音频。setSound()方法接收一个Uri参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。例如:每个手机的/system/media/audio/ringtones目录下很多音频文件,我们可以从中随便选一个音频文件,那么就可以在代码中这样指定;
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) //指定音频.build();
setVibrate()
用于设置手机静止和振动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时长,以此类推。
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setVibrate(new long[]{0,1000,1000,1000}) //指定振动时长.build();
备注:控制手机振动需要声明权限:
<uses-permission android:name="android.permission.VIBRATE"/>
setLights()
用于控制在通知到来时,控制手机LED灯的显示。
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setLights(Color.GREEN,1000,1000).build();
参数一:指定LED灯的颜色
参数二:指定LED灯亮起的时长,以毫秒为单位
参数三:指定LED灯暗去的时长,以毫秒为单位
setDefaults()
如果不想繁杂的设置,可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何振动。
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setDefaults(NotificationCompat.DEFAULT_ALL) //使用默认效果.build();
8.2.3 通知的高级功能
NotificationCompat.Builder中还有更强大的API,可以构建出更加丰富的通知效果。
setStyle()
当通知栏中的内容足够长的时候,正常情况下是无法完全显示的,如下图所示:
如果我们想让通知栏中的内容完全显示,可以使用setStyle()方法:
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notification ,send and sync data,and use voice actions. Get the official" +"Andoird IDE and developer tools to build apps for Android.")).build();
运行后的效果如下:
除了显示长文字之外,通知里还可以显示一张大图片,具体用法如下:
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.big_image))).build();
运行后效果如下:
setPriority()
用于设置通知的重要程度。该方法接收一个整型参数用于设置这条通知的重要程度,共有5个指可选:
PRIORITY_DEFAULT 表示默认的重要程度
PRIORITY_MIN 表示最低的重要程度
PRIORITY_LOW 表示较低的重要程度
PRIORITY_HIGH 表示较高的重要程度
PRIORITY_MAX 表示最高的重要程度
具体用法如下:
Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去.....setPriority(NotificationCompat.PRIORITY_MAX).build();
8.3 调用摄像头和相册
调用系统的摄像头进行拍照或者调用系统中的相册都是很常见的功能需求。
8.3.1 调用摄像头拍照
调用系统摄像头拍照的步骤一般如下:
第一步:定义一个触发点
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/take_photo"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Take Photo"android:textAllCaps="false"/><ImageViewandroid:id="@+id/picture"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"/></LinearLayout>
布局中一个Button和一个ImageView。Button用于打开摄像头进行拍照,ImageView用于将拍到的图片显示出来。
第二步:调用摄像头的具体逻辑
public class MainActivity extends AppCompatActivity {private static final int TAKE_PHOTO = 1;private Button mTakePhoto;private Button mMTakePhoto;private ImageView mPicture;private Uri mImageUri;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mMTakePhoto = (Button) findViewById(R.id.take_photo);mPicture = (ImageView) findViewById(R.id.picture);mMTakePhoto.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//创建File对象,用于存储拍照后的照片File outputImage = new File(getExternalCacheDir(), "output_image.jpg");try {if (outputImage.exists()) {outputImage.delete();}outputImage.createNewFile();} catch (IOException e) {e.printStackTrace();}//将创建的File对象转换为Uri对象if (Build.VERSION.SDK_INT >= 24) {mImageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImage);} else {mImageUri = Uri.fromFile(outputImage);}//启动相机程序Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);startActivityForResult(intent,TAKE_PHOTO);}});}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {switch (requestCode) {case TAKE_PHOTO:if (resultCode == RESULT_OK) {try {//将拍摄的照片显示出来Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(mImageUri));mPicture.setImageBitmap(bitmap);} catch (FileNotFoundException e) {e.printStackTrace();}}break;}}
}
对上面的代码分四大块进行解释:
1. 创建存放拍下图片的路径
创建一个File对象,用于存放摄像头拍下的图片,并命名为output_image.jpg,并将它存放在手机SD卡的应用关联缓存目录下。
应用关联缓存目录是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法就可以得到这个目录,具体路径是/sdcard/Android/data/com.example.cameraalbumtest/cache ,如下图所示:
为什么要是使用应用关联缓存目录呢?因为从Android6.0开始,读写SD卡被列为危险权限,如果将图片存放在SD卡的任何其他目录,都需要进行运行是权限处理才行,而使用应用关联目录则可以逃过这一步。
2. 将创建的File对象转换为Uri对象
在低于Android7.0时,就调用Uri的fromFile()方法将File对象转换为Uri对象。
在高于Android7.0时,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。该方法共接收三个参数:
参数一:Context对象
参数二:authority字符串。需要跟后面再AndroidManifest.xml注册时保持一致
参数三:创建的File对象
之所以要进行这样一层转换,因为从Android7.0开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常,而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行了保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。
这个Uri对象标识着output_image.jpg这张图片的本地真实路径。
3. 启动相机程序
构建一个Intent对象,并将Intent的action指定为android.media.action.IMAGE_CAPTURE,再调用putExtra()方法指定图片的输出地址,最后调用startActivityForResult()来启动活动。
这样我是使用的隐式Intent,系统会找到能够响应这个Intent的活动去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中
4. 将照片显示出来
因为上面用的是startActivityForResult()方法,当照相机程序拍照完成后,就会返回到onActivityResult()方法中,如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。
第三步:注册内容提供器
上面用到了FileProvider,所以就需要到AndroidManifest.xml中去注册:
<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><providerandroid:authorities="com.example.cameraalbumtest.fileprovider"android:name="android.support.v4.content.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths"/></provider>
</application>
属性解释:
android:authorities 指定authorities,需要与FileProvider.getUriForFile()方法中的第二个参数一致
android:name 固定值。必须是android.support.v4.content.FileProvider
android:exported 表示是否允许外部程序访问我们的内容提供器
android:grantUriPermissions 用于给内容提供器的数据子集授权。true表示权限能够被授予内容提供器范围内的任何数据;false表示权限只能授予这个元素所指定的数据子集。
指定Uri的共享路径,并应用一个@xml/file_paths资源
第四步:创建file_paths资源
右击res目录—>New—>Directory,创建一个xml目录,接着右击xml目录—>New—>File,创建一个file_paths.xml文件,然后修改file_paths.xml文件中的内容,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="my_images" path="."/>
</paths>
其中:external-path就是用来指定Uri共享的,name属性的值可以随便填写,path属性的值表示共享的具体路径。这里的.就表示将整个SD卡进行共享,当然也可以仅共享我们存放output_image.jpg这张图片的路径。
第五步:添加权限
在Android4.4之前,访问SD卡的应用关联目录也是要权限的,Android4.4之后的系统则不再需要权限声明了,为了能够兼容老版本,在AndroidManifest.xml中加入相应权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
效果图:
将程序运行到手机上,点击Take Photo按钮进行拍照,拍照完成后,点击√,回到我们程序界面,拍照的照片就会显示出来。如下图所示:
8.3.2 从相册中选择照片
从相册中选择已经拍摄好的照片也是非常常见的功能。
实现从相册中选择照片的功能一般步骤如下:
第一步:新增一个触发点
在前面CameraAlbumTest的基础上,添加一个Button:
<Buttonandroid:id="@+id/choose_from_album"android:layout_width="match_parent"android:layout_height="wrap_content"android:textAllCaps="false"android:text="Choose From Album"/>
Button用于打开系统相册,然后再在ImageView上将选中的图片显示出来。
第二步:打开相册的具体逻辑
public class MainActivity extends AppCompatActivity {private static final int TAKE_PHOTO = 1;private static final int CHOOSE_PHOTO = 2;private Button mMTakePhoto;private ImageView mPicture;private Uri mImageUri;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mMTakePhoto = (Button) findViewById(R.id.take_photo);mPicture = (ImageView) findViewById(R.id.picture);......//从相册中选择照片Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);chooseFromAlbum.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {switch (requestCode) {case 1:if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {openAlbum();} else {Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();}break;}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {switch (requestCode) {......case CHOOSE_PHOTO:if (resultCode == RESULT_OK) {//判断手机系统的版本号if (Build.VERSION.SDK_INT >= 19) {handleImageOnKitKat(data);} else {handleImageBeforeKitKat(data);}}break;default:break;}}@TargetApi(19)private void handleImageOnKitKat(Intent data) {String imagePath = null;Uri uri = data.getData();if (DocumentsContract.isDocumentUri(this,uri)) {//如果是document类型的Uri,则通过document id处理String docId = DocumentsContract.getDocumentId(uri);if ("com.android.providers.media.documents".equals(uri.getAuthority())) {String id = docId.split(":")[1];//解析出数字格式的idString selection = MediaStore.Images.Media._ID + "=" + id;imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {Uri cententUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));imagePath = getImagePath(cententUri, null);}} else if ("content".equalsIgnoreCase(uri.getScheme())) {//如果是content类型的Uri,则使用普通方式处理imagePath = getImagePath(uri, null);} else if ("file".equalsIgnoreCase(uri.getScheme())) {//如果是file类型的Uri,直接获取图片路径即可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 = null;//通过Uri和selection来获取真实的图片路径try {cursor = getContentResolver().query(uri, null, selection, null, null);if (cursor != null) {if (cursor.moveToFirst()) {path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));}}} catch (Exception e) {e.printStackTrace();} finally {if (cursor != null) {cursor.close();}}return path;}//将照片显示出来private void displayImage(String imagePath) {if (imagePath != null) {Bitmap bitmap = BitmapFactory.decodeFile(imagePath);mPicture.setImageBitmap(bitmap);} else {Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();}}
}
解释说明:
需要动态申请WRITE_EXTERNAL_STORAGE这个危险权限
从Android4.4开始,选取相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri,所以在4.4版本以上的手机需要对这个Uri进行解析。返回的Uri分为document、content和file三种类型,需要从这三种情况去分别判断解析。
效果图:
重新运行程序,点击Choose From Album按钮,首先会弹出权限申请框,点击允许后就会打开手机相册,然后随意选择一张照片,回到我们程序的界面,选中的照片就会显示出来,如下图所示:
调用摄像头拍照和从相册中选择照片的代码我已上传到我GitHub,有需要的朋友可以进入查看:
调用摄像头拍照和从相册中选择照片功能实现
8.4 播放多媒体文件
Android在播放音频和视频方面提供了一套较为完整的API,使得开发者可以很轻松地编写出一个简易的音频或视频播放器。
8.4.1 播放音频
Android中播放音频文件一般都是使用MediaPlayer类来实现。
MediaPlayer中常用的控制方法如下:
下面我们在项目中实践下:
第一步:编写布局控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/play"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Play"android:textAllCaps="false"/><Buttonandroid:id="@+id/pause"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Pause"android:textAllCaps="false"/><Buttonandroid:id="@+id/stop"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Stop"android:textAllCaps="false"/></LinearLayout>
布局中放置3个按钮,分别用于对音频文件的播放、暂停和停止操作。
第二步:音频功能的具体实现
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static final String TAG = "MainActivity";private MediaPlayer mMediaPlayer = new MediaPlayer();@Overrideprotected 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(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);} else {initMediaPlayer();//初始化MediaPlayer}}private void initMediaPlayer() {try {File file = new File(Environment.getExternalStorageDirectory(),"audio.wma");mMediaPlayer.setDataSource(file.getPath());//指定音频文件的路径mMediaPlayer.prepare();//让MediaPlayer进入到准备状态} catch (IOException e) {e.printStackTrace();}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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:break;}}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.play:if (!mMediaPlayer.isPlaying()) {mMediaPlayer.start();//开始播放}break;case R.id.pause:if (mMediaPlayer.isPlaying()) {mMediaPlayer.pause();//暂停播放}break;case R.id.stop:if (mMediaPlayer.isPlaying()) {mMediaPlayer.stop();//停止播放}break;default:break;}}@Overrideprotected void onDestroy() {super.onDestroy();if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.release();}}
}
第三步:加入权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
效果图:
运行程序后,会先申请权限,同意授权后,就会出现如下界面:
点击Play按钮就会播放音频。
点击Pause按钮,停止播放音频,再次点击Play按钮,会接着暂停之前的位置继续播放。
点击Stop按钮,停止播放音频,但再次点击Play按钮时,音乐会从从头开始播放。
8.4.2 播放视频
Android中播放视频文件一般都是使用VideoView类来实现。
VideoView中常用的控制方法如下:
下面我们在项目中实践下:
第一步:编写布局控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/play"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textAllCaps="false"android:text="Play"/><Buttonandroid:id="@+id/pause"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textAllCaps="false"android:text="Pause"/><Buttonandroid:id="@+id/replay"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textAllCaps="false"android:text="Replay"/></LinearLayout><VideoViewandroid:id="@+id/video_view"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
放置3个按钮,分别用于控制视频的播放、暂停和重新播放。下面放置一个VideoView,用于显示要播放的视频。
第二步:视频功能的具体实现
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static final String TAG = "MainActivity";private VideoView mVideoView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mVideoView = (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();//初始化VideoView}}private void initVideoPath() {File file = new File(Environment.getExternalStorageDirectory(), "ad.mp4");mVideoView.setVideoPath(file.getPath());}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.play:if (!mVideoView.isPlaying()) {mVideoView.start(); //开始播放}break;case R.id.pause:if (mVideoView.isPlaying()) {mVideoView.pause(); //暂停播放}break;case R.id.replay:if (mVideoView.isPlaying()) {mVideoView.resume(); //重新播放}break;default:break;}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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;}}@Overrideprotected void onDestroy() {super.onDestroy();if (mVideoView != null) {mVideoView.suspend(); //将VideoView所占的资源释放掉}}
}
第三步:加入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
效果图:
运行程序后,会先申请权限,同意授权后,点击Play按钮,就可以看到视频已经开始播放了,如下图所示:
点击Pause按钮可以暂停视频的播放,点击Replay按钮可以从头开始播放视频。
VideoView只是帮我们做了一个很好的封装,它的背后仍然使用MediaPlayer来对视频文件进行控制。
VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在较大的不足。所以,如果想仅仅用VideoView编写出一个功能非常强大的视频播放器是不太现实的。比较适合于用于播放一些游戏类的片头动画或某个应用的宣传视频。
8.5 小结与点评
本章主要对Android系统的各种多媒体技术进行了学习,主要有:
通知的使用技巧
调用摄像头拍照
从相册中选取照片
播放音频文件
播放视频文件
非常感谢您的耐心阅读,希望我的文章对您有帮助。欢迎点评、转发或分享给您的朋友或技术群。
第一行代码学习笔记第八章——运用手机多媒体相关推荐
- 第一行代码学习笔记第六章——详解持久化技术
知识点目录 6.1 持久化技术简介 6.2 文件存储 * 6.2.1 将数据存储到文件中 * 6.2.2 从文件中读取数据 6.3 SharedPreferences存储 * 6.3.1 将数据存储到 ...
- 第一行代码学习笔记第七章——探究内容提供器
知识点目录 7.1 内容提供器简介 7.2 运行权限 * 7.2.1 Android权限机制详解 * 7.2.2 在程序运行时申请权限 7.3 访问其他程序中的数据 * 7.3.1 ContentRe ...
- 第一行代码学习笔记第三章——UI开发的点点滴滴
知识点目录 3.1 如何编写程序界面 3.2 常用控件的使用方法 * 3.2.1 TextView * 3.2.2 Button * 3.2.3 EditText * 3.2.4 ImageView ...
- 第一行代码学习笔记第十章——探究服务
知识点目录 10.1 服务是什么 10.2 Android多线程编程 * 10.2.1 线程的基本用法 * 10.2.2 在子线程中更新UI * 10.2.3 解析异步消息处理机制 * 10.2.4 ...
- 第一行代码学习笔记第九章——使用网络技术
知识点目录 9.1 WebView的用法 9.2 使用HTTP协议访问网络 * 9.2.1 使用HttpURLConnection * 9.2.2 使用OkHttp 9.3 解析XML格式数据 * 9 ...
- 第一行代码学习笔记第五章——详解广播机制
知识点目录 5.1 广播机制 5.2 接收系统广播 * 5.2.1 动态注册监听网络变化 * 5.2.2 静态注册实现开机广播 5.3 发送自定义广播 * 5.3.1 发送标准广播 * 5.3.2 发 ...
- 第一行代码学习笔记第二章——探究活动
知识点目录 2.1 活动是什么 2.2 活动的基本用法 2.2.1 手动创建活动 2.2.2 创建和加载布局 2.2.3 在AndroidManifest文件中注册 2.2.4 在活动中使用Toast ...
- 安卓教程----第一行代码学习笔记
安卓概述 系统架构 Linux内核层,还包括各种底层驱动,如相机驱动.电源驱动等 系统运行库层,包含一些c/c++的库,如浏览器内核webkit.SQLlite.3D绘图openGL.用于java运行 ...
- 第一行代码学习笔记第四章——探究碎片
知识点目录 4.1 碎片是什么 4.2 碎片的使用方式 * 4.2.1 碎片的简单用法 * 4.2.2 动态添加碎片 * 4.2.3 在碎片中模拟返回栈 * 4.2.4 碎片和活动之间进行通信 4.3 ...
最新文章
- 详解 CQRS 架构模式
- ubuntu-make/makefile/cmake
- GTX1080 安装 CUDA 7.5
- golang管道channel的遍历和关闭:应该使用for...range来遍历
- python解析xml+得到pascal voc xml格式用于目标检测+美化xml
- Qt 二级菜单无法输入中文
- js 图片压缩上传(base64位)以及上传类型分类
- C# 利用反射机制开启控件双缓存
- python D40 以及多表查询
- Android操作系统手机遇冷 国外辉煌国内难现
- 深入浅出妙用 Javascript 中 apply、call、bind
- 一位编辑人员给作者们的市场汇报——冰冰子组织的市场宣传活动介绍之交互设计篇
- sa linux,sa | 搜索结果 | Linux运维部落
- AFDX(ARINC664)的交换机规范
- 勒索病毒数据库恢复 勒索病毒解密恢复 中勒索病毒解密恢复数据
- 谈谈如何做到从未来看向当代的能源技术
- 51单片机学习笔记2 -- 单灯控制及流水灯
- 股票:巧用均线多头排列选股
- java web简单线上游戏_手把手教你用Java实现一个简易联网坦克对战小游戏 !
- 如何在Windows系统上实现共享文件夹