Android拍照相册裁剪封装
最近用到从相机/相册选择图片的功能,这个功能虽然不复杂,网上的代码也一大堆,但是考虑到可能以后别的地方也会用到,所以就抽个空封装了一个类,来实现此功能。
先列出来需要解决问题
- 这个类需要具备哪些功能
至少要支持:相机、相册、裁剪、裁剪比例设定、缩放功能,因为使用系统的自带的裁剪功能,所以如裁剪成圆形、裁剪时展示网格、图片进行二次压缩等功能暂且不论。
- 如何能够在下次使用的时候快速集成进去
如果要快速集成,最好是几行代码就可以得到选择之后的图片,那么就需要将弹出相机/相册选择框、拍照/选择相册之后的裁剪逻辑、图片保存逻辑、权限检查逻辑、7.0以上Uri逻辑、版本兼容问题都在一个类中完成,使用者只需要设置开关参数即可。
- 6.0以上权限问题
6.0以上动态检查权限问题、设置了targetSdkVersion设置为23以下时正常的权限检查无效问题,没有权限时自动获取权限。
- 7.0以上Uri问题
在7.0版本以上系统设定不能直接使用本地真是路径的Uri,必须使用FileProvider封装之后才可以获取,但是这个也是分情况的,具体下面会提到。
- 重复选择拍照时产生的垃圾文件问题
选定一张图片后,觉得不好重新由选择了一张,之前的那个图片文件怎么处理,如果加入裁剪功能,那么裁剪后拍照的原图怎么处理。如果像发朋友圈一样需要连续多张图片时,产生的文件会不会冲突?
展示效果
先上效果图,然后说代码:
注意事项
根据前面提到的几个问题,有以下几点需要说明:
-
封装一个类,传入Activity上下文(没有使用content是因为需要打开相机、相册等intent需要startactiity,虽然fragment也可以,但是这样在纯activity中就无法使用了),提供是否裁剪、裁剪比例、裁剪输出尺寸、是否缩放的开关方法,并提供默认值,可以在使用过程中自行改动。使用
PopupWindow
展示选择框(当然也可以使用dialog),这样可以自由控制选择框的。 -
弹出选择框时检查权限,如果没有权限则进行权限申请,如果用户点击了已拒绝授权,弹出设置界面,引导用户授权,授权成功后弹出选择框。
-
提供回调接口,在获取图片成功之后通过回调返回图片的路径。
-
相机拍照、相册选择之后、裁剪之后、权限回调需要通过
onActivityResult
和onRequestPermissionsResult
来实现,所以使用的地方需要将这两个方法传递给封装的类。 -
前面提到过的7.0以上Uri问题,使用
FileProvider
来实现。但是如果项目的targetSdkVersion
设置成23以下的话,即使在7.0以上手机上,也可以不用FileProvider
的形式来获取Uri,不过targetSdkVersion
在23以下,在小米市场无法上传安装包(目前我就知道这个市场) -
在4.4开始,从相册选择照片返回的Uri就不能直接使用,所以需要经过转化才能得到真实的图片路径。
-
7.0以上,
targetSdkVersion
设置为23以上时,相机拍照输出的uri需要通过FileProvider
获取的,否则会抛出异常。裁剪输入的uri为普通形式获取的uri,裁剪输出的uri需要时通过FileProvider
转换的uri,要不会提示无法载入图片/无法保存裁剪的图片。 -
图片裁剪时,可以选择直接
return-data
true或者false,true则直接返回对应的bitmap,false需要制定一个输出uri,为了内存考虑,在封装时,选择false,让其返回对应的uri(需要考虑targetSdkVersion
和7.0以上的uri问题)。 -
在
targetSdkVersion
设置为23以下时,从相机拍照时会生成一个图片,可以得到一个对应的uri,如果需要裁剪,可以继续使用该uri,系统会自动替代之前的拍照生成的文件,这样减少了无用文件。但是设置targetSdkVersion
大于23,且在高版本(这个没有具体研究是哪一个版本,我测试的是华为8.1)使用同一个uri裁剪时,会抛出一个已存在的错误。所以封装类中相机拍照后裁剪输入和输出的uri处理成不同的,裁剪完成后再删除拍照时生成的图片。 -
文件的命名使用当前时间戳,保存在sd卡下面以项目包名为名的文件夹下。
-
为了方便代码集成,一些处理图片、处理uri的公共方法,也封装到一个类中,如果觉得不合适可以自行单独出来一个工具类(或者自己的项目中已经有对应的方法就可以删掉对应的代码)。
-
在fragment中使用时,因为传递的是activity,所以
onActivityResult
等方法是fragment对应的activity先接收到,需要activity传递给fragment在传递到这个类中。 -
代码中有详细的注释
-
以上代码在三星GT-I9300(Android 4.3),华为Mate10Pro(Android8.1)中测试过。
代码
- 主要的类(SelectPictureManager.java)
import android.Manifest;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.content.PermissionChecker;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.PopupWindow;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
/**
* 管理选择相册的类
* Application
* Created by anonyper on 2018/11/19.
*/
public class SelectPictureManager {
/*
定义返回的code值
*/
public static final int TAKE_PHOTO_CODE = 1000;//拍照
public static final int CHOOSE_PHOTO_CODE = 2000;//选择相册
public static final int PICTURE_CROP_CODE = 3000;//剪切图片
public static final int REQUEST_PERMISSIONS = 4000;//授权code
private boolean isNeedCrop = false;//是否需要裁剪 默认不需要
private boolean isScale = true;//是否需要支持缩放 在可裁剪情况下有效
private boolean isContinuous = false;//是否是连拍模式 比如连续需要两张以上照片
private String fileName;//文件名字
private String oldFileName;//拍照裁剪时的原图片 需要删掉
private Uri outImageUri;//相机拍照图片保存地址
private int aspectX = 1000;//裁剪的宽高比例
private int aspectY = 1001;//裁剪的宽高比例 两个比例略微不一样是为了解决部分手机1:1时显示的时圆形裁剪框
private int outputX = 400;//裁剪后输出图片的尺寸大小
private int outputY = 400;//裁剪后输出图片的尺寸大小
Activity activity;//全局上下文 需要