Android 图像选取 图片剪裁 照相选图 照相裁剪 大全 6-19更新

前言

已经完整打包成一个工具 , 添加了图像压缩和修改了图像剪裁功能 , 项目地址在这里

https://github.com/ocwvar/PicturePicker

本篇讲的是使用 “Intent.ACTION_PICK” 来选取图片并进行剪裁加载的操作 , 包括以下两个功能

  • 从本地相册读取图片进行剪裁
  • 从照相机获取图片进行剪裁

注意: 本篇使用一个工具类PickUriUtils 使Uri转换成文件路径 , 工具类在文章最后给出. 本文的Bitmap对象没有进行回收和缓存 , 在真正的使用中是需要进行相关的操作的 , 由于这里是演示 , 就不做多余的处理了.

技术注意点

1.返回值区别

因为我们的选图是通过第三方的选图应用来操作的 , 所以他们之间可能会有些区别 , 我们要特别注意!!

获取剪裁图片一般会有两个过程 , 第一步是获取图像 , 第二步是剪裁图像 , 每一步的返回数据会有以下的类型

  1. 返回Context Uri路径 —— 同时携带Bitmap对象
  2. 返回文件 Uri路径 —— 同时携带Bitmap对象
  3. 仅仅返回Bitmap对象

携带Bitmap对象需要”return-data” 为 true 的状态下 , 如果为 false ,在某些选图软件则什么东西都不会返回来 , 所以返回Bitmap对象是必须的 , 但我们可以选择性使用

2.文件解析异常

在某些图库裁剪之后生成的图像文件无法被 BitmapFactory.decodeFile 解析出来 , 但这个过程是没有异常产生的 , 这时候我们就只能使用到携带的 Bitmap 对象了.

3.选取&剪裁流程区别

在一些图库中 , 你选取完图像之后会启动自带的剪裁工具进行剪裁 或 启动第三方应用剪裁 , 这是最理想的状态. 但有的图库选取完了之后不会启动任何剪裁界面 , 而且直接返回一个Uri路径 , 这时候我们就需要单独对返回的图像进行剪裁.

开始编码

主要的注意点说完了 , 下面我们就开始正式编码! 我们一个个功能来做.剩下的一些细小的注意点我们边写边说.

1.使用到的参数变量

    //请求码
    private final int REQUEST_PERMISSION = 300;
    private final int REQUEST_CAMERA = 301;
    private final int REQUEST_LOCAL = 302;
    private final int REQUEST_CUT = 303;

    //请求参数
    //临时储存点1
    private final String TEMPSAVE_PATH = Environment.getExternalStorageDirectory().getPath()+"/temp.jpg";
    //临时储存点2
    private final String TEMPSAVE_PATH2 = Environment.getExternalStorageDirectory().getPath()+"/temp2.jpg";
    //剪裁图像的长
    private final int PICTURE_WIDTH = 200;
    //剪裁图像的高
    private final int PICTURE_HEIGHT = 200;

    //用于显示的ImageView
    ImageView shower;
    //用于储存得到的Bitmap对象
    Bitmap bitmap;

2.从图库选择图像

请求Intent构建

    /**
     * 启动图片选取界面
     */
    private void requestPickFromLocal(){

        //我们需要将选取到的图像
        File tempFile = new File(TEMPSAVE_PATH);
        try {
            //创建临时文件
            tempFile.createNewFile();
            Intent intent = new Intent(Intent.ACTION_PICK,null);
            //选取的是图像类型
            intent.setType("image/*");
            //请求剪裁 (不一定有卵用)
            intent.putExtra("crop", "true");
            //X轴剪裁比例 , 一般为 1
            intent.putExtra("aspectX", 1);
            //Y轴剪裁比例 , 一般为 1
            intent.putExtra("aspectY", 1);
            //X轴剪裁长度
            intent.putExtra("outputX", PICTURE_WIDTH);
            //Y轴剪裁长度
            intent.putExtra("outputY", PICTURE_HEIGHT);
            //是否将剪裁后的图像以Bitmap返回
            intent.putExtra("return-data", true);
            //是否允许缩放 (不一定有卵用)
            intent.putExtra("scale", true);
            //文件输出格式 (不一定有卵用)
            intent.putExtra("outputFormat" , Bitmap.CompressFormat.JPEG.toString());
            //不进行脸部识别 (不一定有卵用)
            intent.putExtra("noFaceDetection", true);
            //得到的文件对象存放的位置 , 以Uri路径
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
            startActivityForResult(intent, REQUEST_LOCAL);
        } catch (IOException e) {
            Toast.makeText(MainActivity.this, "无法创建临时文件", Toast.LENGTH_SHORT).show();
            tempFile = null;
        }
    }

处理从图库操作后得到的数据

    /**
     * 处理本地选取图片后的结果
     * @param intent    返回的结果 Intent
     * @return  0:失败 1:成功 2:还需要进行剪裁处理
     */
    private int handlePickFromLocal(Intent intent){
        if(intent == null){
            //Intent 为空 , 则这次的请求失败
            return 0;
        }else if (intent.getData() != null){
            // Intent 携带有 Uri 路径 , 我们优先使用它
            String resultPath = PickUriUtils.getPath(MainActivity.this , intent.getData());

            if (resultPath.equals(TEMPSAVE_PATH)){
                //如果返回的路径和指定的临时路径相同 , 则说明图片已经剪裁过 , 否则没有
                this.bitmap = BitmapFactory.decodeFile(TEMPSAVE_PATH);
                if (this.bitmap == null){
                    //Uri读取失败 , 尝试通过Bitmap读取
                    if (intent.hasExtra("data")){
                        //如果存在数据 , 则读取 , 否则当作失败
                        this.bitmap = intent.getParcelableExtra("data");
                        if (this.bitmap != null){
                            //读取成功
                            return 1;
                        }else {
                            //读取失败
                            return 0;
                        }
                    }else {
                        //没有附带数据 , 操作失败
                        return 0;
                    }
                }else {
                    //Uri读取成功
                    return 1;
                }
            }else {
                //进行剪裁之前需要将图像复制到临时文件 , 否则会直接剪裁原始文件并保存 , 导致原始文件被修改
                if (copyFile(resultPath , TEMPSAVE_PATH)){
                    cropImageFromURI(Uri.fromFile(new File(TEMPSAVE_PATH)));
                    return 2;
                }else {
                    return 0;
                }
            }
        }else if (intent.hasExtra("data")){
            //如果 Intent 携带有 Bitmap 对象 , 我们则直接拿出使用

            /**
             * 这个方式虽然直接 , 但是对内存使用并不友好 , 有可能得到的 Bitmap 对象很大
             * 但是有的图像软件裁剪后不会返回 Uri 数据 , 只会返回剪裁后的 Bitmap 对象
             *
             * 比如: 快图浏览 , OPPO自带图库
             */

            this.bitmap = intent.getParcelableExtra("data");
            return 1;
        }else {
            return 0;
        }
    }

3.单独剪裁图像

请求Intent构建

    /**
     * 剪裁图像
     * @param uri   图像Uri
     */
    private void cropImageFromURI(Uri uri){
        //这里我们使用2号临时文件地址 , 因为可能需要剪裁的文件已经存在1号地址 , 如果使用同一个地址会导致剪裁失败
        File tempFile = new File(TEMPSAVE_PATH2);
        try {
            tempFile.createNewFile();
            Intent intent = new Intent("com.android.camera.action.CROP");
            //传入的第一个参数是要剪裁的文件Uri路径
            intent.setDataAndType(uri, "image/*");
            intent.putExtra("crop", "true");
            intent.putExtra("aspectX", 1);
            intent.putExtra("aspectY", 1);
            intent.putExtra("return-data", true);
            intent.putExtra("scale", true);
            intent.putExtra("outputX", PICTURE_WIDTH);
            intent.putExtra("outputY", PICTURE_HEIGHT);
            //剪裁后输出到的位置
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
            startActivityForResult(intent, REQUEST_CUT);
        } catch (IOException e) {
            Toast.makeText(MainActivity.this, "无法创建临时文件", Toast.LENGTH_SHORT).show();
            tempFile = null;
        }
    }

处理剪裁后的数据

     /**
     * 处理剪裁后的数据
     * @param resultCode    返回来的 resultCode
     * @param intent    返回来的 intent
     * @return  执行结果
     */
    private boolean handleCropFromPic(int resultCode , Intent intent){
        if (resultCode == 0){
            //图像裁剪失败
            return false;
        }else if (intent.getData() != null){
            //返回来的是 Uri 路径 , 解析后直接加载即可 , 其文件路径为我们在请求Intent中设定的路径 TEMPSAVE_PATH2
            this.bitmap = BitmapFactory.decodeFile(TEMPSAVE_PATH2);
            if (this.bitmap == null){
                //Uri读取失败 , 尝试读取 Bitmap 对象
                if (intent.hasExtra("data")){
                    //如果存在数据 , 则读取 , 否则当作失败
                    this.bitmap = intent.getParcelableExtra("data");
                }else {
                    //不存在Bitmap数据 , 读取失败
                    return false;
                }
            }
            return this.bitmap != null && !this.bitmap.isRecycled();
        }else if (intent.hasExtra("data")){
            //返回来的是 Bitmap 对象 , 直接加载即可
            this.bitmap = intent.getParcelableExtra("data");
            return this.bitmap != null  && !this.bitmap.isRecycled();
        }else {
            //其他状态 , 为失败
            return false;
        }
    }

以上就是两大功能 , 获取图像&剪裁 下面我们吧一些其他地方补完

4.获取数据以及剩余操作

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode){
            //图像获取请求
            case REQUEST_LOCAL:

                switch (handlePickFromLocal(data)){
                    //处理失败
                    case 0:
                        Toast.makeText(MainActivity.this, "返回数据无效", Toast.LENGTH_SHORT).show();
                        break;
                    //处理成功 , 显示图像
                    case 1:
                        onGotBitmap(this.bitmap);
                        break;
                    //还需要单独剪裁 , 内部已经处理 , 外部就不作处理了
                    case 2:
                    default:
                        break;
                }
                break;
            //单独图像剪裁请求
            case REQUEST_CUT:
                if (!handleCropFromPic(resultCode , data)){
                    Toast.makeText(MainActivity.this, "剪裁无效", Toast.LENGTH_SHORT).show();
                }else {
                    //剪裁处理成功 , 显示图像
                    onGotBitmap(this.bitmap);
                }
                break;
            //照相请求 (我们最后再说这个)
            case REQUEST_CAMERA:
                handlePickFromCamera();
                break;
            default:
                Toast.makeText(MainActivity.this, "未定义操作", Toast.LENGTH_SHORT).show();
                break;
        }
    }

显示图像

    /**
     * 最终获取到图像的时候
     * @param bitmap    得到的Bitmap
     */
    private void onGotBitmap(Bitmap bitmap){
        //显示Bitmap
        shower.setImageBitmap(bitmap);

        //清除临时文件
        new File(TEMPSAVE_PATH).delete();
        new File(TEMPSAVE_PATH2).delete();
    }

复制文件方法

    /**
     * 复制文件
     * @param sourcePosition    源文件路径
     * @param targetPosition    目的地路径
     * @return  执行结果
     */
    private boolean copyFile(String sourcePosition , String targetPosition){
        File sourceFile = new File(sourcePosition);
        File targetFile = new File(targetPosition);
        targetFile.delete();
        try {
            targetFile.createNewFile();
            InputStream inputStream = new FileInputStream(sourceFile);
            OutputStream outputStream = new FileOutputStream(targetFile);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1){
                outputStream.write(buffer,0,length);
            }
            inputStream.close();
            outputStream.flush();
            outputStream.close();
            return true;
        } catch (Exception e) {
            Log.e("复制文件异常", ""+e );
            return false;
        }
    }

5.照相图像处理

为啥我们最后再说这个 , 因为…这个东西没什么可以说的 , 因为它仅仅是获取图像之后单独进行剪裁而已

请求Intent构建

    /**
     * 启动摄像头获取图像
     */
    private void requestPickFromCamera(){
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //将拍照得到的图像 , 存储在临时点1
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(TEMPSAVE_PATH)));
        startActivityForResult(intent, REQUEST_CAMERA);
    }

处理得到的数据

    /**
     * 处理拍照后得到的数据
     */
    private void handlePickFromCamera(){
        /**
         * 拍照后得到照片对象会保存在我们请求中的指定路径内 TEMPSAVE_PATH .
         * 如果设置了带数据返回 "return-data" 则会在Intent中返回一个Bitmap对象
         * 但一般非常不建议这么使用 , 因为一般这个Bitmap对象会很大
         */

        File savedFile = new File(TEMPSAVE_PATH);
        if (savedFile.exists() && savedFile.length() > 0){
            //如果得到的文件存在并且有效 (长度大于0) 我们就将这个对象进行剪裁
            cropImageFromURI(Uri.fromFile(savedFile));
        }else {
            //文件无效 , 操作失败
            Toast.makeText(MainActivity.this, "无效照片文件 , 请检查储存空间是否已满", Toast.LENGTH_SHORT).show();
        }

    }

6.结尾 & 工具类

代码复制粘贴即可使用

public class PickUriUtils {

    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @author paulburke
     */
    public static String getPath(final Context context, final Uri uri) {

        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

}

我会在文章下部评论贴上工程的下载地址 , 有需要的同学可以下载 , 使用Android Studio 导入即可

6-19更新内容

我同学的一部华为手机 , 在选取本地图片的时候发现没有数据返回 , 既没有Bitmap对象 , 也没有Uri路径.

最后我发现虽然什么都没有返回来 , 但是已经在当初 Intent 内指定的位置存放有剪切后的图片了.

以下为更新的代码块 , 同样也会更新工程文件的下载地址

else if (new File(TEMPSAVE_PATH).exists()){
            /**
             * 有可能虽然Intent没有返回路径 , 但是可能会已经把我们要的图像处理完成后存放到我们当初指定的位置内了
             * 有的手机内置图库不会返回路径 同时也不会返回Bitmap数据 , 即使设置了 "return-data", true
             *
             * 比如: 华为荣耀 SCL-CL00 , OS: EMUI 3.1
             *
             */

            if (new File(TEMPSAVE_PATH).length() > 0){
                //如果文件大小大于 0 , 则开始从文件解析图像
                this.bitmap = BitmapFactory.decodeFile(TEMPSAVE_PATH);
                if (this.bitmap != null){
                    //如果解析成功
                    return 1;
                }else {
                    //如果解析失败
                    return 0;
                }
            }else {
                //如果文件虽然存在 , 但是并没有内容 , 则吧这个文件清除 , 同时表示这次读取失败
                new File(TEMPSAVE_PATH).delete();
                return 0;
            }
        }
时间: 2024-09-29 05:49:24

Android 图像选取 图片剪裁 照相选图 照相裁剪 大全 6-19更新的相关文章

图片剪裁控件——ClipImageView

这段时间在做自己的项目时,须要使用到图片剪裁功能,当时大概的思考了一些需求.想到了比較简单的实现方法.因此就抽了点时间做了这个图片剪裁控件--ClipImageView 这里先贴上ClipImageView的代码: package com.example.clipimage; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import a

Android 拍照图片选取与图片剪裁

最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能,尤其在内容型的app中更为常见,那么这些功能是怎么实现的呢?今天,在这里就记录一下好了,防止以后的项目中也会用到,就直接拿来用好了. 1.通过拍照或者图册获取图片(不需要剪裁) 这种获取图片的方式就比较次了,因为不设置图片的剪裁功能,有可能因为图片过大,导致OOM,但是这种方式也是有必要讲一下的,

android拍照图片选取与图片剪裁

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39994913 最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能,尤其在内容型的app中更为常见,那么这些功能是怎么实现的呢?今天,在这里就记录一下好了,防止以后的项目中也会用到,就直接拿来用好了. 1.通过拍照或者图册获取图片(不

Android开发实践:自己动手编写图片剪裁应用(1)

最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用,地址:https://github.com/Jhuster/ImageCropper,效果如下所示: 我的大致计划是首先介绍一下这个库的用法,然后再写几篇文章介绍一下其中的一些原理和关键技术,希望对Android开发新手有所帮助. [特性] 支持通过手势移动和缩放剪裁窗口 支持固定剪裁窗口大小.固定

Android开发实践:自己动手编写图片剪裁应用(2)

上篇文章主要介绍了我开源在Github上的图片剪裁库(ImageCropper)的基本特性和用法,从本文开始,慢慢介绍一些开发图片剪裁应用中涉及的知识点和技术. 其实Android系统本身也提供了图片剪裁的模块,我们可以直接通过Intent来调用系统的图片剪裁功能,本文我们就先了解一下系统自带的图片剪裁功能是如何调用的吧. 得到被剪裁图片的URL地址 既然是图片剪裁,就一定要有被剪裁的图片,由于图片数据一般很大,为了防止内存溢出,普通APP与Android系统图片剪裁应用之间是通过URL来传递图

Android开发实践:自己动手编写图片剪裁应用(3)

前面两篇文章分别介绍了我编写的开源项目ImageCropper库,以及如何调用系统的图片剪裁模块,本文则继续分析一下开发Android图片剪裁应用中需要用到的Bitmap操作. 在Android系统中,对图片的操作主要是通过Bitmap类和Matrix类来完成,本文就介绍一下图片剪裁应用中对Bitmap的一些操作,包括:打开.保存.剪裁.旋转等,我已经将这些操作都封装到了一个BitmapHelper.java类中,放到GitHub上了(点击这里),大家可以方便地集成到自己的项目中. 打开图片 图

Android图片剪裁库

最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用,地址:https://github.com/Jhuster/ImageCropper,效果如下所示: 我的大致计划是首先介绍一下这个库的用法,然后再写几篇文章介绍一下其中的一些原理和关键技术,希望对Android开发新手有所帮助. [特性] 支持通过手势移动和缩放剪裁窗口 支持固定剪裁窗口大小.固定

android之照相、相冊裁剪功能的实现过程

今天无聊做了一些照相.相冊裁剪功能,希望能够帮到大家! 不多说了,贴代码实际一点: 首先是XML: <ImageButton android:id="@+id/imageButton1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/xiaoma" /> <Butt

Android版本微信头像剪裁与系统剪裁之间的选择

当然选类似微信的剪裁咯,为什么?请看下文分析 转载请注明:http://blog.csdn.net/tmacsky/article/details/51179789 众所周知头像剪裁上传是绝大部分APP必备的功能之一,但是剪裁的模式有2种交互形式, 第一种是采用系统自带的剪裁功能,我个人是比较青睐系统的这种交互模式的,但是很可惜,在一些手机上出现问题,不得不弃用 第二种是微信版本自定义剪裁图,当然微信的这个并没有判断图片边界与自定义白色矩形框是否相连,我想这是一个bug吧,属于遗留问题 首先我们