Android 中拍照、相册选择、裁剪照片
一个多月没总结知识点了,差点连博客账号都忘了。。。好了,步入正题,在 Android 中调用摄像头拍照获取图片或者是从相册中选取图片是很常见的功能,比如某些 APP 上传头像的功能就是一个例子。
**因为 Android 7.0 的新特性规定,不同的应用之间不能再使用 file:// 类型的 Uri 共享数据了,否则会报异常,这就就是网上说的 Android 7.0 调用相机拍照崩溃的问题。官方推荐的做法是使用 FileProvider 来实现,**下面来看一下怎么实现这个 APP 中常见的功能:
一般来说,我们通过拍照来获取图片有以下步骤:
调用系统相机拍照 --> 调用系统裁剪程序裁剪裁照片并输出到指定目录 --> 读取裁剪后的图片
获取从相册选择的图片:
调用系统图库 --> 得到选择的图片的 Uri 并裁剪图片然后输出到指定目录 --> 读取裁剪后的图片
这里可能有些小伙伴会问了,为什么要调用系统的裁剪程序裁剪照片呢?因为现在的手机大多数像素比较高,拍出来的照片比较大,如果直接读取拍照后的照片,那么可能会发生栈溢出(就是应用内存不够用)。因此要将图片裁剪之后再读取。可能有些小伙伴对 Uri 的概念还不是很清楚,Uri 通俗来说就是指向某个文件的路径,可以看成文件绝对路径封装后的一个对象,我们可以通过 Uri 来访问其指向的文件。
这里还需要注意的是,Android 6.0 以后,有了新增了危险权限的概念,就是我们在使用这类权限的时候不仅要在 AndroidManifest 文件中声明,我们还需要在使用的时候向用户申请这个权限。如果对这个还不了解的小伙伴可以参考一下这篇文章:http://blog.csdn.net/hacker_zhidian/article/details/56058460
Ok,我们根据上面的步骤来看一下:
新建一个 Android 工程:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center_horizontal"tools:context="com.company.zhidian.usercameraandalbum.MainActivity"><Buttonandroid:id="@+id/startCameraButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="拍照获取图片"/><Buttonandroid:id="@+id/choiceFromAlbumButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="从相册选择"/><ImageViewandroid:id="@+id/pictureImage"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout>
简单的布局,下面是 MainActivity.java:
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;import java.io.File;
import java.io.IOException;public class MainActivity extends AppCompatActivity {private Button startCameraButton = null;private Button choiceFromAlbumButton = null;private ImageView pictureImageView = null;private static final int TAKE_PHOTO_PERMISSION_REQUEST_CODE = 0; // 拍照的权限处理返回码private static final int WRITE_SDCARD_PERMISSION_REQUEST_CODE = 1; // 读储存卡内容的权限处理返回码private static final int TAKE_PHOTO_REQUEST_CODE = 3; // 拍照返回的 requestCodeprivate static final int CHOICE_FROM_ALBUM_REQUEST_CODE = 4; // 相册选取返回的 requestCodeprivate static final int CROP_PHOTO_REQUEST_CODE = 5; // 裁剪图片返回的 requestCodeprivate Uri photoUri = null;private Uri photoOutputUri = null; // 图片最终的输出文件的 Uri@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);startCameraButton = (Button) findViewById(R.id.startCameraButton);startCameraButton.setOnClickListener(clickListener);choiceFromAlbumButton = (Button) findViewById(R.id.choiceFromAlbumButton);choiceFromAlbumButton.setOnClickListener(clickListener);pictureImageView = (ImageView) findViewById(R.id.pictureImage);/** 先判断用户以前有没有对我们的应用程序允许过读写内存卡内容的权限,* 用户处理的结果在 onRequestPermissionResult 中进行处理*/if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {// 申请读写内存卡内容的权限ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_SDCARD_PERMISSION_REQUEST_CODE);}}private View.OnClickListener clickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {// 调用相机拍照if(v == startCameraButton) {// 同上面的权限申请逻辑if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {/** 下面是对调用相机拍照权限进行申请*/ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA,}, TAKE_PHOTO_PERMISSION_REQUEST_CODE);} else {startCamera();}// 从相册获取} else if(v == choiceFromAlbumButton) {choiceFromAlbum();}}};/*** 拍照*/private void startCamera() {/*** 设置拍照得到的照片的储存目录,因为我们访问应用的缓存路径并不需要读写内存卡的申请权限,* 因此,这里为了方便,将拍照得到的照片存在这个缓存目录中*/File file = new File(getExternalCacheDir(), "image.jpg");try {if(file.exists()) {file.delete();}file.createNewFile();} catch (IOException e) {e.printStackTrace();}/*** 因 Android 7.0 开始,不能使用 file:// 类型的 Uri 访问跨应用文件,否则报异常,* 因此我们这里需要使用内容提供器,FileProvider 是 ContentProvider 的一个子类,* 我们可以轻松的使用 FileProvider 来在不同程序之间分享数据(相对于 ContentProvider 来说)*/if(Build.VERSION.SDK_INT >= 24) {photoUri = FileProvider.getUriForFile(this, "com.zhi_dian.provider", file);} else {photoUri = Uri.fromFile(file); // Android 7.0 以前使用原来的方法来获取文件的 Uri}// 打开系统相机的 Action,等同于:"android.media.action.IMAGE_CAPTURE"Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 设置拍照所得照片的输出目录takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE);}/*** 从相册选取*/private void choiceFromAlbum() {// 打开系统图库的 Action,等同于: "android.intent.action.GET_CONTENT"Intent choiceFromAlbumIntent = new Intent(Intent.ACTION_GET_CONTENT);// 设置数据类型为图片类型choiceFromAlbumIntent.setType("image/*");startActivityForResult(choiceFromAlbumIntent, CHOICE_FROM_ALBUM_REQUEST_CODE);}/*** 裁剪图片*/private void cropPhoto(Uri inputUri) {// 调用系统裁剪图片的 ActionIntent cropPhotoIntent = new Intent("com.android.camera.action.CROP");// 设置数据Uri 和类型cropPhotoIntent.setDataAndType(inputUri, "image/*");// 授权应用读取 Uri,这一步要有,不然裁剪程序会崩溃cropPhotoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);// 设置图片的最终输出目录cropPhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoOutputUri = Uri.parse("file:sdcard/image_output.jpg"));startActivityForResult(cropPhotoIntent, CROP_PHOTO_REQUEST_CODE);}/*** 在这里进行用户权限授予结果处理* @param requestCode 权限要求码,即我们申请权限时传入的常量* @param permissions 保存权限名称的 String 数组,可以同时申请一个以上的权限* @param grantResults 每一个申请的权限的用户处理结果数组(是否授权)*/@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {switch (requestCode) {// 调用相机拍照:case TAKE_PHOTO_PERMISSION_REQUEST_CODE:// 如果用户授予权限,那么打开相机拍照if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {startCamera();} else {Toast.makeText(this, "拍照权限被拒绝", Toast.LENGTH_SHORT).show();}break;// 打开相册选取:case WRITE_SDCARD_PERMISSION_REQUEST_CODE:if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {} else {Toast.makeText(this, "读写内存卡内容权限被拒绝", Toast.LENGTH_SHORT).show();}break;}}/*** 通过这个 activity 启动的其他 Activity 返回的结果在这个方法进行处理* 我们在这里对拍照、相册选择图片、裁剪图片的返回结果进行处理* @param requestCode 返回码,用于确定是哪个 Activity 返回的数据* @param resultCode 返回结果,一般如果操作成功返回的是 RESULT_OK* @param data 返回对应 activity 返回的数据*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if(resultCode == RESULT_OK) {// 通过返回码判断是哪个应用返回的数据switch (requestCode) {// 拍照case TAKE_PHOTO_REQUEST_CODE:cropPhoto(photoUri);break;// 相册选择case CHOICE_FROM_ALBUM_REQUEST_CODE:cropPhoto(data.getData());break;// 裁剪图片case CROP_PHOTO_REQUEST_CODE:File file = new File(photoOutputUri.getPath());if(file.exists()) {Bitmap bitmap = BitmapFactory.decodeFile(photoOutputUri.getPath());pictureImageView.setImageBitmap(bitmap);
// file.delete(); // 选取完后删除照片} else {Toast.makeText(this, "找不到照片", Toast.LENGTH_SHORT).show();}break;}}}
}
代码看起来多,但其实思路已经在文章开头已经讲了,因为我们的裁剪照片肯定要写内存卡,于是我们在 onCreate 方法中向用户申请授权写内存卡内容的权限。然后我们对两个按钮的点击事件进行处理,分别对应于拍照和从相册选择图片,因为调用系统相机拍照是危险权限,所以我们要向用户申请授权,这两个权限的授权结果会回调 onRequestPermissionsResult 方法,因此我们在这个方法中对用户的授权结果进行处理。
在 startCamera 方法中,我们对运行设备的 Android 版本进行判断,如果是 7.0 以上,那么我们需要使用 FileProvider 来获取照片输出的 Uri,否则的话用以前的方法获取就行了。
而 choiceFromAlbum 方法的逻辑就简单了,就是打开系统图库来给用户选取图片。
这两个方法的返回结果我们在 onActivityResult 方法中进行处理,这个方法当别的 Activity 返回给当前Activity 数据的时候就会被回调(即通过调用 startActivityForResult 方法来启动别的 Activity 时)。
对于拍照的结果,photoUri 代表的是拍照得到的照片的 Uri,而对于从相册中选择图片,其返回的 Intent 类型的数据中就是用户选取的图片的 Uri,因为我们通过 data.getData() 方法来获取这个 Uri, 最后,对于两种获取图片的方法,我们都需要调用 cropPhoto 方法来对得到的图片进行裁剪,并且最后裁剪的图片输出到内存卡中以便读取。
对于 cropPhoto 方法的返回结果,我们用一个 File 对象来判断图片是不是存在,如果存在我们将其读取,这里笔者把 file.delete();
注释掉了,以便于待会观察裁剪得到的图片,如果没有注释掉的话我们用完这个图片之后,它就会被删除。
因为我们在 startCamera 方法中使用了 FileProvider ,既然使用了 ContentProvider ,那么肯定要对其进行注册了:
<providerandroid:authorities="com.zhi_dian.provider"android:name="android.support.v4.content.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_path"></meta-data>
</provider>
其中,android:authorities 的值要和
photoUri = FileProvider.getUriForFile(this, "com.zhi_dian.provider", file);
的第二个参数值相同,android:name 的值是固定的,因为这是我们使用的 FileProvider 的来源
<meta-data
中 android:resource 的值是我们在 res 文件夹下创建的 xml 文件夹的 file_path.xml 文件:
下面是 file_path 文件的内容:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="image" path="" />
</paths>
其中, name 属性可以随便填,path 属性代表FileProvider 共享的文件路径,空字符串代表共享 sd 卡上的所有文件,当然你也可以填我们拍照得到的图片路径,这样就是只共享我们拍照的到的照片。
最后,当然,别忘了在 Androidmanifest 中申请拍照和写内存卡内容的权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Ok,主要的内容就是这些了,下面来看一下运行结果:
其实应用启动的时候就会有一次权限申请,即对写内存卡的权限的申请,但是由于录制软件问题没录到。
同时我们可以在设备的文件管理中看到这个文件:
好了,一个简单的通过拍照和相册选取照片的应用就完成了,通过这个,我们可以完成一些 APP 中常用的功能。
如果博客中有什么不正确的地方,还请多多指点,如果觉得我写的不错,那么请点个赞支持我吧。下面是上面例子的源码:
Android 中拍照、相册选择、裁剪照片(兼容Android 7.0)
谢谢观看。。。
Android 中拍照、相册选择、裁剪照片相关推荐
- Android中拍照(相册中选择)并上传图片功能(包括动态获取权限)
作为新手小白,为了实现这个拍照和相册选取图片并上传功能,确实花费了很多时间,因为实现不容易,所以记录下来,一和大家分享,二为之后学习做个备忘. 一.实现效果 二. 整体思路 Android手机客户端, ...
- android裁剪图片功能,Android实现拍照、选择图片并裁剪图片功能
一. 实现拍照.选择图片并裁剪图片效果 按照之前博客的风格,首先看下实现效果. 二. uCrop项目应用 想起之前看到的Yalantis/uCrop效果比较绚,但是研究源码之后发现在定制界面方面还是有 ...
- android 图片自动裁剪图片,Android实现拍照、选择相册图片并裁剪功能
通过拍照或相册中获取图片,并进行裁剪操作,然后把图片显示到ImageView上. 当然也可以上传到服务器(项目中绝大部分情况是上传到服务器),参考网上资料及结合项目实际情况, 测试了多款手机暂时没有发 ...
- Ionic2中的相册选择和拍照上传——ImgService
目录 目录 前言 Cordova准备 ImgService服务的实现 ImgService服务的使用 示例效果 相册选择器的汉化 参考 前言 在APP中启动相册选择器或者拍照上传图片这些功能是非常常见 ...
- android 7.0 裁剪,Android 7.0中拍照和图片裁剪适配的问题详解
前言 Android 7.0系统发布后,拿到能升级的nexus 6P,就开始了7.0的适配.发现在Android 7.0以上,在相机拍照和图片裁剪上,可能会碰到以下一些错误: Process: com ...
- 拍照相册和裁剪保存图片集合
1 写在前面 前两天看了下别人保存图片的方式,又看了郭神的第一行代码的拍照和打开相册,又研究了下对图片的裁剪,然后合在一起做了个demo,以后要用直接用就可以了.先说说这个代码比网上其他的代码好的地方 ...
- Ionic系列——调用摄像头拍照和选择图库照片功能的实现
2019独角兽企业重金招聘Python工程师标准>>> 1.需求描述 最近要做一个功能就是调用摄像头拍照,然后上传照片的功能,或者直接打开图库选择照片然后上传. 2.准备 ①.添加插 ...
- Android实现拍照相册图片上传功能
更改头像功能不像修改信息一样直接提交参数就可以,需要上传图片文件 我就直接贴代码了首先给出布局文件 <ImageViewandroid:id="@+id/iv"android ...
- android从本地相册选择图片uri三星手机适配问题
转载地址:http://blog.csdn.net/CathyChen0910/article/details/62456438 启动系统相册intent Intent intentFromGalle ...
最新文章
- java编程点滴(3)--ubuntu下jdk的配置
- 【NOIP2013模拟9.29】TheSwaps
- SAP Spartacus AuthService.getUserToken的实现
- Groovy的本地安装和Eclipse插件的配置
- 云顶之弈小程序 置顶工具(附源码)
- C语言实现常用排序算法——基数排序
- 基于scap的服务器安全基线核查设计与实现
- NetBeans 时事通讯(刊号 # 51 - Apr 07, 2009)
- 《实用机器学习》中的一些拓展问题
- Mysqldump 1449 错误解决 mysqldump: Got error: 1449
- 一个牛人给的java九点建议
- nsis出错_nsis error,教您电脑总是提示nsis error怎么办
- Mac 如何安装tomcat
- 牛客网浙江大学机试--找出直系亲属
- 蓝牙鼠标windows linux,Windows+Linux+MacOS三大系统共用蓝牙鼠标
- 12门课100分,直博清华的学霸火了!“造假都不敢这么写”
- 圆周率怎么计算来的?教你利用欧拉恒等式,生成圆周率万能公式!
- 椭圆检测(Ellipse Detection)算法
- IDEA中怎么创建xml文件
- 经典问题 小白鼠试毒药 这个算法你绝对不知道
热门文章
- html5——3D案例(音乐盒子、百度钱包)
- [转]A Guide To using IMU (Accelerometer and Gyroscope Devices) in Embedded Applications.
- CDN公共库、前端开发常用插件一览表(VendorPluginLib)
- Spark 源码分析之ShuffleMapTask内存数据Spill和合并
- 联动优势 java_联动优势快捷支付
- 想接广告又怕掉粉?——微信公众号如何做粉丝维护
- 在幻灯片中怎么设置竖屏竖版手机PPT?
- 笔记: 解决oracle impdp导入dmp时, 用户名和表空间与dmp中的不相同导致无法导入的问题
- INSERT 插入语句 零基础自学SQL课程系列Day8
- Elasticsearch:Text vs. Keyword - 它们之间的差异以及它们的行为方式