《第一行代码 第二版》第八章

《第一行代码 第二版》

本博客是对第一行代码的精简总结,仅供个人学习使用。如需系统学习请购买正版或者电子书籍。

链接附上 🔗图灵社区:https://www.ituring.com.cn/book/2744/

第 8 章 丰富你的程序——运用手机多媒体

8.1 使用通知

下拉列表可看到通知得信息

创建通知的详细步骤

首先需要一个NotificationManager来对通知进行管理,可以调用Context的getSystemService()方法获取到。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入Context.NOTIFICATION_SERVICE即可。因此,获取NotificationManager的实例就可以写成:

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

support-v4库中提供了一个NotificationCompat类,使用这个类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作了

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


5个设置方法,下面我们来一一解析一下。setContentTitle()方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。setContentText()方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。setWhen()方法用于指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知上。setSmallIcon()方法用于设置通知的小图标,注意只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。setLargeIcon()方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。

以上工作都完成之后,只需要调用NotificationManager的notify()方法就可以让通知显示出来了。notify()方法接收两个参数,第一个参数是id,要保证为每个通知所指定的id都是不同的。第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification对象传入即可。因此,显示一个通知就可以写成:

manager.notify(1, notification);

实现通知的点击效果,我们还需要在代码中进行相应的设置,这就涉及了一个新的概念:PendingIntent

PendingIntent简单地理解为延迟执行的Intent。都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。

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("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)
                        .build();
                manager.notify(1, notification);

么系统状态上的通知图标还没有消失呢?是这样的,如果我们没有在代码中对该通知进行取消,它就会一直显示在系统的状态栏上。解决的方法有两种,一种是在NotificationCompat.Builder中再连缀一个setAutoCancel()方法,一种是显式地调用NotificationManager的cancel()方法将它取消。

8.2.2 通知的进阶技巧

setSound()方法吧,它可以在通知发出的时候播放一段音频,这样就能够更好地告知用户有通知到来。setSound()方法接收一个Uri参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。

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, 1000 })
        .build();

通知到来时,如果想要实现LED灯以绿色的灯光一闪一闪的效果,就可以写成:

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

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

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

8.2.3 通知的高级功能

setStyle()方法,这个方法允许我们构建出富文本的通知内容。也就是说通知中不光可以有文字和图标,还可以包含更多的东西。setStyle()方法接收一个NotificationCompat.Style参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。

长文字:

setStyle()方法中创建了一个NotificationCompat.BigTextStyle对象,这个对象就是用于封装长文字信息的,我们调用它的bigText()方法并将文字内容传入就可以了

大图片:

调用的setStyle()方法,这次我们在参数中创建了一个NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()方法并将图片传入。

优先级:

setPriority()方法

PRIORITY_DEFAULT表示默认的重要程度,和不设置效果是一样的;PRIORITY_MIN表示最低的重要程度,系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;PRIORITY_LOW表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之后;PRIORITY_HIGH表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序,将其排在比较靠前的位置;PRIORITY_MAX表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应操作。

8.3 调用摄像头和相册

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对象,用于存储拍照后的图片
                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(getContent-
                            Resolver().openInputStream(imageUri));
                        picture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }

}


8.3.1 调用摄像头拍照:
1.创建点击事件按钮和用于显示拍照后图片的Imageview及实例。
2.创建File对象,用于存储拍照后的照片,将其存放在当前引用的缓存数据中,图片名字为output_image.jpg

File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
3.如果其存在,则删除,并createNewFile创建新文件实例。
4.1.如果系统低于7.0,在使用Uri.fromFile()方法将File对象转为Uri对象,Uri对象标识jpg文件真实路径;
4.2.如果大于等于7.0,则使用FileProvider.getUriForFile()方法,里面三个参数分别是Context对象,任意字符串以及刚刚创建的Image对象。

if (Build.VERSION.SDK_INT >=24){
                  imageuri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
                }else{
                    imageuri = Uri.fromFile(outputImage);
                }

​ 5.构建Intent对象,将Action指定为android.media.action.IMAGE_CAPTURE,putExtra()填入刚刚得到的URI对象,最后调用startActivityForResult来启动活动。

Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageuri);
startActivityForResult(intent,TAKE_PHOTO);

​ 6.在AndroidManifest.xml中对内容提供器进行注册;(1)android:name固定(2)android:authorities与ileProvider.getUriForFile()第二个参数一致(3)在provider标签中指定meta-data来制定共享路径,并引用xml文件下的资源

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

​ 7.xml文件中external-path是指定Uri共享的,name属性可以随便填,path属性指的是共享的具体路径,设置空值就是将整个SD卡共享

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

​ 8.访问SD卡在4.4之前需要声明访问SD卡的权限

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

8.3.2从相册中选择照片
1.建立点击事件及布局,进行运行时权限处理,WRITE_EXTERNAL_STORAGE为程序对SD卡的读写能力。大于6.0动态申请权限后调用openAlbum()方法,小于6.0直接调用openAlbum()方法

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();
                }

​ 2.openAlbum中构建Intent对象,指定action为android.intent.action.GET_CONTENT,为Intent设定必要参数,调用startActivityForResult来打开相册程序选择照片,其中第二个参数为CHOOSE_PHOTO的2

 private void openAlbum() {
        Intent intent01 = new Intent("android.intent.action.GET_CONTENT");
        intent01.setType("image/*");
        startActivityForResult(intent01, CHOOSE_PHOTO);
}

​ 3.这样在onActivityResult中进入CHOOSE_PHOTO进行处理,为了兼容新旧版本,4.4以上调handleImageOnKitKat方法; 4.4以下调handleImageBeforeKitKat方法。因为4.4以后选择相册中的照片不在返回真实的Uri,因此需要解析。

 case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    if (Build.VERSION.SDK_INT >= 19) {
                        handleImageOnKitKat(data);
                    } else {
                        handleImageBeforeKitKat(data);
                    }
                }

​ 4.1.handleImageOnKitKat解析了封装的Uri,(1)如果是document类型的Uri,则通过document id处理。若URi的authority是media格式,还需要进一步的解析,通过字符串分割获得真实ID,用ID构建新Uri和判断语句,将其传至getImagePath方法中。可以获取真实路径了(2)如果是content类型的uri,则使用普通方法去处理(3)如果是File类型的uri,直接获取图片路径即可,最后调用displayImage根据图片路径显示图片。
​ 4.2.handleImageBeforeKitKat方法中Uri未经封装,无需解析,直接getIamgePath获取真实路径,再调用displayImage方法显示于界面。

 private void displayImage(String imagePath) {
        if(imagePath!=null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            iv_photos.setImageBitmap(bitmap);
        }else {
            Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
        }
}

​ 5.getIamgePath方法中通过Uri和selection来获取真实的路径,一列cursor对象的数据。

private String getIamgePath(Uri externalContentUri, String selection) {
        String path = null;
        //通过Uri和selection来获取真实的路径
        Cursor cursor = getContentResolver().query(externalContentUri,null,selection,null,null);
        if(cursor!=null){
            if(cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }
        

6.displayImage中显示图片至界面。

private void displayImage(String imagePath) {
        if(imagePath!=null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            iv_photos.setImageBitmap(bitmap);
        }else {
            Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
        }
    }   

8.4播放多媒体文件
(一)播放音频

1.类初始化时创建一个MediaPlayer的实例,然后在OnCreate方法是进行动态权限处理,动态申请WRITE_EXTERNAL_STORAGE权限。若用户拒绝,则Finish掉。若用户同意,调用initMediaPlayer方法
2.为MediaPlayer设置初始化操作,创建File指定文件路径,接着调用了setDataSource和prepare方法。注意:目前手机都有内置得超大内部存储,使用Environment.getExternalStorageDirectory()获取路径的时候就不能定位到SD卡了,而是默认检测成内部存储路径。而且不同厂商手机SD卡的挂载路径不一样,所以SD卡挂在目录也就不能统一。

private void initMediaPlayer() {
        try {
            //Environment.getExternalStorageDirectory()为内置内存的根目录
            File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");
            mediaPlayer.setDataSource(file.getPath());//设置要播放视频文件的位置
            mediaPlayer.prepare();//让MediaPlayer进入准备状态。
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

​ 3.点击播放按钮、暂停按钮、停止按钮的响应事件,判断MediaPlayer当前有没有播放视频,若没有,则开始播放。

switch (v.getId()){
            case R.id.btn_play:
                if(!mediaPlayer.isPlaying()){
                    mediaPlayer.start();//开始播放
                }
                break;

​ 4.最后在OnDestory方法中,分别调用stop和release方法以释放资源。

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

​ 这样就可以播放了。

(二)播放视频
几乎一模一样,只是把MediaPlayer改为VideoView来实现。

区别地方在于:
(1)无需再初始化Prepare,直接setVideoPath之后就可以start了;
(2)重头播放用Resume,

case R.id.btn_stop:
                if(videoView.isPlaying()){
                    videoView.resume();
                }
                break;
     (3)释放资源用suspend方法。

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

posted @ 2021-04-27 15:58  AronJudge  阅读(167)  评论(0编辑  收藏  举报