Android调用摄像头拍照从相册中选择图片

news2024/11/18 5:43:03

以下内容摘自郭霖《第一行代码》第三版
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/takePhoneBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Take Photo" />

    <Button
        android:id="@+id/fromAlbumBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="From Album" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>

布局文件中只有两个控件:一个Button和一个ImageView。Button是用于打开摄像头进行拍照的,而ImageView则是用于将拍到的图片显示出来。

MainActivity

package com.example.cameraalbumtest

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 androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import androidx.core.content.FileProvider
import java.io.File

class MainActivity : AppCompatActivity() {

    val takePhoto = 1
    val fromAlbum = 2
    lateinit var imageUri: Uri
    lateinit var outputImage: File

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val takePhoneBtn: Button = findViewById(R.id.takePhoneBtn)
        takePhoneBtn.setOnClickListener {
            // 创建File对象,用于存储拍照后的图片,存放在手机SD卡的应用关联缓存目录下,具体的路径是/sdcard/Android/data/<package name>/cache
            outputImage = File(externalCacheDir, "output_image.jpg")
            if(outputImage.exists()){
                outputImage.delete()
            }
            outputImage.createNewFile()
            imageUri = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
                // 如果运行设备的系统版本高于Android 7.0,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。
                FileProvider.getUriForFile(this, "com.example.cameraalbumtest.fileprovider", outputImage)
            }else{
                // 如果运行设备的系统版本低于Android 7.0,就调用Uri的fromFile() 方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。
                Uri.fromFile(outputImage)
            }
            // 启动相机程序
            val intent = Intent("android.media.action.IMAGE_CAPTURE")
            // 指定图片的输出地址
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
            // 启动Activity
            startActivityForResult(intent, takePhoto)
        }

        val fromAlbumBtn: Button = findViewById(R.id.fromAlbumBtn)
        fromAlbumBtn.setOnClickListener {
            // 打开文件选择器
            // 构建了一个Intent对象,并将它的action指定为Intent.ACTION_OPEN_DOCUMENT,表示打开系统的文件选择器。
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            // 指定只显示图片
            // 注意:要coyp代码需要把/后面的那个空格去掉,这里有个MD文档的书写bug
            intent.type = "image/ *"
            startActivityForResult(intent, fromAlbum)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        val imageView: ImageView = findViewById(R.id.imageView)
        when(requestCode){
            takePhoto -> {
                if(resultCode == Activity.RESULT_OK){
                    // 将拍摄的照片显示出来
                    // 如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。
                    val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
                    imageView.setImageBitmap(rotateIfRequired(bitmap))
                }
            }

            fromAlbum -> {
                if(resultCode == Activity.RESULT_OK && data != null){
                    // 调用了返回Intent的getData()方法来获取选中图片的Uri,
                    data.data?.let {uri ->
                        // 将选择的图片显示出来
                        //然后再调用getBitmapFromUri()方法将Uri转换成Bitmap对象
                        val bitmap = getBitmapFromUri(uri)
                        imageView.setImageBitmap(bitmap)
                    }
                }
            }
        }
    }

    private fun getBitmapFromUri(uri: Uri) = contentResolver
        .openFileDescriptor(uri, "r")?.use{
            BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
    }

    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, degeree: Int): Bitmap{
        val matrix = Matrix()
        matrix.postRotate(degeree.toFloat())
        val rotatedBitMap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        bitmap.recycle()
        return rotatedBitMap
    }
}

先来看用摄像头拍照展示的逻辑:

首先这里创建了一个File对象,用于存放摄像头拍下的图片,这里我们把图片命名为output_image.jpg,并存放在手机SD卡的应用关联缓存目录下。应用关联缓存目录就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法可以得到这个目录,具体的路径是/sdcard/Android/data/<package name>/cache。使用应用关联缓存目录来存放图片是因为从Android 6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。从Android 10.0系统开始,公有的SD卡目录已经不再允许被应用程序直接访问了,而是要使用作用域存储才行。

接着会进行一个判断,如果运行设备的系统版本低于Android 7.0,就调用Uri的fromFile()方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。否则,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。getUriForFile()方法接收3个参数:

  • 第一个参数要求传入Context对象
  • 第二个参数可以是任意唯一的字符串
  • 第三个参数是我们刚刚创建的File对象。

之所以要进行这样一层转换,是因为从Android 7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而FileProvider则是一种特殊的ContentProvider,它使用了和ContentProvider类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。

接下来构建了一个Intent对象,并将这个Intent的action指定为android.media.action.IMAGE_CAPTURE,再调用Intent的putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象,最后调用startActivityForResult()启动Activity。由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的Activity去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中。

由于刚才是使用startActivityForResult()启动Activity的,因此拍完照后会有结果返回到onActivityResult()方法中。如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。

注意:调用照相机程序去拍照有可能会在一些手机上发生照片旋转的情况。这是因为这些手机认为打开摄像头进行拍摄时手机就应该是横屏的,因此回到竖屏的情况下就会发生90度的旋转。为此,这里我们又加上了判断图片方向的代码,如果发现图片需要进行旋转,那么就先将图片旋转相应的角度,然后再显示到界面上。

再来看从相册中选择图片的逻辑:

在“From Album”按钮的点击事件里,我们先构建了一个Intent对象,并将它的action指定为Intent.ACTION_OPEN_DOCUMENT,表示打开系统的文件选择器。接着给这个Intent对象设置一些条件过滤,只允许可打开的图片文件显示出来,然后调用startActivityForResult()方法即可。注意,在调用startActivityForResult()方法的时候,我们给第二个参数传入的值变成了fromAlbum,这样当选择完图片回到
onActivityResult()方法时,就会进入fromAlbum的条件下处理图片。
接下来的部分就很简单了,我们调用了返回Intent的getData()方法来获取选中图片的Uri,然后再调用getBitmapFromUri()方法将Uri转换成Bitmap对象,最终将图片显示到界面上。

在AndroidManifest.xml中对ContentProvider进行注册:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
		...
		>

        <provider
            android:authorities="com.example.cameraalbumtest.fileprovider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        ...
    </application>

</manifest>

android:name属性的值是固定的,而android:authorities属性的值必须和刚才FileProvider.getUriForFile()方法中的第二个参数一致。另外,这里还在<provider>标签的内部使用<meta-data>指定Uri的共享路径,并引用了一个@xml/file_paths资源。

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="my_images"
        path="/" />

</paths>

external-path就是用来指定Uri共享路径的,name属性的值可以随便填,path属性的值表示共享的具体路径。这里使用一个单斜线表示将整个SD卡进行共享,当然也可以仅共享存放output_image.jpg这张图片的路径。

最后来看真机效果图截图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/06b1f924773a4732932bae45e00b22d3.png

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

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

相关文章

简单分享婚宴预订小程序怎么做

婚宴预订小程序需要具备一些功能&#xff0c;通过这些功能&#xff0c;新人可以更方便地选择婚宴场地、预订服务&#xff0c;并且更好地规划自己的婚礼。 1. 场地浏览与选择 婚宴预订小程序可以展示多个婚宴场地的照片和详细信息&#xff0c;包括容纳人数、场地设施、价格等。…

Flutter:flutter_local_notifications——消息推送的学习

前言 注&#xff1a; 刚开始学习&#xff0c;如果某些案例使用时遇到问题&#xff0c;可以自行百度、查看官方案例、官方github。 简介 Flutter Local Notifications是一个用于在Flutter应用程序中显示本地通知的插件。它提供了一个简单而强大的方法来在设备上发送通知&#…

PHP 3des加解密新旧方法可对接加密

一、旧3des加解密方法 <?php class Encrypt_3DES {//加密秘钥&#xff0c;private $_key;private $_iv;public function __construct($key, $iv){$this->_key $key;$this->_iv $iv;}/*** 对字符串进行3DES加密* param string 要加密的字符串* return mixed 加密成…

blender 用蒙版添加材质

一、添加材质常规方法 选择物体新建材质&#xff0c;shift a 新建图像纹理&#xff0c;此时会发现添加上的纹理会有接缝&#xff0c;shift a 新建映射 纹理坐标&#xff0c;纹理坐标选择生成&#xff0c;此时&#xff0c;之前的接缝便会消失&#xff1b; 如何快捷添加纹理坐…

【应用】Asible自动化运维工具的应用与常用命令

ansible自动化运维工具 一、ansible 的概述1. ansible 的概念2. ansible 的特性 二、ansible 的部署与命令1. ansible 的部署1.1 服务器ip地址设置1.2 ansible 服务器部署 2. ansible 命令行模块2.1 command 模块2.2 shell 模块2.3 cron 模块2.4 user 模块2.5 group 模块2.6 co…

TCP KeepAlive与HTTP Keep-Alive

TCP KeepAlive与HTTP Keep-Alive TCP KeepAliveHTTP Keep-AliveTCP服务器怎么检测客户端断开连接 TCP KeepAlive TCP连接建立之后&#xff0c;如果应用程序或者上层协议一直不发送数据&#xff0c;或者隔很长时间才发送一次数据&#xff0c;那么TCP需要判断是应用程序掉线了还…

postgresql|数据库|启动数据库时报错:FATAL: could not map anonymous shared memory的解决

前言&#xff1a; 一个很偶然的出现的问题&#xff0c;因为我需要验证备份文件是否正确&#xff0c;因此&#xff0c;我在一台已启动了一个数据库实例的服务器上&#xff0c;依据全备的数据库文件在启动一个实例&#xff0c;当然&#xff0c;在此之前&#xff0c;已经修改了备…

C语言习题练习

C语言习题练习 一、offsetof宏二、交换奇偶位三、原地移除数组总结 一、offsetof宏 首先我们要了解什么是offsetof宏&#xff1a; . 此具有函数形式的宏返回数据结构或联合类型中成员成员的偏移值&#xff08;以字节为单位&#xff09;。 . 返回的值是size_t类型的无符号整数…

DevOps(四)

CD(二) 1. CDStep 1 - 上传代码Step 2 - 下载代码Step 3 - 检查代码Step 4 - 编译代码Step 5 - 上传仓库Step 6 - 下载软件Step 7 - 制作镜像Step 8 - 上传镜像Step 9 - 部署服务2. 整体预览2.1 预览1. 修改代码2. 查看sonarqube检查结果3. 查看nexus仓库4. 查看harbor仓库5.…

【打卡】Datawhale暑期实训ML赛事

文章目录 赛题描述任务要求数据集介绍评估指标 赛题分析基于LightGBM模型Baseline详解改进baseline早停法添加特征 赛题描述 赛事地址&#xff1a;科大讯飞锂离子电池生产参数调控及生产温度预测挑战赛 任务要求 初赛任务&#xff1a;初赛提供了电炉17个温区的实际生产数据&…

字典树Trie

Trie树又称字典树&#xff0c;前缀树。是一种可以高效查询前缀字符串的树&#xff0c;典型应用是用于统计&#xff0c;排序和保存大量的字符串&#xff08;但不仅限于字符串&#xff09;&#xff0c;所以经常被搜索引擎系统用于文本词频统计。 它的优点是&#xff1a;利用字符串…

欧姆龙 NJ SNMP 协议的使用,用于监控PLC的网络状态

NJ SNMP 协议的使用 实验时间&#xff1a;2023.07.25 实验器材&#xff1a;NJ501-1300 实验目的&#xff1a;NJ SNMP 协议的使用 1. SNMP 协议介绍 ​ SNMP&#xff08;Simple Network Management Protocol&#xff09;是一种简单网络管理协议。它属于 TCP/IP 五层协议中的…

Cerbero Suite Advanced Crack

Cerbero Suite Advanced Crack 用于软件分类和文件分析的最先进的工具套件。分析多种文件格式&#xff0c;包括PE、Mach-O、ELF、Java、SWF、DEX、PDF、DOC、XLS、RTF、Zip等。 它提供自动分析、交互式分析、Carbon interactive Disassembler、字节码反汇编程序、带布局的十六进…

一篇文章搞定《EventBus详解》

一篇文章搞定《EventBus详解》 前言EventBus简述EventBus的使用EventBus源码解析初始化并构建实例EventBus.getDefault()EventBus.builder()EventBus初始化的重要成员 注册流程register方法SubscriberMethodFinder类findSubscriberMethods方法findUsingReflection方法&#xff…

掌握Python的X篇_10+11_if分支语句、else语句、elif语句

文章目录 1. if关键字及语法2. 语句块的概念3. else语句4. elif语句 1. if关键字及语法 基本语法如下&#xff1a; if 条件表达式:条件为True时&#xff0c;要执行的语句举例&#xff1a; number int(input("Input an number")) if number > 5 :print("这…

【Spring框架】spring对象注入的三种方法

目录 1.属性注入问题&#xff1a;同类型的Bean存储到容器多个&#xff0c;获取时报错的问题&#xff1b;1.将属性的名字和Bean的名字对应上。2.使用AutoWiredQualifier来筛选bean对象&#xff1b; 属性注入优缺点 2.Setter注入Setter注入优缺点 3.构造方法注入&#xff08;Spri…

Node.js 安装与版本管理(nvm 的使用)

安装 Node.js Node.js 诞生于 2009 年 5 月&#xff0c;截至今天&#xff08;2022 年 3 月 26 号&#xff09;的最新版本为 16.14.2 LTS 和 17.8.0 Current&#xff0c;可以去官网下载合适的版本。 其中&#xff0c;LTS&#xff08;Long Term Support&#xff09; 是长期维护…

MySQL基础扎实——主键与候选键

词义解释 主键&#xff08;Primary Key&#xff09;和候选键&#xff08;Candidate Key&#xff09;是关系型数据库中的术语&#xff0c;用于标识和唯一确定表中的记录。它们之间有以下区别&#xff1a; 唯一性&#xff1a;主键是表中的唯一标识&#xff0c;每个表只能有一个主…

环境保护数据传输系统监测环境指标

嵌入式实时操作系统&#xff08;RTOS&#xff09;是一种专门设计用于嵌入式系统的操作系统。它具有实时性能和低延迟的特点&#xff0c;能够满足对时间响应性要求较高的应用。本文介绍了一种具备Modbus Slave和Modbus Master功能的嵌入式实时操作系统设备&#xff0c;以及其扩展…

OpenGL Metal Shader 编程:ShaderToy 内置全局变量

OpenGL & Metal Shader 编程&#xff1a;ShaderToy 内置全局变量 前面发了一些关于 Shader 编程的文章&#xff0c;有读者反馈太碎片化了&#xff0c;希望这里能整理出来一个系列&#xff0c;方便系统的学习一下 Shader 编程。 由于主流的 Shader 编程网站&#xff0c;如…