Android 拍照、照片选择以及图片裁剪完全解析

2,703 阅读5分钟

Android中头像选择,图片上传等功能几乎是每一个APP必备的功能,那么关于怎么使用相机,如何进行照片选择,以及选择后的图片裁剪,这一系列的问题都需要逐一解决。这也是本篇文章的主要内容。

一、应用场景



微信朋友圈上传图片,头像上传等功能,经常就会用到以上功能。

二、业务逻辑

主要分为两种业务逻辑:拍照,选择图片。

拍照逻辑:

1.A 界面,点击按钮调用相机拍照;
2.拍照界面拍照后,点击确认得到拍完照片,跳转到 B 界面进行预览;
3.B 界面进行图片裁剪,裁剪后确认,返回A界面进行图片回显;

选择图片逻辑:

1.A界面,点击按钮调用相册选择图片;
2.相册界面选择图片后,跳转到B界面进行预览;
3.B 界面进行图片裁剪,裁剪后确认,返回A界面进行图片回显;


从上面可以清楚地看出,两种方式的主要区别在第一步上面,一种是选择调用相机,另一种选择是调用相册。

下面我们来介绍具体代码逻辑。

三、拍照具体实现

以如下使用场景为例:



头像上传的使用。

1.调用相机

Android 程序上实现拍照功能的方式分为两种:第一种是利用相机的 API 来自定义相机,第二种是利用 Intent 调用系统指定的相机拍照。下面讲的内容都是针对第二种实现方式的应用。

简单使用

Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri fileUri = Uri.fromFile(mPhotoFile);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(captureIntent, CAPTURE_PHOTO_REQUEST_CODE);

很简单,通过上述四行代码就实现了调用系统相机。

加入MediaStore.EXTRA_OUTPUT,使得拍照后的图片输出到对应路径下。

然而由于Android手机的碎片化,我们之前调用系统指定的相机app来拍照,有些手机可能会没有这个app,所以在使用之前要检查是否有系统相机。

/**
     * 判断系统中是否存在可以启动的相机应用
     *
     * @return 存在返回true,不存在返回false
     */
    public boolean hasCamera() {
        PackageManager packageManager = mActivity.getPackageManager();
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        return list.size() > 0;
    }

2.照片预览及图片裁剪

由于之前使用startActivityForResult方式调用系统相机,那么在拍照完成后,会返回到上述页面,这时候需要重写onActivityResult方法,根据之前传入的mPhotoFile路径,就获取了图片所在地,因为拍照后的图片就存在该路径下。

然后我们只需要在开启一个照片预览Activity,进行后续裁剪就可以了。

因为这里我们调用系统裁剪,所以就不设置预览Activity,直接跳转到裁剪页面就可以了。

  //拍照完成后 获取目标文件 跳转到裁剪页面
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CapturePhotoHelper.CAPTURE_PHOTO_REQUEST_CODE) {
            //获取拍照后图片路径
            File photoFile = mCapturePhotoHelper.getPhoto();
            if (photoFile != null) {
                if (resultCode == RESULT_OK) {
                    Uri uri = Uri.fromFile(photoFile);
                    Intent intent = new Intent("com.android.camera.action.CROP");
                    intent.setDataAndType(uri, "image/*");
                    //intent.putExtra("crop", "true");
                    intent.putExtra("aspectX", 1);
                    intent.putExtra("aspectY", 1);
                    intent.putExtra("outputX", 300);
                    intent.putExtra("outputY", 300);
                    intent.putExtra("scale", true);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                    intent.putExtra("return-data", false);
                    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
                    intent.putExtra("noFaceDetection", true); // no face detection
                    intent = Intent.createChooser(intent, "裁剪图片");
                    startActivityForResult(intent, REQUEST_PICKER_AND_CROP);
                } else {
                    if (photoFile.exists()) {
                        photoFile.delete();
                    }
                }
            }

        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

其中要注意几个 extra 字段:


注意:return-data: 设为 true 的时候,在 onActivityResult() 中可以直接通过 data.getParcelableExtra("data") 得到裁剪后的 Bitmap 对象。但是当 Bitmap 过大时,就不能使用这种方法了,容易出现OOM现象,需要通过获取文件,然后先缩放,再加载。

如下图所示:


3.回显图片

如上如所示,修剪图片完成后,点击确定,然后就可以编写回显逻辑。

同样在onActivityReuslt方法中

if(requestCode ==REQUEST_PICKER_AND_CROP){
            File photoFile = mCapturePhotoHelper.getPhoto();
            //存放到相册
            BitmapUtils.displayToGallery(this, photoFile);
            //更新UI 显示图像
            InformationBean informationBean = mList.get(0);
            informationBean.setContent(photoFile.getAbsoluteFile().toString());
            informationBean.setSet(true);
            mAdapter.notifyItemChanged(0);

        }

上述采用了RecyclerView,具体更新头像逻辑在Adapter中。

不过同样由于Android碎片化问题,图片会产生各种各样的问题,比如:拍出来的照片“歪了”,拍完照怎么闪退了,图片无法显示

以上这些问题都比较坑,不过不要紧,已经有前人为我们趟出一条血路,全部都处理好了,详细代码及具体实现参考:你需要知道的Android拍照适配方案

这里我们只负责应用就好了:

                String content = bean.getContent();
                final File file = new File(content);
                ((SpecialViewHolder) holder).mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        ((SpecialViewHolder) holder).mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        mWidth = ((SpecialViewHolder) holder).mImageView.getMeasuredWidth();
                        mHeight = ((SpecialViewHolder) holder).mImageView.getMeasuredHeight();
                        Bitmap bitmap = BitmapUtils.decodeBitmapFromFile(file, mWidth, mHeight);
                        if (bitmap != null) {
                            //检查是否有被旋转,并进行纠正
                            System.out.println("文件所占空间:"+"file.getTotalSpace()");
                            int degree = BitmapUtils.getBitmapDegree(file.getAbsolutePath());
                            if (degree != 0) {
                                bitmap = BitmapUtils.rotateBitmapByDegree(bitmap, degree);
                            }
                            ((SpecialViewHolder) holder).mImageView.setImageBitmap(bitmap);
                        }

                    }
                });

先获取控件的宽高,进行压缩,避免图片无法显示的问题。

然后检查有没有被旋转,如果旋转,那么通过矩阵矫正,避免照片"歪了"的问题。

至于拍完照片后闪退,可以通过重写onSaveInstanceState 和 onRestoreInstanceState 来实现。

回显结果:


四、选择图片具体实现

由于和上述方式只是第一步有区别,我们就具体看看第一步的实现。

 Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);
        startActivityForResult(intent, REQUEST_PICK_IMAGE);

直接开启系统图片选择应用即可,不用额外设置MediaStore.EXTRA_OUTPUT,因为图片已经保存在数据库内了,可直接获取,如下所示。

然后在onActivityResult中,获取图片存储路径,跳转到裁剪页面。

if (requestCode == REQUEST_PICK_IMAGE) {
            //获取选择图片后图片路径

            if (resultCode == RESULT_OK) {
                Uri uri =  data.getData();
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(uri, "image/*");
                intent.putExtra("aspectX", 1);
                intent.putExtra("aspectY", 1);
                intent.putExtra("outputX", 200);
                intent.putExtra("outputY", 200);
                intent.putExtra("scale", true);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
                intent.putExtra("return-data", false);
                intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
                intent.putExtra("noFaceDetection", true); // no face detection
                intent = Intent.createChooser(intent, "裁剪图片");
                startActivityForResult(intent, REQUEST_PICKER_AND_CROP_2);
            }
        }

后面回显步骤和上述一致。

五、总结

上述介绍了拍照,照片选择以及图片剪裁的使用,由于使用的是系统自带的应用,所以可能出现一些意想不到的适配问题,还有待解决。另外,由于是系统自带,功能比较单一,且无法使用个性化,如有这方面的需求,可以使用一些流行的第三方库,Github上有很多优秀的实现。