安卓拍照、裁切、选取图片实践
创始人
2025-05-31 06:50:52
0

安卓拍照、裁切、选取图片实践

前言

最近项目里面有用到裁切功能,没弄多复杂,就是系统自带的,顺便就总结了一下系统拍照、裁切、选取的使用。网上的资料说实话真是没什么营养,但是Android官网上的说明也有点太简单了,真就要实践出真理。

拍照

本来拍照是没什么难度的,不就是调用intent去系统相机拍照么,但是由于文件权限问题,Uri这东西就能把人很头疼。下面是代码(onActivityResult见后文):

    private fun openCamera() {val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)// 应用外部私有目录:files-Picturesval picFile = createFile("Camera")val photoUri = getUriForFile(picFile)// 保存路径,不要uri,读取bitmap时麻烦picturePath = picFile.absolutePath// 给目标应用一个临时授权intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)//android11以后强制分区存储,外部资源无法访问,所以添加一个输出保存位置,然后取值操作intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)startActivityForResult(intent, REQUEST_CAMERA_CODE)}private fun createFile(type: String): File {// 在相册创建一个临时文件val picFile = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"${type}_${System.currentTimeMillis()}.jpg")try {if (picFile.exists()) {picFile.delete()}picFile.createNewFile()} catch (e: IOException) {e.printStackTrace()}// 临时文件,后面会加long型随机数
//        return File.createTempFile(
//            type,
//            ".jpg",
//            requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
//        )return picFile}private fun getUriForFile(file: File): Uri {// 转换为urireturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//适配Android 7.0文件权限,通过FileProvider创建一个content类型的UriFileProvider.getUriForFile(requireActivity(),"com.xxx.xxx.fileProvider", file)} else {Uri.fromFile(file)}}

简单说明

这里的file是使用getExternalFilesDir和Environment.DIRECTORY_PICTURES生成的,它的文件保存在应用外部私有目录:files-Pictures里面。这里要注意不能存放在内部的私有目录里面,不然是无法访问的,外部私有目录虽然也是私有的,但是外面是可以访问的,这里拿官网上的说明:

在搭载 Android 9(API 级别 28)或更低版本的设备上,只要您的应用具有适当的存储权限,就可以访问属于其他应用的应用专用文件。为了让用户更好地管理自己的文件并减少混乱,以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被授予了对外部存储空间的分区访问权限(即分区存储)。启用分区存储后,应用将无法访问属于其他应用的应用专属目录。

Uri的获取

再一个比较麻烦的就是Uri的获取了,网上有一大堆资料,不过我这也贴一下,网上的可能有问题。

manifest.xml

        

res -> xml -> file_paths.xml




ps. 注意authorities这个最好填自己的包名,不然有两个应用用了同样的authorities,后面的应用会安装不上。

打开相册

这里打开相册用的是SAF框架,使用intent去选取(onActivityResult见后文)。

    private fun openAlbum() {val intent = Intent()intent.type = "image/*"intent.action = "android.intent.action.GET_CONTENT"intent.addCategory("android.intent.category.OPENABLE")startActivityForResult(intent, REQUEST_ALBUM_CODE)}

裁切

裁切这里比较麻烦,参数比较多,而且Uri那里有坑,不能使用provider,再一个就是图片传递那因为安卓版本变更,不会传略缩图了,很坑。

    private fun cropImage(path: String) {cropImage(getUriForFile(File(path)))}private fun cropImage(uri: Uri) {val intent = Intent("com.android.camera.action.CROP")// Android 7.0需要临时添加读取Url的权限intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
//        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)intent.setDataAndType(uri, "image/*")// 使图片处于可裁剪状态intent.putExtra("crop", "true")// 裁剪框的比例(根据需要显示的图片比例进行设置)
//        if (Build.MANUFACTURER.contains("HUAWEI")) {
//            //硬件厂商为华为的,默认是圆形裁剪框,这里让它无法成圆形
//            intent.putExtra("aspectX", 9999)
//            intent.putExtra("aspectY", 9998)
//        } else {
//            //其他手机一般默认为方形
//            intent.putExtra("aspectX", 1)
//            intent.putExtra("aspectY", 1)
//        }// 设置裁剪区域的形状,默认为矩形,也可设置为圆形,可能无效// intent.putExtra("circleCrop", true);// 让裁剪框支持缩放intent.putExtra("scale", true)// 属性控制裁剪完毕,保存的图片的大小格式。太大会OOM(return-data)
//        intent.putExtra("outputX", 400)
//        intent.putExtra("outputY", 400)// 生成临时文件val cropFile = createFile("Crop")// 裁切图片时不能使用provider的uri,否则无法保存
//        val cropUri = getUriForFile(cropFile)val cropUri = Uri.fromFile(cropFile)intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri)// 记录临时位置cropPicPath = cropFile.absolutePath// 设置图片的输出格式intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())// return-data=true传递的为缩略图,小米手机默认传递大图, Android 11以上设置为true会闪退intent.putExtra("return-data", false)startActivityForResult(intent, REQUEST_CROP_CODE)}

回调处理

下面是对上面三个操作的回调处理,一开始我觉得uri没什么用,还制造麻烦,后面发现可以通过流打开uri,再去获取bitmap,好像又不是那么麻烦了。

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (resultCode == RESULT_OK) {when(requestCode) {REQUEST_CAMERA_CODE -> {// 通知系统文件更新
//                    requireContext().sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
//                        Uri.fromFile(File(picturePath))))if (!enableCrop) {val bitmap = getBitmap(picturePath)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}else {cropImage(picturePath)}}REQUEST_ALBUM_CODE -> {data?.data?.let { uri ->if (!enableCrop) {val bitmap = getBitmap("", uri)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}else {cropImage(uri)}}}REQUEST_CROP_CODE -> {val bitmap = getBitmap(cropPicPath)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}}}}private fun getBitmap(path: String, uri: Uri? = null): Bitmap? {var bitmap: Bitmap?val options = BitmapFactory.Options()// 先不读取,仅获取信息options.inJustDecodeBounds = trueif (uri == null) {BitmapFactory.decodeFile(path, options)}else {val input = requireContext().contentResolver.openInputStream(uri)BitmapFactory.decodeStream(input, null, options)}// 预获取信息,大图压缩后加载val width = options.outWidthval height = options.outHeightLog.d("TAG", "before compress: width = " +options.outWidth + ", height = " + options.outHeight)// 尺寸压缩var size = 1while (width / size >= MAX_WIDTH || height / size >= MAX_HEIGHT) {size *= 2}options.inSampleSize = sizeoptions.inJustDecodeBounds = falsebitmap = if (uri == null) {BitmapFactory.decodeFile(path, options)}else {val input = requireContext().contentResolver.openInputStream(uri)BitmapFactory.decodeStream(input, null, options)}Log.d("TAG", "after compress: width = " +options.outWidth + ", height = " + options.outHeight)// 质量压缩val baos = ByteArrayOutputStream()bitmap!!.compress(Bitmap.CompressFormat.JPEG, 80, baos)val bais = ByteArrayInputStream(baos.toByteArray())options.inSampleSize = 1bitmap = BitmapFactory.decodeStream(bais, null, options)return bitmap}

这里还做了一个图片的质量压缩和采样压缩,需要注意的是采样压缩的采样率只能是2的倍数,如果需要按任意比例采样,需要用到Matrix,不是很难,读者可以研究下。

权限问题

如果你发现你没有申请权限,那你的去申请一下相机权限;如果你发现你还申请了储存权限,那你可以试一下去掉储存权限,实际还是可以使用的,因为这里并没有用到外部储存,都是应用的私有储存内,具体关于储存的适配,可以看我转载的这几篇文章,我觉得写的非常好:

Android 存储基础

Android 10、11 存储完全适配(上)

Android 10、11 存储完全适配(下)

结语

以上代码都经过我这里实践了,确认了可用,可能写法不是最优,可以避免使用绝对路径,只使用Uri。至于请求码、布局什么的,读者自己改一下加一个就行,核心部分已经在这了。如果需要完整代码,等我下篇文章再加点内容再说喽!

相关内容

热门资讯

关于会计职业生涯规划书模板 会... 篇一:会计职业生涯规划模板  今天站在哪里并不重要,最重要的是你下一步迈向哪里。职业生涯规划对于每个...
工商管理专业职业生涯规划书 工...  工商管理专业职业生涯规划书(一)  一、自我分析  我是一个真诚的人。为什么我会把真诚写在第一位?...
药学专业职业生涯规划书 大专药...  药学专业职业生涯规划书(一)  岁月流光,转瞬即逝,我将迈入大一的生活中去。迈入新的一年里,应该有...
【TypeScript 入门】... 前两个章节都是介绍 TypeScript 中最基础的语法和特性的,这一章,我们讲 TypeScrip...
语义分割网络论文集锦 年份会议/期刊标题内容架构图2015CVPRFully Convolutional Networks...
数学系职业生涯规划书 数学类职...   数学系职业生涯规划书(一)  数学是解决问题的语言,是所有科学和技术的核心。接受数学教育的优点是...
国贸系职业生涯规划书 国贸专业...   国贸系职业生涯规划书(一)  一、前言:职业规划意义  每一个人都拥有自己的梦想,然而并非每一个...
哲学系职业生涯规划书范文 分析...  哲学系职业生涯规划书(一)  一、前言  未来,掌握在自己手中。踏着时光车轮,我已走到23岁的年轮...
法学系职业生涯规划书 法学职业...   法学系职业生涯规划书(一)  一、前言  从我们走入大学校门到现在已经快要一年了,现在我们的大一...
物流管理专业职业生涯规划书 物... 第一部分 物流管理师职业前景分析  我国物流产业兴起于上世纪 90年代,起步虽晚,但发展势头强劲,到...
小波包分解提取 %% wavelet packet coefficients. 求取小波包分解的各个节点的小波包系数...
算法小抄9-快慢指针 在上一节我们已经讲过了怎么使用快慢指针去寻找链表的中点,这一节我们继续学习快慢指针的进阶用法,快慢指...
机械学院职业生涯规划书 机械工...  机械学院职业规划书(一)  第一章 认识自我  尼采曾说:“聪明的人只要能认识自己,便什么都不会失...
化学系职业规划书范文 化学系大...   化学系职业规划书(一)  一。 前言  社会的发展日异月新,社会的竞争越来越大,大学生越来越多,...
土木工程学院职业生涯规划书 土...   土木工程学院职业生涯规划书(一)  一、前言  莎士比亚曾说过:“人生就是一部作品。谁有生活理想...
利用python写一个gui小... 文章目录前言制作设计自己想要的ui举个例子后记 前言 在上文中我们配置好了GUI编译环境࿰...
体育学院职业规划书 体育学院职...  体育学院职业规划书(一)  我们向往着美好的明天,所以一直为着心中的那个梦而奋斗。大学生职业生涯规...
护理专业职业生涯规划书 我的职...   护理专业职业生涯规划书(一)  一、前言:  开学了,又一批大一的新生步入了象牙塔,在高三饱受折...
【设计模式】23种设计模式之创... 【设计模式】23种设计模式之创建型模式一、单例模式1、是什么?2、单例模式有哪八种方式...
外语学院职业生涯规划书 外语大...   外语学院职业生涯规划书(一)  在已经步入二十一世纪的今天,大学生已经与时代接轨赋予了全新的含义...