在安卓手机上拍照,选取图片并且进行裁剪

因为在我们的项目《七星计价器》开发过程中,需要实现拍照,选取照片和手机截屏,中间对于不同的手机的兼容上浪费了不少时间,也深知同行们在做这块的时候一定也会重走探索的路线,特分享如下,以免大家同样浪费时间。

基础概念理解

不管是哪个功能,你都要先理解一个概念:

1,拍照功能,选取图片这些功能是系统提供的一个Activity,用来实现相应功能的,而且不同的安卓定制版,可能在这个优化上有所不同。但是基本都遵循了一些基本的规范。

2,要取得拍照或选取图片的Activity的值,就得做两件事,通过 startActivityForResult 来打开Activity,以便在当前Acivity里的 onActivityResult 获取返回的值。

3,要想知道拍照或选取图片需要哪些参数(通过intent传参的时候如何传),如何返回值(拍照如何把照片传回调用方),可以直接看安卓的这部分(相机,选取相册,裁剪图像)源码,讲得非常清楚。

demo演示

 demo如下图:

我的开发环境是 Android Studio 2.0 ,   最后面我会附上实际代码里相关的代码做成的一个测试项目。

调用端代码

整个介绍顺序从调用开始,然后是实现。

先看调用的代码:

 

 iv_userPhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String[] items = new String[]{"拍照", "相册"};
                Dialog alertDialog = new AlertDialog.Builder(getActivity()).
                        setTitle("选择图片来源")
//                        .setIcon(R.drawable.ic_launcher)
                        .setItems(items, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                if (which == 0) {
                                    PhotoHelper.StartCamera(getActivity(), getActivity());
                                } else if (which == 1) {
                                    PhotoHelper.StartSelectPhoto(getActivity(), getActivity());
                                }
                                //Toast.makeText(getActivity(), items[which], Toast.LENGTH_SHORT).show();
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        }).
                                create();
                alertDialog.show();

            }
        });

相关代码被封装在PhotoHelper里了。但是这里有几个点需要了解。

1,这两个方法均没有直接返回值,因为值是通过在onActivityResult 事件里获取的。

2,没有传一些特别的值,因为在PhotoHelper里我已经把相关的诸如拍照图片保存的位置之类的做了处理。实际工作中这个地方可能需要再进行调整。

3,AlertDialog 是最常用的,这可以直接直接使用了,因为已经在生产环境下如此使用了。

业务流程和代码实现

拍照的流程是这样的:

 

 public static void StartCamera(Context context, Activity activity) {
        String status = Environment.getExternalStorageState();
        if (status.equals(Environment.MEDIA_MOUNTED)) {
            try {
                File dir = new File(Environment.getExternalStorageDirectory() + "/" + localTempImgDir);
                if (!dir.exists()) dir.mkdirs();
                Log.e("Tag", "if (!dir.exists()) dir.mkdirs()");
                Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                File f = new File(dir, localTempImgFileName
                Uri u = Uri.fromFile(f);
                intent.putExtra(MediaStore.Images.Media.ORIENTATION, 0);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, u);
                activity.startActivityForResult(intent, CAMERACODE);
            } catch (ActivityNotFoundException e) {
                Toast.makeText(context, "没有找到储存目录", Toast.LENGTH_LONG).show();
            }
        } else {
            Toast.makeText(context, "没有储存卡", Toast.LENGTH_LONG).show();
        }
    }

在拍照前先通过Intent指定拍完照后,照片存储的位置,然后调用 startActivityForResult 来进行调用,在onActivityResult里直接读取之前传入的位置得到文件。 而不是直接从onActivityResult里的data参数获取。

选取图片的过程:

    public static void StartSelectPhoto(Context context, Activity activity) {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        activity.startActivityForResult(intent, SELECTPHOTOCODE);
    }

 代码非常简单。

最麻烦的是裁剪图片,已经做完后,发现还是很简单的,但是中间还是走了很多弯路。

  public static void CropImage(Activity actiivty, Uri uri, int outputX, int outputY) {
        //裁剪图片意图
        Intent intent = new Intent("com.android.camera.action.CROP");
        //intent.setClassName("com.android.camera", "com.android.camera.CropImage");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        Uri uri1=Uri.fromFile(new File(GetCropFilePath()));
        //裁剪框的比例,1:1
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("scale", true);// 去黑边
        intent.putExtra("scaleUpIfNeeded", true);// 去黑边
        //裁剪后输出图片的尺寸大小
        intent.putExtra("outputX", outputX);
        intent.putExtra("outputY", outputY);
        intent.putExtra(MediaStore.EXTRA_OUTPUT,uri1);
        //图片格式
        intent.putExtra("outputFormat", "JPEG");
        intent.putExtra("noFaceDetection", true);
        intent.putExtra("return-data", false);   //在onActiivtyResult里接收数据.
        actiivty.startActivityForResult(intent, CORPIMAGECODE);
    }

 有几个问题要特别强调:

1,在实际使用的时候,网上看的文章都是要设   intent.putExtra("return-data", true); 把这个设成true,以便在onActivityResult里通过data来得到裁剪完后的图像数据,事实上在这里你极有可能是获取不到的,原因就是因为在通过Intent去在不同的Activity里传输数据时,数据大小是受限的,跟手机的硬件配置有关系,如果太大会出现系统级异常,而且开发环境无法捕获。有可能在高配手机上是可以的,低配就不行了。如果你也是设置为true的,可以把图片大小设成100,100看看,应该是能取到图片的.

2,intent.putExtra(MediaStore.EXTRA_OUTPUT,uri1); 这一步是设置裁剪完后图片存储的位置,这样我们就不用非得通过intent来设计返回值了.这样最大的好处是,裁剪的尺寸随你定.可以很大.保证图片的清晰度.

3,跟拍照的思路一样,裁剪完后我们直接去传入的uri1的地方去取剪切生成的文件.

再来看照相和裁剪的onActivityResult的回调

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case PhotoHelper.CAMERACODE:
                if (resultCode == RESULT_OK) {
                    String filePath = PhotoHelper.GetCameraFilePath();
                    Uri uri = Uri.fromFile(new File(filePath));
                    PhotoHelper.CropImage(MainActivity.this, uri, 600, 600);
                }
                break;
            case PhotoHelper.SELECTPHOTOCODE:
                if (resultCode == RESULT_OK) {
                    Uri photouri = data.getData();
                    PhotoHelper.CropImage(MainActivity.this, photouri, 600, 600);
                }
                break;
            case PhotoHelper.CORPIMAGECODE:
                // 取得的图片,裁剪出来的大图
                String cropImagePath = PhotoHelper.GetCropFilePath();
                Bitmap photo = PhotoHelper.GetBitmapFromUri(MainActivity.this, Uri.fromFile(new File(cropImagePath)));
                View view = findViewById(R.id.iv_userPhoto);
                if (view == null) {
                    Toast.makeText(MainActivity.this, "没有找到用户头像显示组件", Toast.LENGTH_LONG).show();
                    return;
                }
                ImageView iv = (ImageView) view;
                iv.setImageBitmap(photo);//显示压缩后的图处到图像组件中
                break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

 

当前activity为首个activity,名为 MainActivity,在这个Activity里分别调用了拍照,选取图片,裁剪的三个系统Activity.完全看懂上面的代码需要注意:

1,所以在返回的时候均在MainActivity.onActivityResult里进行处理.通过在调用时传入不同的requestCode来区分. CORPIMAGECODE 这些为设置的整型常量.

2,在拍完照或者选择完图片后,直接调用了裁剪,也就是说在拍照或者选取图片的Activity返回时,又调用了新的裁剪的Activity.

3,在实际的处理过程中,裁剪完后我直接调用了上传图片的方法上传图片到服务器.这里我省去了.

 

注:Uri 与 文件路径

这里要说明一下Uri. 在android是表示一个文件的资源,他可能是是这个样子的  file:/img/1.jpg   或者content:/1231234

前面是协议,类似http:// 后面是路径. 但是文件路径是这样:    /img/1.jpg  .两者是不一样的.

在界面上显示资源,还有很多其它地方均需要uri。

在上传的时候需要使用文件路径。

 

文件路径转成uri     uri=Uri.fromfile(new File("文件路径"));

uri转文件路径  需要通过contentresolver 然后去查一下.db的数库,最后得到真实文件路径。这个过程。不是本文重点就不讲了。

 

如需要深入均可直接查看Android 源码。此文不再赘述。

关于  photo.compress() , 我想说的是请君先测试一下, 压缩前后的文件大小和清晰度,慎用,别拷来就用,而且那么多机型。

我实测是压缩后文件变大了,还不清晰。当然参数还可以再调整,但总体感觉很不理想。

demo下载地址为:

http://download.csdn.net/detail/scugzbc/9500836

环境是Android Studio 2.0 .不是eclipse.

 

上传有感:

很久没有上传任何资料,但借这次机会,吐槽一下:

1,cnblogs的编辑器真的很难用,特别是做物图缩放的时候。

2,网上查资料都不到点了,已经弄清楚的人不愿意回头分享,分享的时候也是故意埋一些坑以示清高,明明很简单的事情,硬是弄得让你很久才明白。 所以在国内查资料不如看安卓源码。

 

posted @ 2016-04-24 12:23  贝贝(zbc)  阅读(803)  评论(0编辑  收藏  举报