IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Jetpack 之 ActivityResultContract的使用,调用相机、相册并裁剪的优雅实现 -> 正文阅读

[移动开发]Jetpack 之 ActivityResultContract的使用,调用相机、相册并裁剪的优雅实现

自从Google官方发布Jetpack以来,我们Android开发的很多开发习惯都发生了巨大的变化,最近又双叒叕在实现更换头像的功能。发现以前startActivityForResult + onActivityResult 那一套做法又有了新的实现方式。略微找了下相关资料,动手实现了出来,发现代码能够更简洁也更优雅了,感觉收获了一点小惊喜。所以这里给大家分享一下。

以前请求权限以及startActivityForResult,都免不了要实现onActivityResult,代码分离,逻辑不连贯,还要生命一连串code,一个不留神就功能对应不上,浪费时间排查。现在使用registerForActivityResult + ActivityResultContract来实现就更加方便了。

官方已经帮我们封装好了一部分可以直接使用,列在下面就不多做介绍了:

ActivityResultContracts.CaptureVideo, 
ActivityResultContracts.CreateDocument, 
ActivityResultContracts.GetContent, 
ActivityResultContracts.GetMultipleContents, 
ActivityResultContracts.OpenDocumentTree, 
ActivityResultContracts.OpenDocument, 
ActivityResultContracts.OpenMultipleDocuments,
ActivityResultContracts.PickContact, ActivityResultContracts.RequestMultiplePermissions, ActivityResultContracts.RequestPermission,
ActivityResultContracts.StartActivityForResult,
ActivityResultContracts.StartIntentSenderForResult,
ActivityResultContracts.TakePicturePreview,
ActivityResultContracts.TakePicture,
ActivityResultContracts.TakeVideo,
ActivityResultContracts.WatchFaceEditorContract

这里主要介绍自定义Contract来实现打开相机拍照、打开相册选择照片,然后进行裁剪是如何实现的。

一、首先,先继承ActivityResultContract实现自定义的Contract,如下:

SelectPhotoContract:

/**
 * 选择照片的协定
 */
class SelectPhotoContract : ActivityResultContract<Unit?, Uri?>() {
    companion object {
        private const val TAG = "SelectPhotoContract"
    }

    override fun createIntent(context: Context, input: Unit?): Intent {
        return Intent(Intent.ACTION_PICK).setType("image/*")
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        Logger.d(TAG, "Select photo uri: ${intent?.data}")
        return intent?.data
    }
}

TakePhotoContract:

/**
 * 拍照协定
 */
class TakePhotoContract : ActivityResultContract<Unit?, Uri?>() {
    companion object {
        private const val TAG = "TakePhotoContract"
    }

    private var uri: Uri? = null

    override fun createIntent(context: Context, input: Unit?): Intent {
        val mimeType = "image/jpeg"
        val fileName = "IMG_${System.currentTimeMillis()}.jpg"
        uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 10 及以上获取图片uri
            val values = contentValuesOf(
                Pair(MediaStore.MediaColumns.DISPLAY_NAME, fileName),
                Pair(MediaStore.MediaColumns.MIME_TYPE, mimeType),
                Pair(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
            )
            context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        } else {
            // Android 9 及以下获取图片uri
            FileProvider.getUriForFile(
                context, "${context.packageName}.provider",
                File(context.externalCacheDir, "/$fileName")
            )
        }
        return Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        Logger.d(TAG, "Take photo, resultCode: $resultCode, uri: $uri")
        if (resultCode == Activity.RESULT_OK) return uri
        return null
    }
}

CropPhotoContract:

/**
 * 裁剪照片的协定
 */
class CropPhotoContract : ActivityResultContract<Uri, CropPhotoContract.CropOutput?>() {
    companion object {
        private const val TAG = "CropPhotoContract"
    }

    private var output: CropOutput? = null

    override fun createIntent(context: Context, input: Uri): Intent {
        // 获取输入图片uri的媒体类型
        val mimeType = context.contentResolver.getType(input)
        // 创建新的图片名称
        val fileName = "IMG_${System.currentTimeMillis()}.${
            MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
        }"
        val outputUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 10 及以上获取图片uri
            val values = contentValuesOf(
                Pair(MediaStore.MediaColumns.DISPLAY_NAME, fileName),
                Pair(MediaStore.MediaColumns.MIME_TYPE, mimeType),
                Pair(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
            )
            context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        } else {
            Uri.fromFile(File(context.externalCacheDir!!.absolutePath, fileName))
        }
        output = CropOutput(outputUri!!, fileName)
        return Intent("com.android.camera.action.CROP")
            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            .setDataAndType(input, "image/*")
            .putExtra("outputX", 300)
            .putExtra("outputY", 300)
            .putExtra("aspectX", 1)
            .putExtra("aspectY", 1)
            .putExtra("scale", true)
            .putExtra("crop", true)
            .putExtra("return-data", false) // 在小米手机部分机型中 如果直接返回Data给Intent,图片过大的时候会有问题
            .putExtra("noFaceDetection", true)
            .putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
            .putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())
    }

    override fun parseResult(resultCode: Int, intent: Intent?): CropOutput? {
        Logger.d(TAG, "Crop photo, resultCode: $resultCode output: $output")
        if (resultCode == Activity.RESULT_OK) return output
        return null
    }

    data class CropOutput(val uri: Uri, val fileName: String) {
        override fun toString(): String {
            return "{ uri: $uri, fileName: $fileName }"
        }
    }
}

二、然后,在Activity或者Fragment中注册:

/**
     * 打开相机拍照,并前往裁剪
     */
    private val takePhotoLauncher = (this as ComponentActivity).registerForActivityResult(TakePhotoContract()) { uri ->
        uri?.let {
            cropPhotoLauncher.launch(it)
        }
    }

    /**
     * 前往相册选择照片,并前往裁剪
     */
    private val selectPhotoLauncher = (this as ComponentActivity).registerForActivityResult(SelectPhotoContract()) { uri ->
        uri?.let {
            cropPhotoLauncher.launch(it)
        }
    }

    /**
     * 前往裁剪
     */
    private val cropPhotoLauncher = (this as ComponentActivity).registerForActivityResult(CropPhotoContract()) { output ->
        output?.let {
            Logger.d(TAG, "裁剪完成,开始上传头像到服务器, output: $it")
            // 上传头像
            showLoading(getString(R.string.base_uploading))
            mViewModel.fetchUpload(ContentUriRequestBody(contentResolver, it.uri), it.fileName)
        }
    }

三、调用:

// 打开相机
takePhotoLauncher.launch(null)
// 打开相册
selectPhotoLauncher.launch(null)

OVER

很简单,不是么!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 19:02:44  更:2022-01-30 19:03:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 2:30:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码