Android调用相机、相册、裁剪(适配7.0和MIUI系统)

实现设置头像功能的具体步骤

  1. 创建实现功能的视图界面
  2. 设置权限(android 6.0以上需要动态申请)
  3. 选择拍照或者相册选择图片
  4. 选择图片后的进行裁剪(看需求)
  5. 裁剪完后对结果进行处理(设置到界面、上传到服务器等)

最后会贴上整个测试类的代码

1、创建实现功能的视图界面

类似如下所示,具体内容根据项目需求去实现


设置头像.png

设置方式.png

2、设置权限(6.0以上需要动态申请)

首先在AndroidManifest.xml中添加以下权限

    <!--相机权限-->
    <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" />

6.0以上系统需要动态权限适配

这里用的是RxPermissions来进行权限申请

//检查有无相机使用权限,没有的话动态申请
if (ActivityCompat.checkSelfPermission(TestActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {

                            new RxPermissions(TestActivity.this).requestEach(Manifest.permission.CAMERA).subscribe(new Consumer<Permission>() {
                                @Override
                                public void accept(Permission permission) throws Exception {
                                    if (permission.granted) {
                                        // 用户已经同意该权限
                                        LogUtil.e(TAG, permission.name + " is granted.");
                                        takePhoto();

                                    } else if (permission.shouldShowRequestPermissionRationale) {
                                        // 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时,还会提示请求权限的对话框
                                        LogUtil.e(TAG, permission.name + " is denied. More info should be provided.");
                                        Toast.makeText(TestActivity.this, "未能授予权限,部分功能可能不能正常使用", Toast.LENGTH_SHORT).show();

                                    } else {
                                        // 用户拒绝了该权限,并且选中『不再询问』
                                        LogUtil.e(TAG, permission.name + " is denied.");

                                        Toast.makeText(TestActivity.this, "请打开相机权限", Toast.LENGTH_SHORT).show();
                                        //引导用户至设置页手动授权
                                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                        Uri uri = Uri.fromParts("package", getApplicationContext().getPackageName(), null);
                                        intent.setData(uri);
                                        startActivity(intent);
                                    }
                                }
                            });
                        } else {
                            takePhoto();
                        }

3、选择拍照或者相册选择图片

这里需要注意Android 7.0 以后对于共享文件路径的处理
具体请参考FileProvider(应用间共享文件)

/**
     * 拍照
     */
    private void takePhoto() {
        //创建保存拍照的文件
        outFile = new File(getExternalFilesDir(""), "head.jpg");
        if (outFile.exists()) {
            outFile.delete();
        }
        try {
            //创建文件
            outFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("createXMLFileException", e.getMessage());
        }

        // 启动系统相机
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断7.0 android系统
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //临时添加一个拍照权限
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //通过FileProvider获取uri
            outUri = FileProvider.getUriForFile(TestActivity.this,
                    "com.lin.fileProvider", outFile);
            //将拍取的照片保存到指定URI
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
        } else {
            outUri = Uri.fromFile(outFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
        }
        startActivityForResult(intent, PHOTO_PZ);
    }

 /**
     * 从相册选择
     */
    private void selectPhoto() {
        //调用系统图库,选择图片
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(
                MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
        //返回结果和标识
        startActivityForResult(intent, PHOTO_TK);
    }

4、选择图片后的进行裁剪(看需求)

一般用作头像的图片不宜太大,最好作适当的裁剪
这里也涉及到FileProvider(应用间共享文件),还有对MIUI系统的适配,具体看注释

 /**
     * 裁剪照片
     *
     * @param uri
     */
    private void cutPhoto(Uri uri) {
        //裁剪完成后保存的文件
        cutOutFile = new File(getExternalFilesDir(""), "cutHead.jpg");
        if (cutOutFile.exists()) {
            cutOutFile.delete();
        }
        try {
            //创建文件
            cutOutFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("createXMLFileException", e.getMessage());
        }
        Log.e("Uri====", uri + "");
        //裁剪图片意图
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        // 裁剪框的比例,1:1
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // 裁剪后输出图片的尺寸大小
        intent.putExtra("outputX", 320);
        intent.putExtra("outputY", 320);
        //设置裁剪完保存的URI
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //开启临时权限
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //重点:针对7.0以上的操作
            cutOutUri = FileProvider.getUriForFile(TestActivity.this,
                    "com.lin.fileProvider", cutOutFile);
            intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, cutOutUri));
        } else {
            cutOutUri = Uri.parse("file://" + "/" + cutOutFile.getPath());
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cutOutUri);
        intent.putExtra("return-data", false);
        /**
         * return-data
         * 这个属性决定我们在 onActivityResult 中接收到的是什么数据,
         * 如果设置为true 那么data将会返回一个bitmap
         * 如果设置为false,则会将图片保存到本地并将对应的uri返回,当然这个uri得有我们自己设定。
         * 系统裁剪完成后将会将裁剪完成的图片保存在我们所这设定这个uri地址上。我们只需要在裁剪完成后直接调用该uri来设置图片,就可以了。
         */
        //intent.putExtra("return-data", true); // 重点:小米的系统使用这个会使返回数据为null,所以设置为false用uri来获取数据
        //图片输出格式
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        //头像识别 会启动系统的拍照时人脸识别
        intent.putExtra("noFaceDetection", true);
        startActivityForResult(intent, PHOTO_CLIP);
    }

5、裁剪完后对结果进行处理(设置到界面、上传到服务器等)

在onActivityResult方法中根据请求码对拍照、相册选择、裁剪完后的结果进行处理,大致流程如下图

image.png

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case PHOTO_PZ:
                    cutPhoto(outUri);
                    break;
                case PHOTO_TK:
                    //获取图库结果,执行裁剪
                    cutPhoto(data.getData());
                    break;
                case PHOTO_CLIP:
                    //1.裁剪时,这样设置 cropIntent.putExtra("return-data", true); 处理方案如下
//                    if (data != null) {
//                        Bundle bundle = data.getExtras();
//                        if (bundle != null) {
//                            Bitmap bitmap = bundle.getParcelable("data");
//                            ivHead.setImageBitmap(bitmap);
//                            // 把裁剪后的图片保存至本地
//                            FileUtils.writeImageToFile("cutHead.jpg", bitmap);
//                        }
//                    }
                    //2.cropIntent.putExtra("return-data", false);处理方案如下
                    //裁剪完成后的操作,上传至服务器或者本地设置
                    //将Uri图片转换为Bitmap
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(cutOutUri));
                        ivHead.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        LogUtil.e(e.toString());
                    }
                    break;
                default:
                    break;
            }
        }
    }

这里贴上整个测试类的代码

/**
 * @author Lin
 * @date 2019/5/10
 * @description 选择头像功能
 */

public class TestActivity extends BaseActivity {

    private ImageView iconBack;
    private RelativeLayout rlHead;
    private CircleImageView ivHead;
    private SelectPictureDialog dialog;

    private Uri outUri;
    private Uri cutOutUri;

    //拍照保存的文件
    private File outFile;
    //裁剪完保存的图片
    private File cutOutFile;
    //图库
    private static final int PHOTO_TK = 0;
    //拍照
    private static final int PHOTO_PZ = 1;
    //裁剪
    private static final int PHOTO_CLIP = 2;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_test;
    }

    @Override
    protected void initView() {
        iconBack = findViewById(R.id.icon_back);
        rlHead = findViewById(R.id.rl_head);
        ivHead = findViewById(R.id.iv_head);
    }

    @Override
    protected void initData(Bundle savedInstanceState) {
    }

    @Override
    protected void setEvent() {
        rlHead.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.rl_head:
                dialog = new SelectPictureDialog(this, R.style.MyAppDialog);
                dialog.show();
                dialog.setListener(new SelectPictureDialog.OnSelectListener() {
                    @Override
                    public void onClickTakePhoto() {
                        dialog.dismiss();
                        if (ActivityCompat.checkSelfPermission(TestActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {

                            new RxPermissions(TestActivity.this).requestEach(Manifest.permission.CAMERA).subscribe(new Consumer<Permission>() {
                                @Override
                                public void accept(Permission permission) throws Exception {
                                    if (permission.granted) {
                                        // 用户已经同意该权限
                                        LogUtil.e(TAG, permission.name + " is granted.");
                                        takePhoto();

                                    } else if (permission.shouldShowRequestPermissionRationale) {
                                        // 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时,还会提示请求权限的对话框
                                        LogUtil.e(TAG, permission.name + " is denied. More info should be provided.");
                                        Toast.makeText(TestActivity.this, "未能授予权限,部分功能可能不能正常使用", Toast.LENGTH_SHORT).show();

                                    } else {
                                        // 用户拒绝了该权限,并且选中『不再询问』
                                        LogUtil.e(TAG, permission.name + " is denied.");

                                        Toast.makeText(TestActivity.this, "请打开相机权限", Toast.LENGTH_SHORT).show();
                                        //引导用户至设置页手动授权
                                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                        Uri uri = Uri.fromParts("package", getApplicationContext().getPackageName(), null);
                                        intent.setData(uri);
                                        startActivity(intent);
                                    }
                                }
                            });
                        } else {
                            takePhoto();
                        }
                    }

                    @Override
                    public void onClickAlbum() {
                        dialog.dismiss();
                        selectPhoto();
                    }

                    @Override
                    public void onClickCancel() {
                        //不用操作
                    }
                });
                break;
            default:
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case PHOTO_PZ:
                    cutPhoto(outUri);
                    break;
                case PHOTO_TK:
                    //获取图库结果,执行裁剪
                    cutPhoto(data.getData());
                    break;
                case PHOTO_CLIP:
                    //1.裁剪时,这样设置 cropIntent.putExtra("return-data", true); 处理方案如下
//                    if (data != null) {
//                        Bundle bundle = data.getExtras();
//                        if (bundle != null) {
//                            Bitmap bitmap = bundle.getParcelable("data");
//                            ivHead.setImageBitmap(bitmap);
//                            // 把裁剪后的图片保存至本地
//                            FileUtils.writeImageToFile("cutHead.jpg", bitmap);
//                        }
//                    }
                    //2.cropIntent.putExtra("return-data", false);处理方案如下
                    //裁剪完成后的操作,上传至服务器或者本地设置
                    //将Uri图片转换为Bitmap
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(cutOutUri));
                        ivHead.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        LogUtil.e(e.toString());
                    }
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 从相册选择
     */
    private void selectPhoto() {
        //调用系统图库,选择图片
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(
                MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
        //返回结果和标识
        startActivityForResult(intent, PHOTO_TK);
    }

    /**
     * 拍照
     */
    private void takePhoto() {
        //创建保存拍照的文件
        outFile = new File(getExternalFilesDir(""), "head.jpg");
        if (outFile.exists()) {
            outFile.delete();
        }
        try {
            //创建文件
            outFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("createXMLFileException", e.getMessage());
        }

        // 启动系统相机
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断7.0 android系统
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //临时添加一个拍照权限
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //通过FileProvider获取uri
            outUri = FileProvider.getUriForFile(TestActivity.this,
                    "com.lin.fileProvider", outFile);
            //将拍取的照片保存到指定URI
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
        } else {
            outUri = Uri.fromFile(outFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
        }
        startActivityForResult(intent, PHOTO_PZ);
    }

    /**
     * 裁剪照片
     *
     * @param uri
     */
    private void cutPhoto(Uri uri) {
        //裁剪完成后保存的文件
        cutOutFile = new File(getExternalFilesDir(""), "cutHead.jpg");
        if (cutOutFile.exists()) {
            cutOutFile.delete();
        }
        try {
            //创建文件
            cutOutFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("createXMLFileException", e.getMessage());
        }
        Log.e("Uri====", uri + "");
        //裁剪图片意图
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        // 裁剪框的比例,1:1
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // 裁剪后输出图片的尺寸大小
        intent.putExtra("outputX", 320);
        intent.putExtra("outputY", 320);
        //设置裁剪完保存的URI
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //开启临时权限
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //重点:针对7.0以上的操作
            cutOutUri = FileProvider.getUriForFile(TestActivity.this,
                    "com.lin.fileProvider", cutOutFile);
            intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, cutOutUri));
        } else {
            cutOutUri = Uri.parse("file://" + "/" + cutOutFile.getPath());
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cutOutUri);
        intent.putExtra("return-data", false);
        /**
         * return-data
         * 这个属性决定我们在 onActivityResult 中接收到的是什么数据,
         * 如果设置为true 那么data将会返回一个bitmap
         * 如果设置为false,则会将图片保存到本地并将对应的uri返回,当然这个uri得有我们自己设定。
         * 系统裁剪完成后将会将裁剪完成的图片保存在我们所这设定这个uri地址上。我们只需要在裁剪完成后直接调用该uri来设置图片,就可以了。
         */
        //intent.putExtra("return-data", true); // 重点:小米的系统使用这个会使返回数据为null,所以设置为false用uri来获取数据
        //图片输出格式
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        //头像识别 会启动系统的拍照时人脸识别
        intent.putExtra("noFaceDetection", true);
        startActivityForResult(intent, PHOTO_CLIP);
    }
}