Android实现读取相机(相册)图片并进行剪裁

我们先说一下思路,在android系统中就自带了图片剪切的应用,所以,我们只需要将我们获取到的相片传给图片剪切应用,再将剪切好的相片返回到我们自己的界面显示就ok了

在开发一些APP的过程中,我们可能涉及到头像的处理,比如从手机或者相册获取头像,剪裁成自己需要的头像,设置或上传头像等。网上一些相关的资料也是多不胜数,但在实际应用中往往会存在各种问题,没有一个完美的解决方案。由于近期项目的需求,就研究了一下,目前看来还没有什么问题。

这里我们只讨论获取、剪裁与设置,上传流程根据自己的业务需求添加。先上一张流程图:

这图是用Google Drive的绘图工具绘制的,不得不赞叹Google可以把在线编辑工具做得如此强大。好吧,我就是Google的脑残粉!回到主题,这是我设计的思路,接下来进行详细分析:

1、获得图片的途径无非就两种,第一是相机拍摄,第二是从本地相册获取。

2、我在SD卡上创建了一个文件夹,里面有两个Uri,一个是用于保存拍照时获得的原始图片,一个是保存剪裁后的图片。之前我考虑过用同一个Uri来保存图片,但是在实践中遇到一个问题,当拍照后不进行剪裁,那么下次从SD卡拿到就是拍照保存的大图,不仅丢失了之前剪裁的图片,还会因为加载大图导致内存崩溃。基于此考虑,我选择了两个Uri来分别保存图片。

3、相机拍摄时,我们使用Intent调用系统相机,并将设置输出设置到SDCard\xx\photo_file.jpg,以下是代码片段:

//调用系统相机 Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //将拍照结果保存至photo_file的Uri中,不保留在相册中 intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imagePhotoUri); startActivityForResult(intentCamera, PHOTO_REQUEST_CAREMA);

在回调时,我们需要对photo_file.jpg调用系统工具进行剪裁,并设置输出设置到SDCard\xx\crop_file.jpg,以下是代码片段:

case PHOTO_REQUEST_CAREMA:   if (resultCode == RESULT_OK) {     //从相机拍摄保存的Uri中取出图片,调用系统剪裁工具     if (imagePhotoUri != null) {       CropUtils.cropImageUri(this, imagePhotoUri, imageUri, ibUserIcon.getWidth(), ibUserIcon.getHeight(), PHOTO_REQUEST_CUT);     } else {       ToastUtils.show(this, "没有得到拍照图片");     }   } else if (resultCode == RESULT_CANCELED) {     ToastUtils.show(this, "取消拍照");   } else {     ToastUtils.show(this, "拍照失败");   }   break; //调用系统的剪裁处理图片并保存至imageUri中 public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int width, int height, int requestCode) {   Intent intent = new Intent("com.android.camera.action.CROP");   intent.setDataAndType(orgUri, "image/*");   intent.putExtra("crop", "true");   intent.putExtra("aspectX", 1);   intent.putExtra("aspectY", 1);   intent.putExtra("outputX", width);   intent.putExtra("outputY", height);   intent.putExtra("scale", true);   //将剪切的图片保存到目标Uri中   intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);   intent.putExtra("return-data", false);   intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());   intent.putExtra("noFaceDetection", true);   activity.startActivityForResult(intent, requestCode); }

最后,我们需要在回调中取出crop_file.jpg,因为剪裁时,对图片已经进行了压缩,所以也不用担心内存的问题,在这里我提供两个方法,第一个是直接获取原始图片的Bitmap,第二个是获取原始图片并做成圆形,相信大多数的人对后者比较感兴趣,哈哈!以下是代码片段:

case PHOTO_REQUEST_CUT:   if (resultCode == RESULT_OK) {   Bitmap bitmap = decodeUriiAsBimap(this,imageCropUri)   } else if (resultCode == RESULT_CANCELED) {     ToastUtils.show(this, "取消剪切图片");   } else {     ToastUtils.show(this, "剪切失败");   }   break; //从Uri中获取Bitmap格式的图片 private static Bitmap decodeUriAsBitmap(Context context, Uri uri) {   Bitmap bitmap;   try {     bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri));   } catch (FileNotFoundException e) {     e.printStackTrace();     return null;   }   return bitmap; } //获取圆形图片 public static Bitmap getRoundedCornerBitmap(Bitmap bitmap) {   if (bitmap == null) {   return null;   }   Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);   Canvas canvas = new Canvas(output);   final Paint paint = new Paint();   /* 去锯齿 */   paint.setAntiAlias(true);   paint.setFilterBitmap(true);   paint.setDither(true);   // 保证是方形,并且从中心画   int width = bitmap.getWidth();   int height = bitmap.getHeight();   int w;   int deltaX = 0;   int deltaY = 0;   if (width <= height) {     w = width;     deltaY = height - w;   } else {     w = height;     deltaX = width - w;   }   final Rect rect = new Rect(deltaX, deltaY, w, w);   final RectF rectF = new RectF(rect);   paint.setAntiAlias(true);   canvas.drawARGB(0, 0, 0, 0);   // 圆形,所有只用一个   int radius = (int) (Math.sqrt(w * w * 2.0d) / 2);   canvas.drawRoundRect(rectF, radius, radius, paint);   paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));   canvas.drawBitmap(bitmap, rect, rect, paint);   return output; }

4、相册获取时,这也是最难的地方。Android 4.4以下的版本,从相册获取的图片Uri能够完美调用系统剪裁工具,或者直接从选取相册是带入剪裁图片的Intent,而且效果非常完美。但是在Android 4.4及其以上的版本,获取到的Uri根本无法调用系统剪裁工具,会直接导致程序崩溃。我也是研究了很久,才发现两者的Uri有很大的区别,Google官方文档中让开发者使用Intent.ACTION_GET_CONTENT代替以前的Action,并且就算你仍然使用以前的Action,都会返回一种新型的Uri,我个人猜测是因为Google把所有的内容获取分享做成一个统一的Uri,如有不对,请指正!想通这一点后,问题就变得简单了,我把这种新型的Uri重新封装一次,得到以为"file:\\..."标准的绝对路劲,传入系统剪裁工具中,果然成功了,只是这个封装过程及其艰难,查阅了很多资料,终于还是拿到了。下面说下具体步骤:

第一、调用系统相册,以下是代码片段:

//调用系统相册   Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);   photoPickerIntent.setType("image/*");   startActivityForResult(photoPickerIntent, PHOTO_REQUEST_GALLERY);

第二、在回调中,重新封装Uri,并调用系统剪裁工具将输出设置到crop_file.jpg,调用系统剪裁工具代码在拍照获取的步骤中已经贴出,这里就不重复制造车轮了,重点贴重新封装Uri的代码,以下是代码片段:

case PHOTO_REQUEST_GALLERY:   if (resultCode == RESULT_OK) {     //从相册选取成功后,需要从Uri中拿出图片的绝对路径,再调用剪切     Uri newUri = Uri.parse("file:///" + CropUtils.getPath(this, data.getData()));     if (newUri != null) {       CropUtils.cropImageUri(this, newUri, imageUri, ibUserIcon.getWidth(),       ibUserIcon.getHeight(), PHOTO_REQUEST_CUT);     } else {       ToastUtils.show(this, "没有得到相册图片");     }   } else if (resultCode == RESULT_CANCELED) {     ToastUtils.show(this, "从相册选取取消");   } else {     ToastUtils.show(this, "从相册选取失败");   }   break; @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {   // ExternalStorageProvider   if (isExternalStorageDocument(uri)) {     final String docId = DocumentsContract.getDocumentId(uri);     final String[] split = docId.split(":");     final String type = split[0];     if ("primary".equalsIgnoreCase(type)) {       return Environment.getExternalStorageDirectory() + "/"+ split[1];     }   }   // DownloadsProvider   else if (isDownloadsDocument(uri)) {     final String id = DocumentsContract.getDocumentId(uri);     final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(id));     return getDataColumn(context, contentUri, null, null);   }   // MediaProvider   else if (isMediaDocument(uri)) {     final String docId = DocumentsContract.getDocumentId(uri);     final String[] split = docId.split(":");     final String type = split[0];     Uri contentUri = null;     if ("image".equals(type)) {       contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;     } else if ("video".equals(type)) {       contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;     } else if ("audio".equals(type)) {       contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;     }     final String selection = "_id=?";     final String[] selectionArgs = new String[]{split[1]};     return getDataColumn(context, contentUri, selection,selectionArgs);     }   }   // MediaStore (and general)   else if ("content".equalsIgnoreCase(uri.getScheme())) {     return getDataColumn(context, uri, null, null);   }   // File   else if ("file".equalsIgnoreCase(uri.getScheme())) {     return uri.getPath();   }   return null; } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. */ private static String getDataColumn(Context context, Uri uri,String selection, String[] selectionArgs) {   Cursor cursor = null;   final String column = "_data";   final String[] projection = {column};   try {     cursor = context.getContentResolver().query(uri, projection,selection, selectionArgs, null);     if (cursor != null && cursor.moveToFirst()) {       final int column_index = cursor.getColumnIndexOrThrow(column);       return cursor.getString(column_index);     }   } finally {     if (cursor != null)       cursor.close();   }   return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ private static boolean isExternalStorageDocument(Uri uri) {   return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ private static boolean isDownloadsDocument(Uri uri) {   return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ private static boolean isMediaDocument(Uri uri) {   return "com.android.providers.media.documents".equals(uri.getAuthority()); }

后续的系统剪裁工具调用跟拍照获取步骤一致,请参见上的代码。

5、所有步骤完成,在Nexus 5设备中的最新系统中测试通过,在小米、三星等一些设备中表现也很完美。如果在你的设备上存在缺陷,一定要跟帖给我反馈,谢谢!

文章结尾附上一个网友的完整示例,给了我很多的参考

package com.only.android.app; import java.io.File; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.provider.MediaStore; import android.view.View; import android.widget.Button; import android.widget.ImageView; import com.only.android.R; public class CopyOfImageScaleActivity extends Activity implements View.OnClickListener { /** Called when the activity is first created. */ private Button selectImageBtn; private ImageView imageView; private File sdcardTempFile; private AlertDialog dialog; private int crop = 180; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.imagescale); selectImageBtn = (Button) findViewById(R.id.selectImageBtn); imageView = (ImageView) findViewById(R.id.imageView); selectImageBtn.setOnClickListener(this); sdcardTempFile = new File("/mnt/sdcard/", "tmp_pic_" + SystemClock.currentThreadTimeMillis() + ".jpg"); } @Override public void onClick(View v) { if (v == selectImageBtn) { if (dialog == null) { dialog = new AlertDialog.Builder(this).setItems(new String[] { "相机", "相册" }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra("output", Uri.fromFile(sdcardTempFile)); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1);// 裁剪框比例 intent.putExtra("aspectY", 1); intent.putExtra("outputX", crop);// 输出图片大小 intent.putExtra("outputY", crop); startActivityForResult(intent, 101); } else { Intent intent = new Intent("android.intent.action.PICK"); intent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"); intent.putExtra("output", Uri.fromFile(sdcardTempFile)); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1);// 裁剪框比例 intent.putExtra("aspectY", 1); intent.putExtra("outputX", crop);// 输出图片大小 intent.putExtra("outputY", crop); startActivityForResult(intent, 100); } } }).create(); } if (!dialog.isShowing()) { dialog.show(); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (resultCode == RESULT_OK) { Bitmap bmp = BitmapFactory.decodeFile(sdcardTempFile.getAbsolutePath()); imageView.setImageBitmap(bmp); } } }

最后再啰嗦一句,功能虽然已经实现了,但是实际代码还是可以进一步优化的,感兴趣的童鞋们可以改进下。

时间: 2024-09-21 15:52:27

Android实现读取相机(相册)图片并进行剪裁的相关文章

Android实现读取相机(相册)图片并进行剪裁_Android

我们先说一下思路,在android系统中就自带了图片剪切的应用,所以,我们只需要将我们获取到的相片传给图片剪切应用,再将剪切好的相片返回到我们自己的界面显示就ok了 在开发一些APP的过程中,我们可能涉及到头像的处理,比如从手机或者相册获取头像,剪裁成自己需要的头像,设置或上传头像等.网上一些相关的资料也是多不胜数,但在实际应用中往往会存在各种问题,没有一个完美的解决方案.由于近期项目的需求,就研究了一下,目前看来还没有什么问题. 这里我们只讨论获取.剪裁与设置,上传流程根据自己的业务需求添加.

Android拍照和获取相册图片_Android

之前遇到各种拍照啊,获取相册图片之类,都是直接去度娘,要么之前的代码复制下,没好好总结过.  再也不要问度娘了,再也不用一堆博客里找啊找了...  ----------------------------------------------我是正文的分割线-----------------------------------------------------------  一个一个来,先说调用手机相机拍照(最简单版): cameraButton.setOnClickListener(new V

Android拍照和获取相册图片

之前遇到各种拍照啊,获取相册图片之类,都是直接去度娘,要么之前的代码复制下,没好好总结过. 再也不要问度娘了,再也不用一堆博客里找啊找了... ----------------------------------------------我是正文的分割线----------------------------------------------------------- 一个一个来,先说调用手机相机拍照(最简单版): cameraButton.setOnClickListener(new View

Android优化查询加载大数量的本地相册图片_Android

一.概述 讲解优化查询相册图片之前,我们先来看下PM提出的需求,PM的需求很简单,就是要做一个类似微信的本地相册图片查询控件,主要包含两个两部分: 进入图片选择页面就要显示出手机中所有的照片,包括系统相册图片和其他目录下的所有图片,并按照时间倒叙排列 切换相册功能,切换相册页面列出手机中所有的图片目录列表,并且显示出每个目录下所有的图片个数以及封面图片 这两个需求看似简单,实则隐藏着一系列的性能优化问题.在做优化之前,我们调研了一些其他比较出名的app在加载大数量图片的性能表现(gif录制的不够

android中打开相机、打开相册进行图片的获取示例

这里介绍在Android中实现相机调取.拍照片.获取照片.存储新路径等已经打开相册.选择照片等功能 首先看一下界面,很简单 配置读取内存卡和调用照相头的功能 <!-- 使用网络权限 --> <uses-permission android:name="android.permission.INTERNET"/> <!-- 写sd卡的权限 --> <uses-permission android:name="android.permis

Android开发从相机或相册获取图片裁剪_Android

废话不多说了,直接给大家贴代码了. package com.only.android.app; import java.io.File; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.gr

Android开发从相机或相册获取图片裁剪

废话不多说了,直接给大家贴代码了. package com.only.android.app; import java.io.File; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.gr

Android调用系统相机拍照保存以及调用系统相册的方法

系统已经有的东西,如果我们没有新的需求的话,直接调用是最直接的.下面讲讲调用系统相机拍照并保 存图片和如何调用系统相册的方法. 首先看看调用系统相机的核心方法: Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(camera, 100); 相机返回的数据通过下面的回调方法取得,并处理 @Override protected void onActivityResult(int re

android调用系统相机拍照返回图片模糊

问题描述 android调用系统相机拍照返回图片模糊 上传代码 调用系统相机 Intent it = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(it, 1); 然后获取图片 Bundle extras = data.getExtras(); b = (Bitmap) extras.get("data"); String name = new SimpleDateFormat("yyy