Android 通过相机和系统相册获取图片,压缩,结果回调

news2024/12/27 2:45:32

一、需求背景

在常规的App开发中,很多时候需要用户上传图片来进行一些业务上的实现,例如用户反馈,图片凭证等。

二、实现功能

1.选择弹窗(即选择拍照或者相册)

2.申请权限(相机权限)

3.相机拍照回调,图片处理

4.相册选择回调,图片处理

5.图片压缩,上传服务器

三、实现步骤

1.选择弹窗

1.1 弹窗选择

  PhotoSelectDialog.Builder(this)
            .setCallClickListener(object : PhotoSelectDialog.PhotoSelectListener {
                override fun clickPhoto(dialog: Dialog) {
                    toCheckPermission()
                    dialog.dismiss()
                }

                override fun clickAlbum(dialog: Dialog) {
                    dispatchChoosePictureIntent()
                    dialog.dismiss()
                }

            }).create().show()

1.2 弹窗的代码

class PhotoSelectDialog(context: Context, themeStyle: Int) : Dialog(context, themeStyle) {
    init {
        initView()
    }

    private fun initView() {
        setContentView(R.layout.iamge_select_dialog)
    }

    class Builder(private val context: Context) {
        private var photoSelectListener: PhotoSelectListener? = null

        fun setCallClickListener(photoSelectListener: PhotoSelectListener): Builder {
            this.photoSelectListener = photoSelectListener
            return this
        }

        fun create(): PhotoSelectDialog {
            val dialog = PhotoSelectDialog(context, R.style.CustomDialogStyle)
            dialog.setCancelable(true)
            dialog.setCanceledOnTouchOutside(true)
            if (photoSelectListener != null) {
                dialog.findViewById<AppCompatImageView>(R.id.toSelectImage).setOnClickListener {
                    photoSelectListener?.clickAlbum(dialog)
                }
                dialog.findViewById<AppCompatImageView>(R.id.toTakePhoto).setOnClickListener {
                    photoSelectListener?.clickPhoto(dialog)
                }
                dialog.findViewById<AppCompatImageView>(R.id.dialog_close).setOnClickListener {
                    dialog.dismiss()
                }
            }
            return dialog
        }

    }

    interface PhotoSelectListener {
        fun clickPhoto(dialog: Dialog)
        fun clickAlbum(dialog: Dialog)
    }
}

1.3 弹窗的展示

2.申请相机权限

众所周知,像调用相机是必须要有权限。

2.1在manifest进行声明

<uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

<uses-permission android:name="android.permission.CAMERA" />

2.2 申请权限 

我使用的是EasyPermission框架,也可以自己写。

  private fun toCheckPermission() {
        if (!EasyPermissions.hasPermissions(  this,android.Manifest.permission.CAMERA,) ) {
            EasyPermissions.requestPermissions(
                this,
                getString(R.string.permission_tips),
                AppConstant.PER_CAMERA,
                android.Manifest.permission.CAMERA,
            )
        } else {
            dispatchTakePictureIntent()//打开相机
        }
    }

相关权限回调处理,可以去看我的另一篇博客

Android Permission 权限申请,EasyPermission和其他三方库-CSDN博客

3.相机拍照回调,图片处理

3.1 调起相机

private val REQUEST_IMAGE_CAPTURE = 1//请求码

// 启动相机并捕获照片的函数
private fun dispatchTakePictureIntent() {
    // 创建一个用于调用系统相机的 Intent
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // 检查是否有相机应用能够处理该 Intent
        takePictureIntent.resolveActivity(packageManager)?.also {
            // 尝试创建用于存储照片的文件
            val photoFile: File? = try {
                createImageFile() // 创建图片文件
            } catch (ex: IOException) {
                null // 处理文件创建过程中可能出现的异常
            }

            // 如果创建成功,继续执行
            photoFile?.also {
                // 获取文件的 URI,使用 FileProvider 来确保文件访问权限正确
                photoURI = FileProvider.getUriForFile(this, "com.uz.cashloanuzi.fileprovider", it)

                // 将照片文件的 URI 传递给相机应用,确保照片被保存到正确的位置
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)

                // 启动相机应用并等待结果,结果会回调到 onActivityResult
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

// 创建用于保存图片的文件
@Throws(IOException::class)
private fun createImageFile(): File {
    // 生成一个带有时间戳的文件名
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
    
    // 获取用于存储图片的目录路径,使用应用专属的外部存储
    val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)

    // 创建临时文件,文件名以 "JPEG_时间戳_" 开头,扩展名为 .jpg
    return File.createTempFile(
        "JPEG_${timeStamp}_", /* 文件前缀 */
        ".jpg", /* 文件后缀 */
        storageDir /* 存储目录 */
    ).apply {
        // 保存文件的绝对路径,供其他用途使用(这儿最开始我在用,后面没用也没注释,可以不管)
        currentPhotoPath = absolutePath
    }
}

流程请看代码注释,其中的fileprovider,需要自己在manifest中声明

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.uz.cashloanuzi.fileprovider" <!-- 指定与代码中的 `FileProvider.getUriForFile` 相同的 authorities -->
    android:exported="false" <!-- 防止其他应用直接访问你的 FileProvider,增加安全性 -->
    android:grantUriPermissions="true"> <!-- 允许你临时授予其他应用对文件的访问权限 -->
    
    <!-- FileProvider 的路径配置 -->
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS" <!-- 配置文件的路径信息 -->
        android:resource="@xml/file_paths" /> <!-- 引用 XML 文件,定义哪些目录可以被共享 -->
</provider>

file_paths文件

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 共享外部存储图片目录 -->
    <external-files-path
        name="my_images"
        path="Pictures/" />
</paths>

tips:@xml/file_paths 指的是一个 XML 文件,你需要在 res/xml 目录下创建这个文件,告诉 FileProvider 你允许分享的文件路径。通常这个文件包含一个 <paths> 标签,并定义了可共享的目录。

3.2 图片回调处理

@SuppressLint("SuspiciousIndentation")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQUEST_IMAGE_CAPTURE -> {//拍照
                    val bitmap: Bitmap =
                        MediaStore.Images.Media.getBitmap(this.contentResolver, photoURI)
                    val file = createFileFromBitmap(bitmap)//创建一个文件,且压缩
                    val fileSizeInKB = file.length().div(1024)
                    val fileSizeInMB = fileSizeInKB / 1024
                    LogUtils.e("file Size", fileSizeInMB.toString())
                    uploadImage(file) { isSus, result ->//接口上传
                        if (isSus) {
                            toShowPic(bitmap)//展示
                        } else {
                            ToastUtils.makeText(getString(R.string.upload_failed))
                        }
                    }


                }

                REQUEST_IMAGE_PICK -> {//相册
                    data?.data?.let { uri ->
                        val bitmap: Bitmap =
                            MediaStore.Images.Media.getBitmap(this.contentResolver, uri)
                        val file = createFileFromBitmap(bitmap)//创建一个文件,且压缩
                        uploadImage(file) { isSus, result ->//接口上传
                            if (isSus) {
                                toShowPic(bitmap)//展示到UI上
                            } else {
                                ToastUtils.makeText(getString(R.string.upload_failed))
                            }
                        }

                    }
                }
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            if (requestCode == REQUEST_IMAGE_CAPTURE) {
                 ToastUtils.makeText("拍照取消"))
            } else if (requestCode == REQUEST_IMAGE_PICK) {                                                 
             ToastUtils.makeText("相册获取图片取消")
            }
        }

    }

4.相册选择回调,图片处理

4.1 打开相册

private val REQUEST_IMAGE_PICK = 2
private fun dispatchChoosePictureIntent() {//ACTION_PICK 相册选择
        Intent(
            Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        ).also { choosePictureIntent ->
            choosePictureIntent.type = "image/*"
            startActivityForResult(choosePictureIntent, REQUEST_IMAGE_PICK)
        }
    }

4.2 相册回调

即上面onActivityResult里的方法

5.图片压缩,上传服务器

5.1 图片压缩(如果后端需要对图片有要求,得压缩)

// 将 Bitmap 转换为文件并进行压缩,保证文件大小不超过 2MB
private fun createFileFromBitmap(bitmap: Bitmap): File {
    // 使用当前时间戳生成唯一的文件名
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
    
    // 获取应用的外部图片存储目录
    val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    
    // 创建文件对象,文件名为 JPEG_加上时间戳,并且以 .jpg 结尾
    val file = File(storageDir, "JPEG_${timeStamp}_.jpg")
    
    // 创建一个文件输出流,准备将字节数组写入文件
    val fos = FileOutputStream(file)
    
    // 初始化图片的质量为 100(最高质量)
    var quality = 100
    
    // 将 Bitmap 转换为字节数组,并指定初始的质量
    var byteArray = convertBitmapToByteArray(bitmap, quality)
    
    // 输出初始的图片大小
    LogUtils.e("image size", byteArray.size.toString())
    
    // 如果图片大小超过 2MB,并且质量大于 10,就继续压缩
    while (byteArray.size > 2 * 1024 * 1024 && quality > 10) {
        quality -= 10  // 每次减少 10% 的质量
        byteArray = convertBitmapToByteArray(bitmap, quality)  // 重新生成字节数组
    }
    
    // 输出压缩后的图片大小
    LogUtils.e("deal image size", byteArray.size.toString())
    
    // 将最终的字节数组写入文件
    fos.write(byteArray)
    
    // 刷新并关闭文件输出流,确保数据写入完成
    fos.flush()
    fos.close()
    
    // 返回创建的文件对象
    return file
}

5.2 图片上传

我的Retrofit请求自己又封了一下,这儿就不粘贴。传文件和普通接口会有些不同,注意一下就好了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2134572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

油耳用什么掏耳朵比较好?可视挖耳勺推荐平价

掏耳朵是一个轻松又舒服的感觉&#xff0c;很多人就会用棉签和普通耳勺越掏越进&#xff0c;在盲掏的过程中容易弄伤耳膜。所以我们在掏耳时要选好工具。市面上的智能可视挖耳勺&#xff0c;顶端带有摄像头&#xff0c;可以通过清楚的观察到耳道中的情况。但现在市面上关于可视…

【Unity学习心得】如何使用Unity制作“饥荒”风格的俯视角2.5D游戏

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、需要导入的素材二、要实现的步骤 俯视角2D人物移动控制2.5D风格的实现使用协程实现相机绕玩家旋转效果总结 前言 由于要找工作开始重新拾起学习Unity&#…

系统资源智能管理:zTasker软件的监控与优化

在创新的引领下&#xff0c;科技不断迭代升级&#xff0c;为我们应对快节奏生活的挑战提供了强大的工具。它让我们在协调工作与家庭的同时&#xff0c;也能保持内心的宁静与平衡——而自动化工具的出现&#xff0c;正是科技力量在提升工作效率和生活质量方面的体现。zTasker&am…

System.out源码解读——err 和 out 一起用导致的顺序异常Bug

前言 笔者在写一个小 Demo 的过程中&#xff0c;发现了一个奇怪的问题。问题如下&#xff1a; // 当 flagtrue 时打印 a1 &#xff1b;当 flagfalse 时打印 a2。 public static void main(String[] args) {boolean flag false;for (int i 0; i < 10; i) {if (flag) {Sys…

AI 与大模型如何助力金融研发效能最大化?

在金融行业&#xff0c;技术创新与严格合规的需求并行存在&#xff0c;推动着研发团队不断寻求更高效的解决方案。面对日益增长的市场竞争和技术进步&#xff0c;金融机构必须迅速适应变化&#xff0c;同时确保所有创新措施都符合监管要求。这种需求催生了对高效研发流程和先进…

深入掌握:如何进入Docker容器并运行命令

感谢浪浪云支持发布 浪浪云活动链接 &#xff1a;https://langlangy.cn/?i8afa52 文章目录 查看正在运行的容器使用 docker exec 命令进入容器进入容器的交互式 shell在容器中运行命令 使用 docker attach 命令附加到容器检查容器日志退出容器从 docker exec 方式退出从 docke…

趣味SQL | 从围棋收官到秦楚大战的数据库SQL语言实现

目录 0 前言 1 秦孝公大战商鞅 2 收官类型与城池特征 3 收官顺序与攻城策略 4 秦孝公展示SQL神功 5 写在最后 欲知后事如何&#xff0c;想进一步了解SQL这门艺术语言的&#xff0c;可以订阅我的专栏数字化建设通关指南&#xff0c;且听下回分解。专栏 原价99&#xff0c…

MacBook上怎么查找历史复制记录?

你是否经常遇到这样的情况:做内容或方案时,需要用到素材就去找,找到后回来粘贴,然后再去找,再回来粘贴?这个过程是不是很繁琐? 那么找到的素材要不要保存下来呢?每个都存成文件似乎太麻烦了。但如果不单独保存,过两天想再利用又找不到了,怎么办? 在网上看到的一段好文案、…

解锁头条创作新纪元:文字游侠AI工具助你解放双手 ,一键生成爆文!

如今&#xff0c;自媒体创作早已不再是专业人士的专属领地&#xff0c;而是成为了普通人轻创首选的新途径。然而&#xff0c;对于许多想要通过自媒体创业的朋友来说&#xff0c;创作内容的难度和耗时却成为了不可忽视的障碍。 今天&#xff0c;为大家揭秘一款颠覆性的AI写作神…

【附源码】用Python开发一个音乐下载工具,并打包EXE文件,所有音乐都能搜索下载!

现在听个歌&#xff0c;不是要这就是要那&#xff0c;乱七八糟的&#xff0c;下软件都下不赢。 于是决定加班熬夜来做一个&#xff0c;想怎么听就怎么听&#xff0c;大家自己看到就好&#xff0c;悄悄用&#xff0c;别告诉别人哈~ 好了不闲聊&#xff0c;开整&#xff01; 首先…

新书速览|循序渐进Vue.js 3.x前端开发实践

《循序渐进Vue.js 3.x前端开发实践》 本书内容 《循序渐进Vue.js 3.x前端开发实践》由一位拥有丰富前端开发经验的架构师撰写&#xff0c;旨在通过详尽的理论知识讲解和丰富的实践练习&#xff0c;帮助初学者深入掌握Vue.js框架&#xff0c;并能够独立开发商业级别的Web应用程…

【题解】CF1993D

目录 翻译思路总代码 翻译 原题链接 思路 容易发现&#xff0c;无论如何操作&#xff0c;最后剩下的数量是一定的&#xff0c;记剩下的数组中中位数的位置为 m m m&#xff08;从1开始记&#xff09;&#xff0c;注意不能将数组删空。有&#xff1a; 剩余数组的长度 L ( n …

windows@移除资源管理器中的网盘等软件的图标@一键移除方案

文章目录 abstract设置方案移除注册表(不推荐单独使用)设置访问权限GUI设置powershell方案 利用powershell设置相应注册表(一键执行脚本)移除所有用户对指定注册表路径的访问权限移除所有权限但保留管理员&#x1f47a; abstract 国内的云盘等软件比如百度网盘,夸克网盘,wps等…

轻量级模型汇总解读——涉及MobileNet、ShuffleNet、GhostNet、EfficientNet、NasNet、轻量transformer

前言&#xff1a;最近需要将模型移植到瑞芯微rv1106上运行&#xff0c;相比于rv1126 NPU的2.0T算力&#xff0c;它的算力更小&#xff0c;只支持0.5T的算力&#xff0c;而且rv1106目前只支持int8量化&#xff0c;为了保证模型推理在满足精度要求的情况下&#xff0c;保证时间尽…

基于C++实现(控制台)停车场管理系统

停车场管理系统设计报告 1 需求分析 1.1问题描述 停车场内只有一个可停放 n 辆汽车的狭长通道&#xff0c;且只有一个大门可供汽车进出。 汽车在停车场内按车辆到达时间的先后顺序&#xff0c;依次由北向南排列&#xff08;大门在最南端&#xff0c;最先到达的第一辆车停放…

Python_两个jpg图片文件名称互换

项目场景 处理Adobe Photoshop导出的两个切片的顺序错误问题 小编在进行图片切片处理的时候&#xff0c;发现用PS导出的切片顺序错误&#xff0c;例如用PS导出的切片分别为test_01.jpg&#xff0c;test_02.jpg&#xff0c;但实际的使用需求是将两个图片的顺序调换&#xff0c…

IC开发——Verilog简明教程

1. 基础概念 1.1. 逻辑值 逻辑0&#xff0c;低电平&#xff0c;对应电路中接地GND。 逻辑1&#xff0c;高电平&#xff0c;对应电路中的电源VCC。 逻辑Z&#xff0c;高阻态&#xff0c;对应电路的悬空。 逻辑X&#xff0c;未知态&#xff0c;数据仿真中可能存在&#xff0c;如…

Delphi Web和Web服务开发目前有哪些选择

Delphi Web和Web服务开发目前有哪些选择 Delphi Web和Web服务开发目前有以下几个选择&#xff1a; Delphi MVC Framework&#xff08;https://github.com/delphimvcframework/delphimvcframework&#xff09;&#xff1a;这是一个开源的Delphi Web框架&#xff0c;基于MVC&am…

小程序uniapp关闭手势返回操作

需求&#xff1a;进入当前页面后&#xff0c;无法返回其他页面&#xff0c;禁止所有返回操作&#xff08;手势返回、左上角返回按钮等&#xff09; 解决&#xff1a; 方法一&#xff1a;wx.enableAlertBeforeUnload wx.enableAlertBeforeUnload 在onLoad里调用&#xff1a; on…

-isystem isystem 实验记录

1&#xff0c;isystem 的理论 2&#xff0c;实验方案 $ tree . ├── inc111 │ └── test.h ├── inc222 │ └── test.h └── src ├── a.out └── hello.c inc111/test.h: #pragma once#define NUM 111 inc222/test.h #pragma once#define N…