第九章 多媒体

192 阅读3分钟

第九章 多媒体

  1. 通知

    1. 基础

      Android 8.0系统引入了通知渠道概念,每条通知都要属于一个对应的渠道。比如私信、新闻、点赞等

      // 创建渠道
      val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          notificationManager.createNotificationChannel(
            	// 渠道ID,渠道名称(展示给用户,设置和长按通知可以看到),通知的重要等级
              NotificationChannel(
                  "like", "点赞", NotificationManager.IMPORTANCE_DEFAULT
              )
          )
      }
      

      创建通知:

      // 使用NotificationCompat保证各Android版本行为一致,"like"为通知渠道
      val build = NotificationCompat.Builder(this, "like")
          .setContentTitle("This is content title")
          .setContentText("This is content text")
          .setSmallIcon(R.drawable.apple_pic)
          .setLargeIcon(
              BitmapFactory.decodeResource(resources, R.drawable.ic_launcher_foreground)
          )
          .build()
      // 1为id,保证为每个通知指定的id都不同
      notificationManager1.notify(1, build)
      
    2. PandingIntent

      从名字上看与Intent类似,PendingIntent倾向于在某个合适的时机执行某个动作。

      PendingIntent提供了几个静态方法用于获取PendingIntent实例,可以根据需求选择是使用getActivity()方法、 getBroadcast() 方法、还是getService()方法。

      val intent = Intent(this, Chapter9Activity::class.java)
      // 第二个参数一般为0,第三个参数为Intent,第四个参数通常为0
      val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
      
      val build = NotificationCompat.Builder(this, "like")
          .setContentTitle("This is content title")
          .setContentText("This is content text")
          .setSmallIcon(R.drawable.apple_pic)
          .setLargeIcon(
              BitmapFactory.decodeResource(resources, R.drawable.ic_launcher_foreground)
          )
      		// 设置PendingIntent,点击效果
          .setContentIntent(pendingIntent)
      		// 点击后图标消失
          .setAutoCancel(true)
          .build()
      notificationManager1.notify(1, build)
      

      取消通知:

      val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
      // 取消通知,id为发送时的id
      notificationManager.cancel(1)
      
    3. 进阶

      • 显示长文字

        val build = NotificationCompat.Builder(this, "like")
                    ...
                    .setStyle(NotificationCompat.BigTextStyle().bigText("最近使用的应用屏幕(也称为概览屏幕、近期任务列表或最近用过的应用)是一个系统级界面,上面列出了最近访问过的 Activity 和任务。用户可以浏览该列表并选择要恢复的任务,也可以通过滑开操作将任务从列表中移除。"))
        						...
                    .build()
        
      • 显示大图

        val build = NotificationCompat.Builder(this, "like")
           ...     .setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources, R.drawable.banana_pic)))
            ...
            .build()
        
      • 通知重要等级

        具体可以看设置里的相关通知设置

  2. 调用摄像头和相册

    1. 调用摄像头

      package com.youngly.firstlineofcode.chapter9
      
      import android.app.Activity
      import android.content.Intent
      import android.graphics.Bitmap
      import android.graphics.BitmapFactory
      import android.graphics.Matrix
      import android.media.ExifInterface
      import android.net.Uri
      import android.os.Build
      import android.os.Bundle
      import android.provider.MediaStore
      import android.view.View
      import android.view.View.OnClickListener
      import android.widget.ImageView
      import android.widget.TextView
      import androidx.core.content.FileProvider
      import com.youngly.firstlineofcode.BaseActivity
      import com.youngly.firstlineofcode.R
      import java.io.File
      
      class CameraActivity : BaseActivity(), OnClickListener {
          private val takePhotoCode = 1
          private lateinit var imageView: ImageView
          private lateinit var outputImage: File
          private lateinit var imageUri: Uri
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_camera)
              imageView = findViewById(R.id.imageView)
              findViewById<TextView>(R.id.takePhoto).setOnClickListener(this)
          }
      
          override fun onClick(v: View?) {
              // 创建File对象,用于存储拍照后的图片,externalCacheDir /sdcard/Android/data/<package name>/cache
              // 读写应用相关联的存储区域不需要申请SD权限
              outputImage = File(externalCacheDir, "camara_image.jpg")
              if (outputImage.exists()) {
                  outputImage.delete()
              }
              outputImage.createNewFile()
              // Android 7.0开始,直接使用本地真实路径的Uri被认为是不安全的。
              imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                  // 第二个参数可以是任意唯一的字符串
                  FileProvider.getUriForFile(
                      this,
                      "com.youngly.firstlineofcode.fileprovider",
                      outputImage
                  )
              } else {
                  // 将File对象转化成相应的Uri对象
                  Uri.fromFile(outputImage)
              }
              val intent = Intent("android.media.action.IMAGE_CAPTURE")
              intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
              startActivityForResult(intent, takePhotoCode)
          }
      
          override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
              super.onActivityResult(requestCode, resultCode, data)
              when (requestCode) {
                  takePhotoCode -> {
                      if (resultCode == Activity.RESULT_OK) {
                          val bitmap =
                              BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
                          imageView.setImageBitmap(rotateIfRequired(bitmap))
                      }
                  }
              }
          }
      
          private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
              val exifInterface = ExifInterface(outputImage.path)
              val orientation = exifInterface.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 createBitmap =
                  Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
              // 将不再需要的bitmap回收
              bitmap.recycle()
              return createBitmap
          }
      }
      
      // 在Manifest.xml中添加如下配置
      <provider
          android:name="androidx.core.content.FileProvider"
          android:authorities="com.youngly.firstlineofcode.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true">
          <meta-data
              android:name="android.support.FILE_PROVIDER_PATHS"
              android:resource="@xml/file_paths" />
      </provider>
      

      创建file_paths.xml文件

      <?xml version ="1.0" encoding ="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android">    // name任意    <external-path name="camara_image.jpg" path="/" /></paths>
      
    2. 从相册中选择文件

      val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
      intent.addCategory(Intent.CATEGORY_OPENABLE)
      intent.type = "image/*"
      startActivityForResult(intent, fromAlbum)
      
      if (resultCode == Activity.RESULT_OK && data != null) {
          data.data?.let {
              val bitmapFromUri = getBitmapFromUri(it)
              imageView.setImageBitmap(bitmapFromUri)
          }
      }
      
      private fun getBitmapFromUri(uri: Uri)  = contentResolver.openFileDescriptor(uri, "r")?.use {
          BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
      }
      
  3. 播放多媒体文件

    package com.youngly.firstlineofcode.chapter9
    
    import android.media.MediaPlayer
    import android.net.Uri
    import android.os.Bundle
    import android.provider.MediaStore
    import android.view.View
    import android.widget.VideoView
    import com.youngly.firstlineofcode.BaseActivity
    import com.youngly.firstlineofcode.R
    
    class MediaActivity : BaseActivity() {
        private val mediaPlayer = MediaPlayer()
        private lateinit var videoView:VideoView
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_media)
            videoView = findViewById<VideoView>(R.id.videoView)
            initMediaPlayer()
        }
    
        private fun initMediaPlayer() {
            val openFd = assets.openFd("张学友-烦恼歌.mp3")
            mediaPlayer.setDataSource(openFd.fileDescriptor, openFd.startOffset, openFd.length)
            mediaPlayer.prepare()
    
            val parse = Uri.parse("android.resource:://$packageName/${R.raw.screen}")
            videoView.setVideoURI(parse)
        }
    
        fun start(view: View) {
            if (!mediaPlayer.isPlaying) {
                mediaPlayer.start()
            }
        }
        fun pause(view: View) {
            if (mediaPlayer.isPlaying) {
                mediaPlayer.pause()
            }
        }
        fun stop(view: View) {
            if (mediaPlayer.isPlaying) {
                mediaPlayer.stop()
            }
        }
    
        fun vStart(view: View) {
            if (!videoView.isPlaying) {
                videoView.start()
            }
        }
        fun vPause(view: View) {
            if (videoView.isPlaying) {
                videoView.pause()
            }
        }
        fun vStop(view: View) {
            if (videoView.isPlaying) {
                videoView.resume()
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
          	// 释放资源
            mediaPlayer.stop()
            mediaPlayer.release()
            videoView.suspend()
        }
    }
    
  4. Kotlin课堂

    infix函数

    infix fun String.beginWith(prefix:String)= startsWith(prefix)
    
    if ("hello" beginWith "1") {
      // todo
    }
    

    infix函数允许我们将函数调用时的小数点、括号等计算机相关的语法去掉。

    限制:

    1. 不能定义成顶层函数,必须是某个类的成员函数或者可以使用扩展函数的方式定义到某个类中
    2. infix函数必须接收且只能接收一个函数