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

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

一、应用场景


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

二、业务逻辑

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

拍照逻辑:

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

选择图片逻辑:

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


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

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

三、拍照具体实现

以如下使用场景为例:
<img src="http://upload-images.jianshu.io/upload_images/3985563-b82a260a49b89082.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width = "250" height = "450" align=center />
头像上传的使用。

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上有很多优秀的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • 朦胧的快要看不见时,才知道最近的远方是故乡。
    夏末MOMO阅读 108评论 0 0
  • 我们每个人都是互相影响的,比如:孩子会受父母影响,老公会受老婆影响,员工会所领导影响,学生会受老师影响。除了家人我...
    营养私教西西阅读 399评论 0 1
  • 知晓真相的我 从那一刻起,才发现 我对于你,仅仅是利益 如今,为了那一点小小的利益 你毫不保留地将我舍弃 可能不是...
    梓晔漫漫阅读 196评论 0 2