第九章 多媒体
-
通知
-
基础
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) -
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) -
进阶
-
显示长文字
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() -
通知重要等级
具体可以看设置里的相关通知设置
-
-
-
调用摄像头和相册
-
调用摄像头
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> -
从相册中选择文件
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) }
-
-
播放多媒体文件
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() } } -
Kotlin课堂
infix函数infix fun String.beginWith(prefix:String)= startsWith(prefix)if ("hello" beginWith "1") { // todo }infix函数允许我们将函数调用时的小数点、括号等计算机相关的语法去掉。限制:
- 不能定义成顶层函数,必须是某个类的成员函数或者可以使用扩展函数的方式定义到某个类中
- infix函数必须接收且只能接收一个函数