Android拍照相册裁剪封装

最近用到从相机/相册选择图片的功能,这个功能虽然不复杂,网上的代码也一大堆,但是考虑到可能以后别的地方也会用到,所以就抽个空封装了一个类,来实现此功能。

先列出来需要解决问题:

  • 这个类需要具备哪些功能

至少要支持:相机、相册、裁剪、裁剪比例设定、缩放功能,因为使用系统的自带的裁剪功能,所以如裁剪成圆形、裁剪时展示网格、图片进行二次压缩等功能暂且不论。

  • 如何能够在下次使用的时候快速集成进去

如果要快速集成,最好是几行代码就可以得到选择之后的图片,那么就需要将弹出相机/相册选择框、拍照/选择相册之后的裁剪逻辑、图片保存逻辑、权限检查逻辑、7.0以上Uri逻辑、版本兼容问题都在一个类中完成,使用者只需要设置开关参数即可。

  • 6.0以上权限问题

6.0以上动态检查权限问题、设置了targetSdkVersion设置为23以下时正常的权限检查无效问题,没有权限时自动获取权限。

  • 7.0以上Uri问题

在7.0版本以上系统设定不能直接使用本地真是路径的Uri,必须使用FileProvider封装之后才可以获取,但是这个也是分情况的,具体下面会提到。

  • 重复选择拍照时产生的垃圾文件问题

选定一张图片后,觉得不好重新由选择了一张,之前的那个图片文件怎么处理,如果加入裁剪功能,那么裁剪后拍照的原图怎么处理。如果像发朋友圈一样需要连续多张图片时,产生的文件会不会冲突?

展示效果

先上效果图,然后说代码:

image.png

注意事项

根据前面提到的几个问题,有以下几点需要说明:

  • 封装一个类,传入Activity上下文(没有使用content是因为需要打开相机、相册等intent需要startactiity,虽然fragment也可以,但是这样在纯activity中就无法使用了),提供是否裁剪、裁剪比例、裁剪输出尺寸、是否缩放的开关方法,并提供默认值,可以在使用过程中自行改动。使用PopupWindow展示选择框(当然也可以使用dialog),这样可以自由控制选择框的。

  • 弹出选择框时检查权限,如果没有权限则进行权限申请,如果用户点击了已拒绝授权,弹出设置界面,引导用户授权,授权成功后弹出选择框。

  • 提供回调接口,在获取图片成功之后通过回调返回图片的路径。

  • 相机拍照、相册选择之后、裁剪之后、权限回调需要通过onActivityResultonRequestPermissionsResult来实现,所以使用的地方需要将这两个方法传递给封装的类。

  • 前面提到过的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-datatrue或者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;//全局上下文 需要startactiity
​
 PictureSelectListner pictureSelectListner;//选择之后照片的回调监听
​
​
 /**
 * 构造SelectPictureManager对象
 *
 * @param activity 上下文 这样就可以自主的实现
 */
 public SelectPictureManager(Activity activity) {
 this(activity, null);
 }
​
 /**
 * 构造SelectPictureManager对象
 *
 * @param activity             上下文
 * @param pictureSelectListner 回调监听
 */
 public SelectPictureManager(Activity activity, PictureSelectListner pictureSelectListner) {
 this.activity = activity;
 this.pictureSelectListner = pictureSelectListner;
 fileName = System.currentTimeMillis() + ".jpg";//默认使用时间戳作为图片名字
 }
​
 /**
 * 设置图片回调监听
 *
 * @param pictureSelectListner
 */
 public void setPictureSelectListner(PictureSelectListner pictureSelectListner) {
 this.pictureSelectListner = pictureSelectListner;
 }
​
 /**
 * 设置连拍 这样可以连续排多张不重复的图片
 *
 * @param isContinuous
 * @return
 */
 public SelectPictureManager setContinuous(boolean isContinuous) {
 this.isContinuous = isContinuous;
 return this;
 }
​
 /**
 * 设置是否需要裁剪
 *
 * @param isNeedCut
 * @return
 */
 public SelectPictureManager setNeedCrop(boolean isNeedCrop) {
 this.isNeedCrop = isNeedCrop;
 return this;
 }
​
 /**
 * 设置裁剪是否需要缩放
 *
 * @param isScale
 * @return
 */
 public SelectPictureManager setScaleAble(boolean isScale) {
 this.isScale = isScale;
 return this;
 }
​
 /**
 * 设置裁剪的宽高比例
 *
 * @param x 裁剪比例宽
 * @param y 裁剪比例高
 * @return
 */
 public SelectPictureManager setAspect(int x, int y) {
 this.aspectX = x;
 this.aspectY = y;
 return this;
 }
​
 /**
 * 设置裁剪后输出的尺寸大小
 *
 * @param x 裁剪输入的宽
 * @param y 裁剪输出的高
 * @return
 */
 public SelectPictureManager setOutPutSize(int x, int y) {
 this.outputX = x;
 this.outputY = y;
 return this;
 }
​
​
 View showView;//使用PopupWindow 显示时需要一个View,如果觉得不便可以使用dialog
​
​
 /**
 * 展示选择拍照的pop框  一个是从相机选择 一个是拍照选择
 *
 * @param showView 需要展示view
 */
 public void showSelectPicturePopupWindow(View showView) {
 if (showView == null) {
 return;
 }
 this.showView = showView;
 if (this.activity == null) {
 if (this.pictureSelectListner != null) {
 this.pictureSelectListner.throwError(new NullPointerException("上下文activity不可为空"));
 } else {
 throw new NullPointerException("上下文activity不可为空");
 }
 }
 boolean hasPermission = checkPermission();
 if (hasPermission) {
 //拥有权限 可以直接打开
 final PopupWindow popupWindow = new PopupWindow(this.activity);
 LayoutInflater inflater = (LayoutInflater) this.activity
 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 assert inflater != null;
 final View mView = inflater.inflate(R.layout.pop_window_view, null);
 Button btn_camera = (Button) mView.findViewById(R.id.icon_btn_camera);
 Button btn_select = (Button) mView.findViewById(R.id.icon_btn_choose);
 Button btn_cancel = (Button) mView.findViewById(R.id.icon_btn_cancel);
​
 btn_select.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 choosePhoto();
 popupWindow.dismiss();
 }
 });
 btn_camera.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 takePhoto();
 popupWindow.dismiss();
 }
 });
 btn_cancel.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 popupWindow.dismiss();
 }
 });
​
 // 导入布局
 popupWindow.setContentView(mView);
 // 设置动画效果
 popupWindow.setAnimationStyle(R.anim.in_top);
 popupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
 popupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
 // 设置可触
 popupWindow.setFocusable(true);
 ColorDrawable dw = new ColorDrawable(0x0000000);
 popupWindow.setBackgroundDrawable(dw);
 // 单击弹出窗以外处 关闭弹出窗
 mView.setOnTouchListener(new View.OnTouchListener() {
 @Override
 public boolean onTouch(View v, MotionEvent event) {
 int height = mView.findViewById(R.id.ll_pop).getTop();
 int y = (int) event.getY();
 if (event.getAction() == MotionEvent.ACTION_UP) {
 if (y < height) {
 popupWindow.dismiss();
 }
 }
 return true;
 }
 });
 popupWindow.showAtLocation(showView,
 Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
 }
​
 }
​
 /**
 * 先初始化好要保存的文件以及对应的uri
 */
 private void initSavedFile(boolean isCrop) {
 if (!isContinuous && outImageUri != null && !TextUtils.isEmpty(fileName)) {//不是连拍 同时已经存在
 return;
 }
 File parentFile = new File(Environment.getExternalStorageDirectory(), activity.getPackageName());//先创建包名对应的文件夹
 if (!parentFile.exists()) {
 parentFile.mkdir();
 } else if (!parentFile.isDirectory()) {
 parentFile.delete();
 parentFile.mkdir();
 }
​
 File outputImage = new File(Environment.getExternalStorageDirectory(), activity.getPackageName() + "/" + fileName);
 try {
 if (outputImage.exists()) {
 if (isContinuous || isCrop) {//如果是连拍模式,就需要重新起一个名字
 if (isCrop) {//如果是裁剪的话 当这个文件已存在表示是通过相机拍照过来的,所以需要额外指定裁剪之后存储的uri
 oldFileName = fileName;
 }
 fileName = System.currentTimeMillis() + ".jpg";
 initSavedFile(isCrop);//重新来一下
 return;
 } else {
 outputImage.delete();
 }
 }
 outputImage.createNewFile();
 } catch (IOException e) {
 e.printStackTrace();
 }
 //以下uri的处理在targetVersion大于23时,同时在7.0版本以上时需要做区分
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || isCrop) {//裁剪的时候 保存需要使用一般的uri 要不然出现无法保存裁剪的图片的错误
 outImageUri = Uri.fromFile(outputImage);
 } else {
 //Android 7.0系统开始 使用本地真实的Uri路径不安全,使用FileProvider封装共享Uri(相机拍照输出的uri)
 outImageUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", outputImage);
 }
 }
​
​
 /**
 * 从相机拍照
 */
 private void takePhoto() {
 initSavedFile(false);
 Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 // 下面这句指定调用相机拍照后的照片存储的路径(7.0以上需要使用privider获取的uri)
 takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, outImageUri);
 activity.startActivityForResult(takeIntent, TAKE_PHOTO_CODE);
 }
​
 /**
 * 从相册选择
 */
 private void choosePhoto() {
 Intent intent = new Intent("android.intent.action.GET_CONTENT");
 intent.setType("image/*");
 activity.startActivityForResult(intent, CHOOSE_PHOTO_CODE); // 打开相册
 }
​
​
 /**
 * 裁剪图片
 */
 private void cropPicture(Uri pictureUri) {
 initSavedFile(true);//从相册先择时,是没有初始化要保存的文件路径以及对应的uri
 Intent cropIntent = new Intent("com.android.camera.action.CROP");
 cropIntent.setDataAndType(pictureUri, "image/*");//7.0以上 输入的uri需要是provider提供的
​
 // 开启裁剪:打开的Intent所显示的View可裁剪
 cropIntent.putExtra("crop", "true");
 // 裁剪宽高比
 cropIntent.putExtra("aspectX", aspectX);
 cropIntent.putExtra("aspectY", aspectY);
 // 裁剪输出大小
 cropIntent.putExtra("outputX", outputX);
 cropIntent.putExtra("outputY", outputY);
 cropIntent.putExtra("scale", isScale);
 /**
 * return-data
 * 这个属性决定onActivityResult 中接收到的是什么数据类型,
 * true data将会返回一个bitmap
 * false,则会将图片保存到本地并将我们指定的对应的uri。
 */
 cropIntent.putExtra("return-data", false);
 // 当 return-data 为 false 的时候需要设置输出的uri地址
 cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, outImageUri);//输出的uri为普通的uri,通过provider提供的uri会出现无法保存的错误
 // 图片输出格式
 cropIntent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
 cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//不加会出现无法加载此图片的错误
 cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);// 这两句是在7.0以上版本当targeVersion大于23时需要
 activity.startActivityForResult(cropIntent, PICTURE_CROP_CODE);
 }
​
 /**
 * 收到图片结果后的处理逻辑
 *
 * @param requestCode
 * @param resultCode
 * @param data
 */
 public void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (resultCode == Activity.RESULT_OK) {
 File imageFile = new File(Environment.getExternalStorageDirectory(), activity.getPackageName() + "/" + fileName);
 Uri inImageUri = null;//需要裁剪时输入的uri
 switch (requestCode) {
 case TAKE_PHOTO_CODE:
 // TODO: 调用相机拍照
 if (imageFile == null && !imageFile.exists()) {
 if (pictureSelectListner != null) {
 pictureSelectListner.throwError(new NullPointerException("没有找到对应的路径"));
 }
 return;
 }
 if (this.isNeedCrop) {
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
 inImageUri = Uri.fromFile(imageFile);
 } else {
 //Android 7.0系统开始 使用本地真实的Uri路径不安全,使用FileProvider封装共享Uri
 inImageUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", imageFile);
 }
 cropPicture(inImageUri);
 } else {
 if (pictureSelectListner != null) {
 pictureSelectListner.onPictureSelect(imageFile.getAbsolutePath());
 }
 }
 break;
 case CHOOSE_PHOTO_CODE:
 // TODO: 从相册选择
 Uri uri = data.getData();
 String filePath = Util.getFilePathByUri(activity, uri);
 if (TextUtils.isEmpty(filePath)) {
 if (pictureSelectListner != null) {
 pictureSelectListner.throwError(new NullPointerException("没有找到对应的路径"));
 }
 return;
 }
 if (this.isNeedCrop) {
 File cropFile = new File(filePath);
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
 inImageUri = Uri.fromFile(cropFile);
 } else {
 //Android 7.0系统开始 使用本地真实的Uri路径不安全,使用FileProvider封装共享Uri
 inImageUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", cropFile);
 }
 cropPicture(inImageUri);
 } else {
 if (pictureSelectListner != null) {
 pictureSelectListner.onPictureSelect(filePath);
 }
 }
 break;
 case PICTURE_CROP_CODE:
 // TODO: 图片裁剪
 String cropFile = Util.getFilePathByUri(activity, outImageUri);
 if (!TextUtils.isEmpty(oldFileName)) {//相机拍照裁剪时删掉原来的照片
 File oldImageFile = new File(Environment.getExternalStorageDirectory(), activity.getPackageName() + "/" + oldFileName);
 oldImageFile.deleteOnExit();
 }
 if (TextUtils.isEmpty(cropFile)) {
 if (pictureSelectListner != null) {
 pictureSelectListner.throwError(new NullPointerException("没有找到对应的路径"));
 }
 } else {
 if (pictureSelectListner != null) {
 pictureSelectListner.onPictureSelect(cropFile);
 }
 }
​
 break;
 }
 }
 }
​
​
 /**
 * 6.0版本以上需要检查权限 动态的权限控制 这个地方做了相机、存储权限的模糊检查,虽然单从相册选择时不需要相机权限
 */
 private boolean checkPermission() {
 String[] permissions = new String[]{Manifest.permission.CAMERA,
 Manifest.permission.WRITE_EXTERNAL_STORAGE,
 Manifest.permission.READ_EXTERNAL_STORAGE};
​
 //检查权限
 if (!checkSelfPermission(activity, Manifest.permission.CAMERA)
 || !checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
 || !checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
 ) {
 ActivityCompat.requestPermissions(activity, permissions, REQUEST_PERMISSIONS);//没有权限时进行权限申请
 return false;
 }
 return true;
 }
​
 /**
 * 根部不同的情况进行权限检查,在targetSdkVersion设置为23以下时,不管权限是否开启,一般的checkSelfPermission都返回true
 *
 * @param activity
 * @param permission
 * @return
 */
 private boolean checkSelfPermission(Activity activity, String permission) {
 int sdkVersion = activity.getApplicationInfo().targetSdkVersion;
 boolean ret = true;
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 if (sdkVersion >= Build.VERSION_CODES.M) {
 ret = activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
 } else {
 ret = PermissionChecker.checkSelfPermission(activity, permission) == PermissionChecker.PERMISSION_GRANTED;
 }
 }
 return ret;
​
 }
​
​
 /**
 * 6。0版本以上的权限申请
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 switch (requestCode) {
 case REQUEST_PERMISSIONS:
 boolean isGrant = true;
 for (int index = 0; index < grantResults.length; index++) {
 if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
 isGrant = false;
 }
 }
 if (isGrant) {
 showSelectPicturePopupWindow(showView);
 } else {
 Toast.makeText(activity, "没有获取对应的权限", Toast.LENGTH_SHORT).show();
 /**
 * 跳转到 APP 详情的权限设置页
 */
 Intent intent = Util.getSettingIntent(activity);
 activity.startActivity(intent);
 }
 break;
 default:
 }
 }
​
​
 /**
 * 图片选定之后的回调监听
 */
 public interface PictureSelectListner {
​
 void onPictureSelect(String imagePath);//返回图片的路径
​
 void throwError(Exception e);//当错误时返回对应的异常
 }
​
 /**
 * 工具类 为了保持使用方便,将该类直接放到这里,如果觉得不便可以单独出来一个类
 */
 static class Util {
 /**
 * 打开系统的设置界面 让用户自己授权
 *
 * @param context
 * @return
 */
 public static Intent getSettingIntent(Context context) {
 Intent localIntent = new Intent();
 localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 if (Build.VERSION.SDK_INT >= 9) {
 localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
 localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
 } else if (Build.VERSION.SDK_INT <= 8) {
 localIntent.setAction(Intent.ACTION_VIEW);
 localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
 localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
 }
 return localIntent;
 }
​
 /**
 * 获取uri对应的真是路径
 *
 * @param context
 * @param uri
 * @param selection
 * @return
 */
 public static String getImagePath(Context context, Uri uri, String selection) {
 String path = null;
 // 通过Uri和selection来获取真实的图片路径
 Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
 if (cursor != null) {
 if (cursor.moveToFirst()) {
 path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
 }
 cursor.close();
 }
 return path;
 }
​
 /**
 * 4。4以上获取相册的地址 相册图片返回的uri是经过系统封装过的
 *
 * @param data
 */
​
 public static String getFilePathByUri(Context context, Uri uri) {
 String imagePath = null;
 if (context == null || uri == null) {
 return imagePath;
 }
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 if (DocumentsContract.isDocumentUri(context, uri)) {
 // 如果是document类型的Uri,则通过document id处理
 String docId = DocumentsContract.getDocumentId(uri);
 if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
 String id = docId.split(":")[1]; // 解析出数字格式的id
 String selection = MediaStore.Images.Media._ID + "=" + id;
 imagePath = getImagePath(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
 } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
 Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
 imagePath = getImagePath(context, contentUri, null);
 }
 } else if ("content".equalsIgnoreCase(uri.getScheme())) {
 // 如果是content类型的Uri,则使用普通方式处理
 imagePath = getImagePath(context, uri, null);
 } else if ("file".equalsIgnoreCase(uri.getScheme())) {
 // 如果是file类型的Uri,直接获取图片路径即可
 imagePath = uri.getPath();
 }
 } else {
 if ("content".equalsIgnoreCase(uri.getScheme())) {
 // 如果是content类型的Uri,则使用普通方式处理
 imagePath = getImagePath(context, uri, null);
 } else if ("file".equalsIgnoreCase(uri.getScheme())) {
 // 如果是file类型的Uri,直接获取图片路径即可
 imagePath = uri.getPath();
 }
 }
 return imagePath;
 }
​
 }
​
}
  • 布局文件(pop_window_view.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="#55000000"
 android:orientation="vertical">
​
 <LinearLayout
 android:id="@+id/ll_pop"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_alignParentBottom="true"
 android:layout_marginLeft="15dp"
 android:layout_marginRight="15dp"
 android:orientation="vertical">
​
 <Button
 android:id="@+id/icon_btn_camera"
 android:layout_width="match_parent"
 android:layout_height="42dp"
 android:background="@drawable/d_arc_5_white"
 android:text="拍照"
 android:textColor="@color/blue" />
​
 <View
 android:layout_width="match_parent"
 android:layout_height="10px" />
​
 <Button
 android:id="@+id/icon_btn_choose"
 android:layout_width="match_parent"
 android:layout_height="42dp"
 android:background="@drawable/d_arc_5_white"
 android:text="从相册选择"
 android:textColor="@color/blue" />
​
 <Button
 android:id="@+id/icon_btn_cancel"
 android:layout_width="match_parent"
 android:layout_height="42dp"
 android:layout_marginBottom="15dp"
 android:layout_marginTop="10dp"
 android:background="@drawable/d_arc_5_white"
 android:text="取消"
 android:textColor="@color/black" />
 </LinearLayout>
​
</RelativeLayout>
  • 按钮背景(d_arc_5_white.xml)
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle">
 <!-- 填充的颜色 -->
 <solid android:color="@color/white" />
 <!-- 设置按钮的四个角为弧形 -->
 <!-- android:radius 弧形的半径 -->
 <corners android:radius="5dip" />
</shape>
  • 颜色
color/white=“#FFFFFF”
color/blue=“#0000FF”
  • 7.0以上Provider设置

写在AndroidManifest.xml中,其中authorities对应的值要和代码的中保持一致,否则会抛出异常

<provider
 android:name="android.support.v4.content.FileProvider"
 android:authorities="{packagename}.fileprovider"
 android:exported="false"
 android:grantUriPermissions="true">
 <meta-data
 android:name="android.support.FILE_PROVIDER_PATHS"
 android:resource="@xml/provider_xml" />
</provider>
  • provider_xml.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
 <external-path name="external-path" path="."/>
</paths>
  • Provider中paths节点中参数对应说明
子节点 对应路径
files-path Context.getFilesDir()
cache-path Context.getCacheDir()
external-path Environment.getExternalStorageDirectory()
external-files-path Context.getExternalFilesDir(null)
external-cache-path Context.getExternalCacheDir()
  • 使用方法(fragment中)对应的activity中也需要设置传递onActivityResult和onRequestPermissionsResult
SelectPictureManager selectPictureManager;
​
 void initSelectPictureManager() {
 selectPictureManager = new SelectPictureManager(this.getActivity());
 selectPictureManager.setPictureSelectListner(new SelectPictureManager.PictureSelectListner() {
 @Override
 public void onPictureSelect(String imagePath) {
 LogUtil.i("选择图片的路径是:" + imagePath);
 ImageUtil.displayImage(MineFragmentTest.this.getContext(), new File(imagePath), imgv_userhead);
 }
​
 @Override
 public void throwError(Exception e) {
 e.printStackTrace();
 }
 });
 selectPictureManager.setNeedCrop(true);//需要裁剪
 selectPictureManager.setOutPutSize(400, 400);//输入尺寸
 selectPictureManager.setContinuous(true);//设置连拍
 selectPictureManager.showSelectPicturePopupWindow(this.getView());
 }
​
 @Override
 public void onActivityResult(int requestCode, int resultCode, Intent data) {
 super.onActivityResult(requestCode, resultCode, data);
 selectPictureManager.onActivityResult(requestCode, resultCode, data);
 }
​
 @Override
 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 selectPictureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
 }
  • 配置权限
<uses-feature android:name="android.hardware.camera" />
<!--相机权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--写入SD卡的权限:保存相机拍照后的照片-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--读取SD卡的权限:打开相册选取图片所必须的权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

参考资料

FileProvider

checkSelfPermission失效问题

谷歌

代码下载地址

以上!