运用手机多媒体

2021.2.24

Gary哥哥的哥哥

运用手机多媒体丰富你的程序

Android提供了一系列的相关API,使得我们在程序当中可以调用很多手机的多媒体资源,从而编写出更加丰富的应用

程序运行在Android手机上

在正式讲解之前,我们先来了解一下,如何将程序运行在Android手机上

这个很简单啦,USB连电脑,手机开发者选项开启调试,然后运行Android Studio的项目代码即可安装到手机上了

下面我们对几个常用的多媒体进行一一讲解

使用通知

这是一个很有特色的功能,连iOS5.0页引入了类似的功能

即便当应用程序不在前台运行的时候,借助通知来发送提示用户

通知渠道

Android 8.0开始,每一个通知都要属于一个相应的渠道,每个应用程序都可以自由地创建当前应用拥有哪些通知渠道,但这些渠道的掌控权在用户的手上。用户可以自由地选择这些通知渠道的重要程度,是否响铃,是否震动或者是否要关闭这个渠道

为此,我们不用再担心一些垃圾通知的骚扰了

  • 下面我们来看看创建自己的通知渠道的详细步骤吧
  • 首先需要一个NotificationManager对通知进行管理,可以调用Contenx的getSystemService()方法来获取
  • getSystemService()
  • p:接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入Context.NOTIFICATION_SERVICE即可
  • 写法如下
val manager=getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
  • 接下来需要使用NotificationCannel类构件一个通知渠道,并调用NotificationManager的createNotificationChannel()方法完成构建
  • 由于这些方法都是Android8.0新增的API,我们调用的时候还需要进行版本的判断
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    val channel = NotificationChannel(channelId, channelName, importance)
    manager. createNotificationChannel(channel)
}

我这里重点提醒提醒,VERSION_CODES.O(是O不是0)


  • 创建一个通知渠道至少需要知道渠道的ID,渠道名称和重要性三个删除
  • 渠道的ID可以随便定义,只要保证全局唯一性即可
  • 渠道名称是个用户看的,需要可以清楚地表达这个渠道的用途
  • 通知重要性等级从高到低:IMPORTANCE_HIGH / DEFAULT / LOW / MIN
  • 用户可以随意更改通知的重要性等级,开发者是无法干预的

通知的基本用法

通知的使用方法还是比较灵活的,可以创建在Activity,BroadcastReceiver 和Service当中(Service后面会学习到)。一般创建场景在后面两个常见,Activity里创建通知还是比较少的,因为一般只有当程序进入后台的时候才需要使用通知

我们先来学习一下创建通知的一般步骤

  1. 首先需要一个Builder构造器来创建Notification对象,AndroidX库提供了一些兼容之前版本的API
  • AndroidX提供了一个NotificationCompat类,使用这个类来构建Notification对象就不存在兼容性的问题了
val notification = NotificationCompat.Builder(context, channelId).build()
  • NotificationCompat.Builder的构造函数中,p1是一个context;p2是渠道的ID
  • 需要我们在创建通知渠道时指定的渠道ID相匹配才行
  • 上述代码只是创建了一个空的Notification对象,并没有什么实际的作用, 我们可以在最终的build()方法中连缀人一多的设置方法来创建一个丰富的Notification对象,先来看看一些基本的操作:
val notification=NotificationCompat.Builder(context,channelId)
	.setContentTitle("This is content title")
    .setContentText("This is content text").setSmallIcon(R.drawable.ic_launcher_background)
    .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background))
    .build()

这里一共调用了四个设置方法:

  • 设置Title
  • 设置正文内容(下拉状态栏也可以看到这部分的内容)
  • 设置通知的小图标
  • 注意:这里使用纯alpha图层的图片进行设置,小图标会显示在状态栏上方出现
  • 设置大图标,下拉状态栏可以看到大图标
  1. 以上工作全部完成过后,只需要调用NotificationManager的notify()方法就可以放通知显示出来
  • notify()方法接受两个参数:
  • p1:id,保证为每个通知指定的id都是不相同的
  • p2:Notification对象,这里我们将我们刚刚创建的Notification对象传入即可,因此显示一个通知就写成:
manager.notify(1, notification)

下面我们就以一个完整的例子NotificationTest项目来看看通知到底是长什么样的

activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Notice"
        android:textAllCaps="false"
        android:id="@+id/sendNotice"/>

</LinearLayout>

MainActivity中:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.0){
            val channel = NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        sendNotice.setOnClickListener {
            val notification = NotificationCompat.Builder(this,"normal")
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_launcher_background))
                .build()
            manager.notify(1,notification)
        }
    }
}

下面我们要使点击这条通知之后有效果,这里涉及新的概念PendingIntent

  • 和Intent有些类似
PendingIntent用法

它主要提供几个静态方法用于获取PendingIntent的实例

  • getActivity()
  • getBroadcast()
  • getService

他们的参数都是:

  • p1:Context
  • 一般不用到,传入0即可
  • p3:是一个Intent对象,通过这个对象构建出PendingIntent的意图
  • p4:用于确定PendingIntent的行为,有FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,通常情况下传入0即可(至于上面这几个需要自行查阅文档)

下面来优化我们的NotificationTest项目

activity_notification.xml和NotificationActivity

<?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="This is notification layout"/>
</RelativeLayout>

MainActivity中:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            val channel = NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        sendNotice.setOnClickListener {
            //构建意图
            val intent=Intent(this,NotificationActivity::class.java)
            val pi = PendingIntent.getActivity(this,0,intent,0)

            //注意下面setContentIntent(pi)
            //setAutoCancel(true) 点击之后自动消失
            val notification = NotificationCompat.Builder(this,"normal")
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_launcher_background))
                .setContentIntent(pi)
                .setAutoCancel(true)
                .build()
            manager.notify(1,notification)
        }
    }
}

关于点击后自动消失,也可以在NotificationActivity中实现

class NotificationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_notification)
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.cancel(1)//这里的1就是那个通知设定的id
    }
}
进阶技巧

实际上NotificationCompat.Builder中还提到了非常丰富的API,以便我们创建出更加多样的通知效果

但我们只讲解一些比较常用的API,其他API可以在大家实践的时候自行查阅资料

setStyle()方法

这个方法允许我们构建富文本的通知内容。也就是说,通知不光可以有文字和图标,还可以包含更多的内容

p:NotificationCompat.Style参数,这个参数就是用来构建具体的富文本信息,如长文字和图片等

ex:如果按我们之前讲的那样子写,如果通知的内容文字太多,他就只会缩略尾部的方式进行显示,而这里可以做到将全部东西显示出来

sendNotice.setOnClickListener {
            //构建意图
            val intent=Intent(this,NotificationActivity::class.java)
            val pi = PendingIntent.getActivity(this,0,intent,0)

            //注意下面setContentIntent(pi)
            //setAutoCancel(true) 点击之后自动消失
            val notification = NotificationCompat.Builder(this,"normal")
                .setContentTitle("This is content title")
//                .setContentText(" 全国脱贫攻坚总结表彰大会开始,中共中央政治局常委、全国政协主席汪洋同志宣读《中共中央 国务院关于授予全国脱贫攻坚楷模荣誉称号的决定》。\n" +
//                        "\n" +
//                        "  为隆重表彰激励先进,大力弘扬民族精神、时代精神和脱贫攻坚精神,充分激发全党全国各族人民干事创业的责任感、使命感、荣誉感,汇聚更强大的力量推进全面建设社会主义现代化国家,党中央、国务院决定,授予毛相林等10名同志,河北省塞罕坝机械林场等10个集体“全国脱贫攻坚楷模”荣誉称号。")
                .setStyle(NotificationCompat.BigTextStyle().bigText(" 全国脱贫攻坚总结表彰大会开始,中共中央政治局常委、全国政协主席汪洋同志宣读《中共中央 国务院关于授予全国脱贫攻坚楷模荣誉称号的决定》。\n" +
                        "\n" +
                        "  为隆重表彰激励先进,大力弘扬民族精神、时代精神和脱贫攻坚精神,充分激发全党全国各族人民干事创业的责任感、使命感、荣誉感,汇聚更强大的力量推进全面建设社会主义现代化国家,党中央、国务院决定,授予毛相林等10名同志,河北省塞罕坝机械林场等10个集体“全国脱贫攻坚楷模”荣誉称号。"))
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_launcher_background))
                .setContentIntent(pi)
//                .setAutoCancel(true)
                .build()
            manager.notify(1,notification)
        }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qu7DDJ9R-1614257888277)(E:\kotlin-study\Studying-Kotlin\Multimedia\setStyle()].png)

除此之外还可以做到完整的显示一张图片

sendNotice.setOnClickListener {
            //构建意图
            val intent=Intent(this,NotificationActivity::class.java)
            val pi = PendingIntent.getActivity(this,0,intent,0)

            //注意下面setContentIntent(pi)
            //setAutoCancel(true) 点击之后自动消失
            val notification = NotificationCompat.Builder(this,"normal")
                .setContentTitle("This is content title")
//                .setContentText(" 全国脱贫攻坚总结表彰大会开始,中共中央政治局常委、全国政协主席汪洋同志宣读《中共中央 国务院关于授予全国脱贫攻坚楷模荣誉称号的决定》。\n" +
//                        "\n" +
//                        "  为隆重表彰激励先进,大力弘扬民族精神、时代精神和脱贫攻坚精神,充分激发全党全国各族人民干事创业的责任感、使命感、荣誉感,汇聚更强大的力量推进全面建设社会主义现代化国家,党中央、国务院决定,授予毛相林等10名同志,河北省塞罕坝机械林场等10个集体“全国脱贫攻坚楷模”荣誉称号。")
//                .setStyle(NotificationCompat.BigTextStyle().bigText(" 全国脱贫攻坚总结表彰大会开始,中共中央政治局常委、全国政协主席汪洋同志宣读《中共中央 国务院关于授予全国脱贫攻坚楷模荣誉称号的决定》。\n" +
//                        "\n" +
//                        "  为隆重表彰激励先进,大力弘扬民族精神、时代精神和脱贫攻坚精神,充分激发全党全国各族人民干事创业的责任感、使命感、荣誉感,汇聚更强大的力量推进全面建设社会主义现代化国家,党中央、国务院决定,授予毛相林等10名同志,河北省塞罕坝机械林场等10个集体“全国脱贫攻坚楷模”荣誉称号。"))
                .setContentText("Look, what a beautiful picture")
                .setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.drawable.ic_launcher_background)))
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_launcher_background))
                .setContentIntent(pi)
//                .setAutoCancel(true)
                .build()
            manager.notify(1,notification)
        }

需要注意的是,开发者只能再创建通知渠道的时候为他制定初始的重要等级,用户不认可,用户可以自行修改,因为通知渠道一旦创建就不能通过代码修改,我们再创建一条通知渠道来测试:

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    val channel = NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT)
    manager.createNotificationChannel(channel)
    val channel2=NotificationChannel("important","Important",NotificationManager.IMPORTANCE_HIGH)
    manager.createNotificationChannel(channel2)
}
sendNotice.setOnClickListener {
    //构建意图
    val intent=Intent(this,NotificationActivity::class.java)
    val pi = PendingIntent.getActivity(this,0,intent,0)

    //注意下面setContentIntent(pi)
    //setAutoCancel(true) 点击之后自动消失
    val notification = NotificationCompat.Builder(this,"important")
        .setContentTitle("This is content title")
    ...
}

搞个high给他,发现这里已经变成一个弹出式的通知了

调用摄像头和相册

调用摄像头拍照

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="take Photo"
        android:id="@+id/takePhotoBtn"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/imageView"/>


</LinearLayout>

MainActivity中:

下面代码确实比较长,详细的解析请见书本p369页

class MainActivity : AppCompatActivity() {
    val takePhoto = 1
    lateinit var imageUri :Uri
    lateinit var outputImage: File

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        takePhotoBtn.setOnClickListener {
            //创建File对象,用于存储拍照后的图片

            outputImage = File(externalCacheDir,"output_image.jpg")//采用应用管理目录来缓存(避免读写SD卡的危险权限)
            if(outputImage.exists()){
                outputImage.delete()
            }
            outputImage.createNewFile()
            imageUri = if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N){
                //将File对象转化成一个封装过的Uri对象(FileProvider对数据进行了保护)
                FileProvider.getUriForFile(this,"com.workaholiclab.cameraalbumtest.fileprovider",outputImage)
            }else{
                Uri.fromFile(outputImage) //该设备低于android7就调用Uri的fromFile方法将File转话为Uri对象
                //这个Uri对象包含了这张图片的真实存在的路径
            }
            //启动相机程序
            val intent =Intent("android.media.action.IMAGE_CAPTURE")
            intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri)//指定图片的输入地址,这里为刚刚的Uri对象
            startActivityForResult(intent,takePhoto)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when(requestCode){
            takePhoto->{
                if (resultCode ==Activity.RESULT_OK){
                    //将拍摄的照片显示出来
                    val bitmap= BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))//将这张图片解析称为Bitmap对象
                    imageView.setImageBitmap(rotateIfRequired(bitmap))//变成ImageView,需要注意一些手机上拍照转化会发生一些旋转,需要处理一下
                }
            }
        }
    }

    //照片旋转处理
    private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
        val exif = ExifInterface(outputImage.path)
        val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)
        return when(orientation){
            ExifInterface.ORIENTATION_ROTATE_90->rotateBitmap(bitmap,90)
            ExifInterface.ORIENTATION_ROTATE_180->rotateBitmap(bitmap,180)
            ExifInterface.ORIENTATION_ROTATE_270->rotateBitmap(bitmap,270)
            else-> bitmap
        }
    }

    private fun rotateBitmap(bitmap: Bitmap, degree: Int): Bitmap {
        val matrix =Matrix()
        matrix.postRotate(degree.toFloat())
        val rotateBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.width,bitmap.height,matrix,true)
        bitmap.recycle()//将不再需要的Bitmap对象回收
        return rotateBitmap
    }
}

下面不要忘记注册我们Provider了,FileProvider是一种特殊的Provider

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.workaholiclab.cameraalbumtest">

    <application
        android: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">
        <provider
            android:authorities="com.workaholiclab.cameraalbumtest.fileprovider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

name属性是固定的

authorities属性的值必须和刚才getUriForFile()方法的第二个参数一致

指定Uri的分享路径,冰鞋引用了一个@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
        name="my_images"
        path="/"/>
</paths>

name属性随便写

path属性值表示共享的具体路径,一个/表示将整个SD卡进行分享,当然你也可以这分享存放上面图片的路径

从相册中选取照片

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="take Photo"
        android:id="@+id/takePhotoBtn"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="From Album"
        android:id="@+id/fromAlbumBtn"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/imageView"/>



</LinearLayout>
class MainActivity : AppCompatActivity() {
    val takePhoto = 1
    lateinit var imageUri :Uri
    lateinit var outputImage: File

    val fromAlbum = 2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        takePhotoBtn.setOnClickListener {
            //创建File对象,用于存储拍照后的图片

            outputImage = File(externalCacheDir,"output_image.jpg")//采用应用管理目录来缓存(避免读写SD卡的危险权限)
            if(outputImage.exists()){
                outputImage.delete()
            }
            outputImage.createNewFile()
            imageUri = if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N){
                //将File对象转化成一个封装过的Uri对象(FileProvider对数据进行了保护)
                FileProvider.getUriForFile(this,"com.workaholiclab.cameraalbumtest.fileprovider",outputImage)
            }else{
                Uri.fromFile(outputImage) //该设备低于android7就调用Uri的fromFile方法将File转话为Uri对象
                //这个Uri对象包含了这张图片的真实存在的路径
            }
            //启动相机程序
            val intent =Intent("android.media.action.IMAGE_CAPTURE")
            intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri)//指定图片的输入地址,这里为刚刚的Uri对象
            startActivityForResult(intent,takePhoto)
        }

        //选取相册
        fromAlbumBtn.setOnClickListener {
            //打开文件选择器
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            //指定只显示图片
            intent.type ="image/*"
            startActivityForResult(intent,fromAlbum)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when(requestCode){
            takePhoto->{
                if (resultCode ==Activity.RESULT_OK){
                    //将拍摄的照片显示出来
                    val bitmap= BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))//将这张图片解析称为Bitmap对象
                    imageView.setImageBitmap(rotateIfRequired(bitmap))//变成ImageView,需要注意一些手机上拍照转化会发生一些旋转,需要处理一下
                }
            }

            fromAlbum->{
                if(resultCode == Activity.RESULT_OK && data != null){
                    data.data?.let {
                        uri ->
                        //将选择的图片显示
                        val bitmap=getBitmapFromUri(uri)
                        imageView.setImageBitmap(bitmap)
                    }
                }
            }
        }
    }

    private fun getBitmapFromUri(uri: Uri) = contentResolver
        .openFileDescriptor(uri,"r")?.use {
            BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
        }

    //照片旋转处理
    private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
        val exif = ExifInterface(outputImage.path)
        val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)
        return when(orientation){
            ExifInterface.ORIENTATION_ROTATE_90->rotateBitmap(bitmap,90)
            ExifInterface.ORIENTATION_ROTATE_180->rotateBitmap(bitmap,180)
            ExifInterface.ORIENTATION_ROTATE_270->rotateBitmap(bitmap,270)
            else-> bitmap
        }
    }

    private fun rotateBitmap(bitmap: Bitmap, degree: Int): Bitmap {
        val matrix =Matrix()
        matrix.postRotate(degree.toFloat())
        val rotateBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.width,bitmap.height,matrix,true)
        bitmap.recycle()//将不再需要的Bitmap对象回收
        return rotateBitmap
    }
}

需要注意的是,一般的程序对加载的图片压缩,这样子是为了避免直接加载到内存崩溃,好的做法是先压缩,这里大家可以自行查阅相关资料了

播放多媒体文件

Android MediaPlayer 常用方法介绍

方法:create(Context context, Uri uri)
解释:静态方法,通过Uri创建一个多媒体播放器。

方法:create(Context context, int resid)
解释:静态方法,通过资源ID创建一个多媒体播放器

方法:create(Context context, Uri uri, SurfaceHolder holder)
解释:静态方法,通过Uri和指定 SurfaceHolder 【抽象类】 创建一个多媒体播放器

方法: getCurrentPosition()
解释:返回 Int, 得到当前播放位置

方法: getDuration()
解释:返回 Int,得到文件的时间

方法:getVideoHeight()
解释:返回 Int ,得到视频的高度

方法:getVideoWidth()
解释:返回 Int,得到视频的宽度

方法:isLooping()
解释:返回 boolean ,是否循环播放

方法:isPlaying()
解释:返回 boolean,是否正在播放

方法:pause()
解释:无返回值 ,暂停

方法:prepare()
解释:无返回值,准备同步

方法:prepareAsync()
解释:无返回值,准备异步

方法:release()
解释:无返回值,释放 MediaPlayer 对象

方法:reset()
解释:无返回值,重置 MediaPlayer 对象

方法:seekTo(int msec)
解释:无返回值,指定播放的位置(以毫秒为单位的时间)

方法:setAudioStreamType(int streamtype)
解释:无返回值,指定流媒体的类型

方法:setDataSource(String path)
解释:无返回值,设置多媒体数据来源【根据 路径】

方法:setDataSource(FileDescriptor fd, long offset, long length)
解释:无返回值,设置多媒体数据来源【根据 FileDescriptor】

方法:setDataSource(FileDescriptor fd)
解释:无返回值,设置多媒体数据来源【根据 FileDescriptor】

方法:setDataSource(Context context, Uri uri)
解释:无返回值,设置多媒体数据来源【根据 Uri】

方法:setDisplay(SurfaceHolder sh)
解释:无返回值,设置用 SurfaceHolder 来显示多媒体

方法:setLooping(boolean looping)
解释:无返回值,设置是否循环播放

事件:setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)
解释:监听事件,网络流媒体的缓冲监听

事件:setOnCompletionListener(MediaPlayer.OnCompletionListener listener)
解释:监听事件,网络流媒体播放结束监听

事件:setOnErrorListener(MediaPlayer.OnErrorListener listener)
解释:监听事件,设置错误信息监听

事件:setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener)
解释:监听事件,视频尺寸监听

方法:setScreenOnWhilePlaying(boolean screenOn)
解释:无返回值,设置是否使用 SurfaceHolder 显示

方法:setVolume(float leftVolume, float rightVolume)
解释:无返回值,设置音量

方法:start()
解释:无返回值,开始播放

方法:stop()
解释:无返回值,停止播放

我们先来梳理一下MediaPlayer的工作流程

  1. 创建MediaPlayer对象
  2. 调用setDataSource()方法设置音频文件的路径
  3. 再调用prepare()方法进入准备状态
  4. start(),pause(),reset()方法

我们来看一个很简单的例子吧

这部分比较简单不再展开来说了,有兴趣自己看一下书p374页即可

播放视频

VedioView和MediaPlayer很类似,其主要方法如下:

  • setVideoPath:设置要播放的视频文件的位置
  • start:开始或继续播放视频
  • pause:暂停播放视频
  • resume:将视频从头开始播放
  • seekTo:从指定的位置开始播放视频
  • isPlaying:判断当前是否正在播放视频
  • getCurrentPosition:获取当前播放的位置
  • getDuration:获取载入的视频文件的时长
  • setVideoPath(String path):以文件路径的方式设置VideoView播放的视频源
  • setVideoURI(Uri uri):以Uri的方式设置视频源,可以是网络Uri或本地Uri
  • setMediaController(MediaController controller):设置MediaController控制器
  • setOnCompletionListener(MediaPlayer.onCompletionListener l):监听播放完成的事件
  • setOnErrorListener(MediaPlayer.OnErrorListener l):监听播放发生错误时候的事件
  • setOnPreparedListener(MediaPlayer.OnPreparedListener l):监听视频装载完成的事件

同样,这一部分比较简单详见书本p378页即可

多媒体部分完更