Notification Application: 应用程序不在前台运行且希望向用户发出提示信息时
通知渠道: 对通知信息的细分, 可以按照通知渠道控制一类通知的重要程度(是否响铃、是否振动或者是否要关闭这个渠道的通知)
Quick Start
获取通知管理对象: NotificationManager
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
创建通知渠道: NotificationChannel
1 2 3 4 5 6 7 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(channelId: String, channelName: String, importance) manager.createNotificationChannel(channel) }
设定通知触发的意图: 这里以触发一个 Activity 为例
PendingIntent can be understood as an intent to delay execution Params list is: context, 0, intent object, action (set 0 in basic use)
val intent = Intent(this , NotificationActivity::class .java)val pendingIntent = PendingIntent.getActivity(this , 0 , intent, 0 )
创建通知对象: NotificationCompat.Builder(context, channelId: String)
1 2 3 4 5 6 7 8 val notification = NotificationCompat.Builder(this , "normal" ) .setContentTitle("This is content title" ) .setContentText("This is content text" ) .setSmallIcon(R.drawable.small_icon) .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon)) .setContentIntent(pendingIntent) .setAutoCancel(true ) .build()
触发通知: manager.notify(id: Int, notification)
manager.notify(1 , notification)
长文字通知 默认情况下, 通知内容多余一行将会被 ...
截断, 可以通过 setStyle 中的 NotificationCompat.BigTextStyle() 进行富文本化
.setStyle(NotificationCompat.BigTextStyle().bigText(content))
图片通知 通过 setStyle 中的 NotificationCompat.BigPictureStyle() 进行展示
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources, R.drawable.big_image)))
Take Photo 调用摄像头媒体进行拍照并展示照片:
定义一个 File 对象用于存储拍下的照片, 避免涉及运行时权限, 选择存储在 SD 卡的应用关联缓存目录下 getExternalCacheDir()
构建目标照片的 Uri: 通过 getUriForFile()
进行获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 buttonTakePhoto.setOnClickListener { outputImage = File(externalCacheDir, "output_image.jpg" ) if (outputImage.exists()) { outputImage.delete() } outputImage.createNewFile() imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile(this , "com.example.a16cameraalbumtest.fileprovider" , outputImage) } else { Uri.fromFile(outputImage) } val intent = Intent("android.media.action.IMAGE_CAPTURE" ) intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri) ImageCaptureActivityForResult.launch(intent) }
拍照完成后相机 Activity 将数据流返回, 通过 contentResolver 与 Uri 进行获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private val ImageCaptureActivityForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri)) imageView.setImageBitmap(rotateIfRequired(bitmap)) } } 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 rotatedBitmap = Bitmap.createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true ) bitmap.recycle() return rotatedBitmap }
声明注册 provider: meta-data 中用于指定 uri 的共享路径通过 xml 资源进行赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 <provider android:name ="androidx.core.content.FileProvider" android:authorities ="com.example.a16cameraalbumtest.fileprovider" android:exported ="false" android:grantUriPermissions ="true" > <meta-data android:name ="android.support.FILE_PROVIDER_PATHS" android:resource ="@xml/file_paths" /> </provider > <paths xmlns:android ="http://schemas.android.com/apk/res/android" > <external-path name ="my_images" path ="/" /> </paths >
Select Photos 从相册中选择照片, 打开系统的文件选择器 Intent.ACTION_OPEN_DOCUMENT
指定文件类别为 Intent.CATEGORY_OPENABLE
1 2 3 4 5 6 7 buttonSelectPhoto.setOnClickListener { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "image/*" SelectPhotoForResult.launch(intent) }
选定照片后通过返回的 Uri 获取资源对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private val SelectPhotoForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK && result.data != null ) { result.data ?.data .let { uri -> if (uri != null ) { val bitmap = getBitmapFromUri(uri) imageView.setImageBitmap(bitmap) } } } } private fun getBitmapFromUri (uri: Uri ) = contentResolver.openFileDescriptor(uri, "r" )?.use { BitmapFactory.decodeFileDescriptor(it.fileDescriptor) }
Play Audio 主要通过 MediaPlayer 类实现的:
setDataSource(): 设置要播放的音频文件的位置
prepare(): 开始播放之前调用, 完成准备工作
start(): 开始/继续播放
pause()
reset(): 将 MediaPlayer 重置为刚刚创建的状态
seekTo(): 从指定位置开始播放音频
stop()
release()
isPlaying()
getDuration(): 获取载入的音频文件的时长
获取 MediaPlayer 对象 并进行初始化
1 2 3 4 5 6 7 8 9 10 11 private val mediaPalyer = MediaPlayer()private fun initMediaPlayer () { val assetManager = assets val fd = assetManager.openFd("music.mp3" ) mediaPalyer.setDataSource(fd.fileDescriptor, fd.startOffset, fd.length) mediaPalyer.prepare() }
按钮逻辑操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 when (v?.id) { R.id.play -> { if (!mediaPalyer.isPlaying) { mediaPalyer.start() } } R.id.pause -> { if (mediaPalyer.isPlaying) { mediaPalyer.pause() } } R.id.stop -> { mediaPalyer.reset() initMediaPlayer() } }
Activity 被销毁时记得释放 MediaPlayer
1 2 3 4 5 override fun onDestroy () { super .onDestroy() mediaPalyer.stop() mediaPalyer.release() }
Play Video 主要通过 VideoView 类实现的, 通过命名可以看出, 这个类将 Video 的显示与控制集一身:
setVideoPath(): 设置要播放的视频文件
resume(): 将视频从头开始播放
suspend(): 释放 VideoView
start, pause, seekTo, isPlaying, getDuration
Tips: VideoView 不支持直接播放 assets 目录下的视频资源, 但是支持播放 res/raw/*
目录下的视频资源
设置媒体对象
1 2 3 4 5 videoView = findViewById(R.id.videoView) val uri = Uri.parse("android.resource://$packageName /${R.raw.video} " )videoView.setVideoURI(uri) videoView.setMediaController(MediaController(this ))
逻辑控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 when (v?.id) { R.id.play -> { if (!videoView.isPlaying) { videoView.start() } } R.id.pause -> { if (videoView.isPlaying) { videoView.pause() } } R.id.replay -> { if (videoView.isPlaying) { videoView.resume() } } }
事后释放
1 2 3 4 override fun onDestroy () { super .onDestroy() videoView.suspend () }