前言

  我们在Android开发中经常会需要使用相机或者从相册中选取图片的情况,今天就把这里面相关的知识点总结下,方便以后开发的时候使用。

1、相机拍照并可自定义截图功能

  我们先来看如何使用Intent来打开照相机,相信这段代码大伙应该很熟悉了。代码如下:



1 //打开照相机,进行拍照
2 intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
3 //设置照片的临时保存路径
4 intent.putExtra(MediaStore.EXTRA_OUTPUT, mTempImageUri);
5 startActivityForResult(intent, TAKE_PHOTO_BY_CAMERA);



  这段代码中我们看到,对于第四行设置了拍照后图片的保存路径。这个我们可以自定义的。然后我们通过startActivityForResult来打开相机,并接受相关的数据。

  下面我们来看下相机拍摄后的处理逻辑。代码如下:



1 /**
 2      * 处理照相或者相册返回的图片数据
 3      * @param requestCode
 4      * @param resultCode
 5      * @param data
 6      */
 7     @Override
 8     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 9         if(resultCode==Activity.RESULT_OK){
10             switch (requestCode){
11                 //处理相机
12                 case TAKE_PHOTO_BY_CAMERA:
13                     if(mCrop) {
14                         //对指定路径下的图片进行裁剪
15                         cropCameraImage(mTempImageUri, DEFAULT_CROP_X, DEFAULT_CROP_Y, CROP_PHOTO);
16                     }else {
17                         mBitmap = getThumbnail(mTempImageUri, mImageWidth, mImageHeight);
18                         setResult(true);
19                     }
20                     break;
21                 //处理相册
22                 case SELECT_PHOTO_BY_ALBUM:
23                     Uri selectedPhotoUri=data.getData();
24                     //对图片进行裁剪
25                     if(mCrop) {
26                         cropAlbumImage(selectedPhotoUri, mTempImageUri, DEFAULT_CROP_X, DEFAULT_CROP_Y, CROP_PHOTO);
27                     }else {
28                         //否则就将图片压缩成指定大小返回
29                         mBitmap = getThumbnail(selectedPhotoUri, mImageWidth, mImageHeight);
30                         setResult(true);
31                     }
32                     break;
33                 //裁剪图片
34                 case CROP_PHOTO:
35                     if(data!=null) {
36                         /**
37                          * 如果想使用data.getParcelableExtra("data")获取bitmap
38                          * 只需要在裁剪时设置return-data为true即可
39                          */
40                         if(mReturnData){
41                             mBitmap = data.getParcelableExtra("data");
42                             if (mBitmap == null) {
43                                 setResult(false);
44                             }
45                         }else {
46                             Uri originalUri = data.getData();
47                             if (originalUri == null) {
48                                 originalUri = mTempImageUri;
49                             }
50                             //获取缩略图
51                             mBitmap = getThumbnail(originalUri, mImageWidth, mImageHeight);
52                         }
53                     }
54                     setResult(true);
55                     break;
56                 default:
57                     break;
58             }
59         }else {
60             setResult(false);
61         }
62         super.onActivityResult(requestCode, resultCode, data);
63     }



  我们来看代码的12-20行,我们看到这段代码会产生一个分支,如果crop为true,那么会调用cropCameraImage方法对拍摄的图片进行截图。否则的话就返回指定大小的图片(bitmap)给调用者。我们分开来看。

  1、拍照并截图。相关代码如下:



1 /**
 2      * 对照相机拍摄的图片进行裁剪
 3      * @param uri
 4      * @param outputX
 5      * @param outputY
 6      * @param requestCode
 7      */
 8     private void cropCameraImage(Uri uri, int outputX, int outputY, int requestCode){
 9         //相机拍摄的图片像素较高,不使用return-data属性使用Uri返回
10         mReturnData=false;
11         cropImage(uri,uri,outputX,outputY,mReturnData,requestCode);
12     }



  我们看到里面调用了cropImage方法(从相册取图也使用了这个方法)。由于现在相机像素都很高,生成的图片很大,往往有5M+的大小。不建议使用return-data属性,这样会占用很大的内存。我们这边使用Uri。下面会详细介绍。

  cropImage方法的介绍。



1 /**
 2      *
 3      * @param sourceUri
 4      * @param outputUri
 5      * @param outputX
 6      * @param outputY
 7      * @param returnData
 8      * @param requestCode
 9      */
10     private void cropImage(Uri sourceUri,Uri outputUri,int outputX,int outputY,boolean returnData,int requestCode) {
11         try {
12             /**
13              * 下面解释各个参数的详细信息
14              */
15             Intent intent = new Intent("com.android.camera.action.CROP");
16             intent.setDataAndType(sourceUri, "image/*");
17             //发送裁剪信号
18             intent.putExtra("crop", "true");
19             //X,Y方向上的比例(1:1就是正方形,2:1就是长方形)
20             intent.putExtra("aspectX", 1);
21             intent.putExtra("aspectY", 1);
22             //裁剪区的宽,高
23             intent.putExtra("outputX", outputX);
24             intent.putExtra("outputY", outputY);
25             //是否保留比例
26             intent.putExtra("scale", true);
27             if(returnData) {
28                 //是否将数据保留在Bitmap中返回(注意:小图片可以使用return-data:true的设置,大图片的话建议使用extra_output)
29                 intent.putExtra("return-data", returnData);
30             }else {
31                 //设置裁剪后的图片路径覆盖相机拍摄图片路径
32                 intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
33             }
34             //裁剪后图片的后缀名
35             intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
36             //没有人脸检测
37             intent.putExtra("noFaceDetection", true);
38             startActivityForResult(intent, requestCode);
39         } catch (Exception ex) {
40             setResult(false);
41         }
42     }



  在代码的27-32行我们看到通过returnData我们设置是否使用return-data属性。设置了return-data属性,Android系统会自动返回bitmap对象给我们,如:onActivityResult中的data.getParcelableExtra这种方法。这样对于小图片当然非常方便。但是对于大图片会造成内存消耗过高,应用程序产生异常。在最后我们看cropImage方法也是调用了startActivityForResult方法,只不过它的参数是CROP_PHOTO。

  2、直接生成指定大小的图片

  下面我们来介绍不使用截图,直接将生成指定大小图片并返回的方法,即代码中的getThumbnail方法。这一段代码是可以实现将图片缩小成指定长宽的尺寸。代码如下:



1 /**
 2      * @param uri
 3      * @param width
 4      * @param height
 5      * @return
 6      * @throws IOException
 7      */
 8     public Bitmap getThumbnail(Uri uri,int width,int height){
 9         Bitmap bitmap=null;
10         InputStream input=null;
11         try {
12             ContentResolver resolver = getContentResolver();
13             input = resolver.openInputStream(uri);
14             BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
15             /**
16              * 如果设置inJustDecodeBounds为true,仍可以获取到bitmap信息,但完全不用分配内存,因为没有获取像素,所以我们可以利用得到的Bitmap的大小,
17              * 重新压缩图片,然后在内存中生成一个更小的Bitmap,这样即便是一个4MB的JPG,我们也可以随心所欲地把他压缩到任意大小,从而节省了内存
18              */
19             onlyBoundsOptions.inJustDecodeBounds = true;
20             onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
21             BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
22             if(input!=null) {
23                 input.close();
24             }
25             if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
26                 return bitmap;
27             }
28             BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
29             /**
30              * 计算Bitmap的缩放比例
31              */
32             bitmapOptions.inSampleSize = calculateInSampleSize(onlyBoundsOptions, width, height);
33             bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
34             input = resolver.openInputStream(uri);
35             bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
36         }catch (Exception ex){
37             return bitmap;
38         }finally {
39             if(input!=null){
40                 try {
41                     input.close();
42                 }catch (IOException ioEx){}
43             }
44         }
45         return bitmap;
46     }



  这种方法的原理可以参考我的另一篇文章:。通过介绍我们知道了相机拍摄图片并且进行截图和指定大小图片获取的相关方法。相信聪明的你也一定知道从相册取图肯定也是类似的。的确,从上面onActivityResult方法中我们看到从相册取图也是大同小异的。最后我再来介绍下onActivityResult方法中的CROP_PHOTO的相关逻辑。代码如下:



1 /**
 2  * 如果想使用data.getParcelableExtra("data")获取bitmap
 3  * 只需要在裁剪时设置return-data为true即可
 4  */
 5 if(mReturnData){
 6     mBitmap = data.getParcelableExtra("data");
 7     if (mBitmap == null) {
 8         setResult(false);
 9     }
10 }else {
11     Uri originalUri = data.getData();
12     if (originalUri == null) {
13         originalUri = mTempImageUri;
14     }
15     //获取缩略图
16     mBitmap = getThumbnail(originalUri, mImageWidth, mImageHeight);
17 }



EXTRA_OUTPUT, outputUri);cropImage方法的29行。我们可以通过获取Uri,然后重新生成一张bitmap返回给调用者。

 参考文档


   附整个代码的逻辑