本篇文章主要介绍以下几个知识点:
- 通知的使用;
- 调用系统相机拍照或调用相册选取照片;
- 播放多媒体文件。
8.1 使用通知
当某个应用程序希望向用户发送一些提示消息,而该应用程序又不在前台运行时,就可借助通知(Notification)来实现。
8.1.1 通知的基本用法
通知的用法灵活,既可以在活动里创建,也可在广播接收器里创建,还可在服务里创建。
无论在哪里创建通知,整体的步骤都是相同的。接下来学习下创建通知的步骤,代码如下:
public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
Button btn_send_notice = (Button) findViewById(R.id.btn_send_notice);
btn_send_notice.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notice:
// 1. 获取 NotificationManager 实例来对通知进行管理
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 2. 使用 Builder 构造器来创建 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();
// 3. 显示通知. 其中notify()的两个参数:第一个是id,要保证为每个通知所指定的id都是不同的;
// 第二个参数是 Notification 对象
manager.notify(1,notification);
break;
default:
break;
}
}
}
上述代码,界面上就一个 发送通知 按钮,在按钮的点击事件里面完成了通知的创建工作。运行程序,点击按钮效果如下:
接下来实现通知的点击效果,这里要用到 PendingIntent ,即在某个合适的时机去执行某个动作。PendingIntent 实例可由 getActivity()、getBroadcast()、getServices() 等方法获取,其接收的参数:第一个是 Context;第二个一般用不到传0即可;第三个是 Intent 对象;第四个用于确定 PendingIntent 的行为,一般传0即可。
首先,准备好一个点击通知跳转的活动 NotificationTestActivity ,然后,修改 NotificationActivity 的代码如下:
public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {
. . .
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notice:
Intent intent = new Intent(this,NotificationTestActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
// 1. 获取 NotificationManager 实例来对通知进行管理
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 2. 使用 Builder 构造器来创建 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))
.setContentIntent(pi) // 传入pi
.build();
// 3. 显示通知
manager.notify(1,notification);
break;
default:
break;
}
}
}
现在重新运行程序并点击按钮,效果如下:
细心的你会发现系统状态栏上的通知图标还没消失,解决方法有两种,一种是在 NotificationCompat.Builder 中再连缀一个 setAutoCancel() 方法,如下:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setAutoCancel(true)
.build();
另外一种是显式调用 NotificationManager 的 cancel()方法,在点击通知跳转的活动 NotificationTestActivity 中添加如下代码:
public class NotificationTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification_test);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(1); // 传入创建通知时指定的id
}
}
8.1.2 通知的进阶用法
上面提到了通知的基本用法,接下来介绍一些通知的其他技巧,比如:
- 在通知发出时播放一段音频,调用 setSound() 方法:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) // 在音频目录下选个音频文件
.build();
- 在通知到来时让手机振动,设置 vibrate 属性:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setVibrate(new long[]{0,1000,1000,1000}) // 数组下标0表静止的时长,下标1表振动的时长,下标2表静止的时长,以此类推
.build();
当然别忘了声明振动权限:
<uses-permission android:name="android.permission.VIBRATE" />
- 在通知到来时显式手机 LED 灯,调用 setLights() 方法:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setLights(Color.GREEN,1000,1000) // 三个参数:LED 灯的颜色、灯亮时长、灯暗时长
.build();
- 当然也可直接使用默认效果,如下:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();
8.1.3 通知的高级功能
上面提到了通知的进阶用法,接下来介绍一些通知的高级功能,比如:
-
NotificationCompat.Builder 类中的 setStyle() 方法
这个方法允许我们构建出富文本的通知内容,setStyle() 方法接收一个 NotificationCompat.style 参数,这个参数用来构造具体的富文本信息,如长文字、图片等。
在通知当中显示一段长文字,代码如下:
Notification notification = new NotificationCompat.Builder(this)
. . .
// 在setStyle方法中创建NotificationCompat.BigTextStyle对象,调用其bigText()方法传入文字即可
.setStyle(new NotificationCompat.BigTextStyle().bigText("红红火火恍恍惚惚," +
"哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈呵呵,红红火火恍恍惚惚" +
"哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈"))
.build();
效果如图所示:
在通知当中显示一张图片,代码如下:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.
decodeResource(getResources(),R.drawable.big_image)))))
.build();
效果如图所示:
-
NotificationCompat.Builder 类中的 setPriority() 方法
这个方法用于设置通知的重要程度,setPriority() 方法接收一个整型参数。
将通知的重要程度设置最高,代码如下:
Notification notification = new NotificationCompat.Builder(this)
. .
// 一共有5个常量可选:PRIORITY_DEFAULT默认、PRIORITY_MIN最低、PRIORITY_LOW较低、
// PRIORITY_HIGH较高、PRIORITY_MAX最高
.setPriority(NotificationCompat.PRIORITY_MAX)
.build();
效果如图所示:
8.2 调用摄像头和相册
调用摄像头拍照以及从相册中选择照片的相关代码如下:
public class TakePhotoActivity extends AppCompatActivity {
private Button btn_take_photo; // 调用摄像头拍照
private Button btn_select_photo; // 从相册中选照片
private ImageView iv_show_photo; // 展示照片
public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);
btn_take_photo = (Button) findViewById(R.id.btn_take_photo);
btn_select_photo = (Button) findViewById(R.id.btn_select_photo);
iv_show_photo = (ImageView) findViewById(R.id.iv_show_photo);
// 调用相机拍照
btn_take_photo.setOnClickListener(new View.OnClickListener() {
@Override
public 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){
// getUriForFile() 方法接收三个参数:Context对象、任意唯一的字符串、File对象
imageUri = FileProvider.getUriForFile(TakePhotoActivity.this, "com.wonderful." +
"myfirstcode.chapter8.TakePhotoActivity.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);
}
});
// 打开相册选照片
btn_select_photo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ActivityCompat.checkSelfPermission(TakePhotoActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(TakePhotoActivity.this,
new String[]{android.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 {
ToastUtils.showShort("你拒绝了权限请求");
}
break;
default:
break;
}
}
@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));
iv_show_photo.setImageBitmap(bitmap);
}catch (FileNotFoundException e){
e.printStackTrace();
}
}
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK){
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19){
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
}else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}
default:
break;
}
}
/**
* 4.4及以上系统处理图片方法
* @param data
*/
@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];// 解析出数字格式的id
String 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 contentUri = ContentUris.withAppendedId(Uri.parse(
"content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
}else if ("content".equalsIgnoreCase(uri.getScheme())){
// 若是content类型的Uri,则使用普通方法处理
imagePath = getImagePath(uri,null);
}else if ("file".equalsIgnoreCase(uri.getScheme())){
// 若是file类型到1Uri,则直接获取图片路径即可
imagePath = uri.getPath();
}
// 根据图片路径显示图片
displayImage(imagePath);
}
/**
* 4.4以下系统处理图片方法
* @param data
*/
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri,null);
displayImage(imagePath);
}
/**
* 通过Uri和selection来获取真实的图片路径
* @param uri
* @param selection
* @return
*/
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;
}
/**
* 根据图片路径显示图片
* @param imagePath
*/
private void displayImage(String imagePath){
if (imagePath != null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv_show_photo.setImageBitmap(bitmap);
}else {
ToastUtils.showShort("选取图片失败");
}
}
}
上述代码中,调用摄像头拍照时用到了内容提供器,所以别忘了在 AndroidManifest.xml 中对内容提供器进行注册,如下:
<!-- android:name 属性的值是固定的
android:authorities 属性的值要和FileProvider.getUriForFile()方法中的第二个参数一致
<meta-data>来指定Uri的共享路径 -->
<provider
android:authorities="com.wonderful.myfirstcode.chapter8.TakePhotoActivity.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>
上面引用了一个 @xml/file_paths 资源是不存在的,需要另外创建它。新建 file_paths.xml 文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- external_path指定Uri共享,其name的值可随便填,path是指共享的具体路径 -->
<external_path name = "my_images" path = ""/>
</paths>
当然也别忘了声明访问SD卡的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
注意事项:在实际开发中最好根据项目的需求先对照片进行适当的压缩,然后再加载到内存中。
8.3 播放多媒体文件
在安卓中播放音频文件一般用 MediaPlayer 类来实现,播放视频文件主要用 VideoView 类来实现。以下是两个类的常用方法:
实现播放多媒体代码相对简单,这里播放音频和视频写在一块了,代码如下:
public class MediaPlayActivity extends AppCompatActivity implements View.OnClickListener {
private Button music_play,music_pause,music_stop;// 音频播放、暂停、停止
private Button video_play,video_pause,video_replay;// 视频播放、暂停、重新播放
// 音频播放 创建MediaPlayer实例
private MediaPlayer mediaPlayer = new MediaPlayer();
// 视频播放显示
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_play);
initMediaPlayerView();
if (ActivityCompat.checkSelfPermission(MediaPlayActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MediaPlayActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initMediaPlayer(); // 初始化MediaPlayer
initVideoPath(); // 初始化MediaPlayer
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initMediaPlayer();
initVideoPath();
}else {
ToastUtils.showShort("你拒绝了权限请求");
}
break;
default:
break;
}
}
/**
* 初始化音频播放器
*/
private void initMediaPlayer() {
File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");//事先放好的音频文件
try {
mediaPlayer.setDataSource(file.getPath()); // 指定音频文件的路径
mediaPlayer.prepare();// 让 MediaPlayer 进入到准备状态
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 初始化视频播放器
*/
private void initVideoPath() {
File file = new File(Environment.getExternalStorageDirectory(),"movie.mp4");//事先放好的视频文件
videoView.setVideoPath(file.getPath()); // 指定视频文件的路径
}
private void initMediaPlayerView() {
music_play = (Button) findViewById(R.id.music_play);
music_pause = (Button) findViewById(R.id.music_pause);
music_stop = (Button) findViewById(R.id.music_stop);
music_play.setOnClickListener(this);
music_pause.setOnClickListener(this);
music_stop.setOnClickListener(this);
videoView = (VideoView) findViewById(R.id.video_view);
video_play = (Button) findViewById(R.id.video_play);
video_pause = (Button) findViewById(R.id.video_pause);
video_replay = (Button) findViewById(R.id.video_replay);
video_play.setOnClickListener(this);
video_pause.setOnClickListener(this);
video_replay.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
// 音频播放
case R.id.music_play:
if (!mediaPlayer.isPlaying()){
mediaPlayer.start(); // 开始播放
}
break;
case R.id.music_pause:
if (!mediaPlayer.isPlaying()){
mediaPlayer.pause(); // 暂停播放
}
break;
case R.id.music_stop:
if (!mediaPlayer.isPlaying()){
mediaPlayer.stop(); // 停止播放
}
break;
// 视频播放
case R.id.video_play:
if (!videoView.isPlaying()){
videoView.start(); // 开始播放
}
break;
case R.id.video_pause:
if (!videoView.isPlaying()){
videoView.pause(); // 暂停播放
}
break;
case R.id.video_replay:
if (!videoView.isPlaying()){
videoView.resume(); // 重新播放
}
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();// 释放资源
}
if (videoView != null){
videoView.suspend(); // 释放资源
}
}
}
当然别忘了声明权限:
<!-- 访问SD卡权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
本章内容相对简单,下一章将学习网络编程的知识。