借助系统自带图片裁剪集成图片选择以及7.0适配

1、前文

  以前使用的图片裁剪功能一直是使用第三方的,也没时间去思考本身写一个的想法。后来无心间发现android本身原本就有裁剪功能,因此本身动手去集成了一把,而且把本身的权限封装以及7.0的适配都加进去php

2、注意的几个点

  其实也没有什么好说的,基本没有难度,只是有几个须要注意的点
1.一个是7.0的文件安全机制,7.0以后android对于文件的安全增长了保护,在部分地方使用Uri会产生FileUriExposedException文件暴露异常。
2.其次,就是对于权限的封装,只有拿到了权限才能进行操做,作好权限适配,这些下面会一并讲到。
java

3、权限封装

  权限封装最好封装到一个方法里面,独立处理权限,这样也能够比较轻松的集成在BaseActivity里面,在用的地方直接调,能够达到权限随用随取得效果。先看看关键代码android

public void needPermission(AppPermissionListener mAppPermissionListener, List<String> permissions) {
        if (null != permissions && permissions.size() > 0) {
            this.appPermissionListener = mAppPermissionListener;
            this.allPermission = permissions;
            // 容许的权限
            allowPermission.clear();
            // 被拒绝的权限
            deniedPermission.clear();
            // 不在询问的权限
            neverAskPermission.clear();
            // 开始遍历拿到的权限
            Observable.fromIterable(allPermission)
                    .subscribe(new Consumer<String>() {
                        @Override
                        public void accept(String s) throws Exception {
                            // 判断是否有WRITE_SETTINGS特殊权限
                            if (s.equals(Manifest.permission.WRITE_SETTINGS)) {
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
	                                // WRITE_SETTINGS须要用该方式判断
                                    if (!Settings.System.canWrite(mActivity))
                                        allowPermission.add(s);
                                    else
                                        haveWriteSetting = true;
                                }
                            } else if (ActivityCompat.checkSelfPermission(mActivity, s)         
                            == PackageManager.PERMISSION_GRANTED) {
                                allowPermission.add(s);
                            } else {
                                deniedPermission.add(s);
                            }
                        }
                    });
            if (haveWriteSetting) {
	            // 请求特殊权限
                requestWriteSettings();
                return;
            }
            if (!deniedPermission.isEmpty()) {
                String[] tempArray = new String[deniedPermission.size()];
                deniedPermission.toArray(tempArray);
                requestPermissions(tempArray);
                if (null != mAppPermissionListener) {
                    mAppPermissionListener.onHaveDenied(deniedPermission);
                }
                return;
            }
            if (!neverAskPermission.isEmpty()) {
                showNeverAskDialog();
                if (null != mAppPermissionListener) {
                    mAppPermissionListener.onNeverAsk(neverAskPermission);
                }
                return;
            }
            // 权限经过,回调onAllGranted方法
            if (null != mAppPermissionListener) {
                mAppPermissionListener.onAllGranted();
            }
        }
    }
复制代码

android有两个特殊权限,一个是WRITE_SETTINGS,另一个是SYSTEM_ALERT_WINDOW,这里只对WRITE_SETTINGS作了处理,我在想有没有更优雅的方法把SYSTEM_ALERT_WINDOW加进去,因此后面会完善。   在这里处理了会触发拒绝权限的弹框。可是当用户点击了再也不询问权限,就不会再弹框。android原生给咱们提供了shouldShowRequestPermissionRationale()方法来判断是否再也不弹框。可是 这个方法会在部分手机上失效,好比我手中的魅族pro6手机,可能因为ROM问题,在调用该方法的时候就是失效的。那么,咱们能够用另一种方法来达到相同的目的。
  咱们能够直接调用requestPermissions方法,在onRequestPermissionsResult的权限回调方法中进行检查,若是拒绝权限列表中还有这个权限的话,就能够进行弹框引导用户去设置中手动打开。不但替代了shouldShowRequestPermissionRationale方法,并且在用户一通拒绝以后还能给予提醒。下面是onRequestPermissionsResult方法的代码
git

// 权限返回结果
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CODE) {
            for (int i = 0; i < permissions.length; i++) {
                String per = permissions[i];
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    if (deniedPermission.contains(per)) {
                        deniedPermission.remove(per);
                    } else if (neverAskPermission.contains(per)) {
                        neverAskPermission.remove(per);
                    }
                    if (!allowPermission.contains(per))
                        allowPermission.add(per);
                } else {
                    if (deniedPermission.contains(per)) {
                        deniedPermission.remove(per);
                        neverAskPermission.add(per);
                    }
                }
            }
            if (!deniedPermission.isEmpty()) {
                neverAskPermission.addAll(new ArrayList<>(deniedPermission));
                deniedPermission.clear();
            }
            if (!neverAskPermission.isEmpty()) {
                showNeverAskDialog();
                if (null != appPermissionListener) {
                    appPermissionListener.onNeverAsk(neverAskPermission);
                }
                return;
            }
            if (null != appPermissionListener) {
                appPermissionListener.onAllGranted();
            }
        }
    }
复制代码

4、7.0文件安全机制

  很多安卓应用开发的程序员,一直都不多有机会接触到安卓四大组件之一的Content Provider,可是因为android7.0文件安全机制的限制,使咱们不得不去接触这个组件。我我的以为这也是一件好事情。不用再只局限于部分组件的开发。
  首先咱们须要在res文件下简历一个xml文件夹,而后再创建一个xml文件,这个xml文件名字本身能够随便命名,这里我命名为file_path。以下图所示程序员

xml文件位置

  file_path里面的内容有事有讲究的。里面有一个root-path 节点,虽然说android会提示element roo-path is not allowed here。可是我目前尚未找到在不用这个节点的时候,可以正常运行的手段。若是有哪位大神知道,能够指出。github

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="path/" />
    <cache-path name="my_cache" path="path/" />
    <external-files-path name="my_file" path="path/" />
    <external-path name="myApp_file" path="path/" />
    <external-cache-path name="myApp_cache" path="path/" />
    <root-path name="myApp" path="" />
</paths>
复制代码

而后,咱们须要创建一个本身的provider。这样在问题出现的时候有利于查找问题。最后,咱们须要在Manifest里面声名这个Provdier。以下所示安全

<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.common.MyImageFileProvider" android:exported="false" android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_path" />
        </provider>
复制代码

这里使用了${applicationId}来代替包名很方便,可是在java代码中仍是须要写彻底。 接下来讲使用。使用起来就会变得很简单,一句代码就能够了app

FileProvider.getUriForFile(context, DEFAULT_AUTHORITIES, imageFile)
复制代码

这个方法会返回Uri对象,在须要的地方进行使用。ide

5、如何调用相机、图库以及裁剪工具

相机和图库的调用,网上其实已经在不少地方写烂了,我这里直接贴出个人方法工具

/** * 打开图库 */
    private void openGallery() {
        initImageFile();
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity) context).startActivityForResult(intent, REQUEST_PICK);
    }

    /** * 开启摄像头 */
    private void doPhoto() {
        initImageFile();
        Uri uri = FileProvider.getUriForFile(context, DEFAULT_AUTHORITIES, imageFile);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, 
	        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        ((Activity) context).startActivityForResult(intent, REQUEST_PICK);
    }
复制代码

接下来,在打开系统自带裁剪工具的时候会有个坑,也是android 7.0的文件安全机制的问题。不过这个和以前的文件安全机制不同。使用FileProvider会提示"没法加载该图片"。经过找到该图片路径能够了解到,android7.0的文件安全机制实际上是但愿能作成相似IOS沙盒机制的效果,每一个app只能访问本身的沙盒,可是从系统相册拿到的图片却不属于当前app自己,因此咱们这时候就须要一个临时受权。intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
调用系统裁剪功能的代码以下

private void beginCropDirect(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        //添加这一句表示对目标应用临时受权该Uri所表明的文件
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        //能够选择图片类型,若是是*代表全部类型的图片
        intent.setDataAndType(uri, "image/*");
        // 下面这个crop = true是设置在开启的Intent中设置显示的VIEW可裁剪
        intent.putExtra("crop", "true");
        // aspectX aspectY 是宽高的比例,这里设置的是正方形(长宽比为1:1)
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪图片宽高
        intent.putExtra("outputX", 500);
        intent.putExtra("outputY", 500);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        //是否将数据保留在Bitmap中返回,true返回bitmap,false返回uri
        intent.putExtra("return-data", false);
        ((Activity) context).startActivityForResult(intent, REQUEST_CROP);
    }
复制代码

本文代码:https://github.com/Kongdy/ImageCropBySystem 我的github地址:https://github.com/Kongdy 我的掘金主页:https://juejin.im/user/595a64def265da6c2153545b csdn主页:http://blog.csdn.net/u014303003

相关文章
相关标签/搜索