在安卓手机上拍照,选取图片并且进行裁剪
因为在我们的项目《七星计价器》开发过程中,需要实现拍照,选取照片和手机截屏,中间对于不同的手机的兼容上浪费了不少时间,也深知同行们在做这块的时候一定也会重走探索的路线,特分享如下,以免大家同样浪费时间。
基础概念理解
不管是哪个功能,你都要先理解一个概念:
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,网上查资料都不到点了,已经弄清楚的人不愿意回头分享,分享的时候也是故意埋一些坑以示清高,明明很简单的事情,硬是弄得让你很久才明白。 所以在国内查资料不如看安卓源码。