最近在做一个从图库选择图片或拍照,然后裁剪的功能.本来是没问题的,一直在用
- Intent intent=new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
的方式来做,是调用系统图库来做,但是发现如果有图片是同步到google相册的话,图库里面能看到一个auto backup的目录,点进去选图片的话是无法获取到图片的路径的.因为那些图片根本就不存在于手机上.然后看到无论是百度贴吧,Instagram,或者还有些会选取图片做修改的app,都是用一个很漂亮的图片选择器(4.4以上,4.3的还是用系统旧的图库).
而这个图片选择器可以屏蔽掉那个auto backup的目录.所以就开始打算用这个图片选择器来选图片了.
这个方法就是
- Intent intent=new Intent(Intent.ACTION_GET_CONTENT);//ACTION_OPEN_DOCUMENT
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(“image/jpeg”);
- if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.KITKAT){
- startActivityForResult(intent, SELECT_PIC_KITKAT);
- }else{
- startActivityForResult(intent, SELECT_PIC);
- }
为什么要分开不同版本呢?其实在4.3或以下可以直接用ACTION_GET_CONTENT的,在4.4或以上,官方建议用ACTION_OPEN_DOCUMENT,但其实都不算太大区别,区别是他们返回的Uri,那个才叫大区别.这就是困扰了我一整天的问题所在了.
4.3或以下,选了图片之后,根据Uri来做处理,很多帖子都有了,我就不详细说了.主要是4.4,如果使用上面pick的原生方法来选图,返回的uri还是正常的,但如果用ACTION_GET_CONTENT的方法,返回的uri跟4.3是完全不一样的,4.3返回的是带文件路径的,而4.4返回的却是content://com.android.providers.media.documents/document/image:3951这样的,没有路径,只有图片编号的uri.这就导致接下来无法根据图片路径来裁剪的步骤了.
还好找了很多方法,包括加权限啊什么的,中间还试过用一些方法,自己的app没崩溃,倒是让系统图库崩溃了,引发了java.lang.SecurityException.
- Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{437b5d88 9494:com.google.android.gallery3d/u0a20} (pid=9494, uid=10020) requires android.permission.MANAGE_DOCUMENTS or android.permission.MANAGE_DOCUMENTS
看来4.4的系统还是有些bug.重点来了,4.4得到的uri,需要以下方法来获取文件的路径
- 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];
- }
- // TODO handle non-primary volumes
- }
- // 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 the remote address
- if (isGooglePhotosUri(uri))
- return uri.getLastPathSegment();
- 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.
- */
- public 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 index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
- return null;
- }
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public 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.
- */
- public 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.
- */
- public static boolean isMediaDocument(Uri uri) {
- return “com.android.providers.media.documents”.equals(uri.getAuthority());
- }
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(Uri uri) {
- return “com.google.android.apps.photos.content”.equals(uri.getAuthority());
- }
这样,就可以在4.4上用漂亮的图片选择器,选到我们想要的文件,又不会出问题了.
昨天发现了个bug,如果在4.4上面不用”图片”来选,用”图库”来选,就会无法读取到图片路径,所以只需要加个判断,如果是用旧方式来选,就用旧方式来读,就是如果
DocumentsContract.isDocumentUri(context, uri) 返回false的话,就用旧的方式
- public static String selectImage(Context context,Intent data){
- Uri selectedImage = data.getData();
- // Log.e(TAG, selectedImage.toString());
- if(selectedImage!=null){
- String uriStr=selectedImage.toString();
- String path=uriStr.substring(10,uriStr.length());
- if(path.startsWith(“com.sec.android.gallery3d”)){
- Log.e(TAG, “It’s auto backup pic path:”+selectedImage.toString());
- return null;
- }
- }
- String[] filePathColumn = { MediaStore.Images.Media.DATA };
- Cursor cursor = context.getContentResolver().query(selectedImage,filePathColumn, null, null, null);
- cursor.moveToFirst();
- int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
- String picturePath = cursor.getString(columnIndex);
- cursor.close();
- return picturePath;
- }
这样就OK的了
转自:http://blog.csdn.net/tempersitu/article/details/20557383
android拍照图片选取与图片剪裁
转载:http://blog.csdn.net/allen315410/article/details/39994913
最近从以前的项目中扒下来一个常用的模块,在这里有必要记录一下的,就是android上获取图片以及裁剪图片,怎么样?这个功能是不是很常用啊,你随便打开一个App,只要它有注册功能都会有设置人物头像的功能,尤其在内容型的app中更为常见,那么这些功能是怎么实现的呢?今天,在这里就记录一下好了,防止以后的项目中也会用到,就直接拿来用好了。
1.通过拍照或者图册获取图片(不需要剪裁)
这种获取图片的方式就比较次了,因为不设置图片的剪裁功能,有可能因为图片过大,导致OOM,但是这种方式也是有必要讲一下的,其获取图片的方式有两种,一是调用系统相机实时拍摄一张图片,二十打开设备上已有的图库,在图库中选择一张照片。这两种方式实现方法都是一个道理,无非就是通过Intent调用系统的东西。下面是源码,首先是图片选择方式的Activity,这个Activity被设置成了Dialog模式,需要进行设置一下。
布局文件/res/layout/activity_select_photo.xml:
- <?xml version=“1.0” encoding=“utf-8”?>
- <RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
- android:layout_width=“fill_parent”
- android:layout_height=“wrap_content”
- android:gravity=“center_horizontal” >
- <LinearLayout
- android:id=“@+id/dialog_layout”
- android:layout_width=“fill_parent”
- android:layout_height=“wrap_content”
- android:layout_alignParentBottom=“true”
- android:layout_marginLeft=“10dip”
- android:layout_marginRight=“10dip”
- android:gravity=“center_horizontal”
- android:orientation=“vertical” >
- <LinearLayout
- android:layout_width=“fill_parent”
- android:layout_height=“wrap_content”
- android:background=“@drawable/select_photo_up_bg”
- android:orientation=“vertical”
- android:paddingBottom=“5dp”
- android:paddingTop=“5dp” >
- <Button
- android:id=“@+id/btn_take_photo”
- android:layout_width=“fill_parent”
- android:layout_height=“35dp”
- android:background=“@drawable/select_photo_bg”
- android:text=“拍照选取”
- android:textStyle=“bold” />
- <View
- android:layout_width=“fill_parent”
- android:layout_height=“0.5px”
- android:background=“#828282” />
- <Button
- android:id=“@+id/btn_pick_photo”
- android:layout_width=“fill_parent”
- android:layout_height=“35dp”
- android:layout_marginTop=“0dip”
- android:background=“@drawable/select_photo_bg”
- android:text=“相册选取”
- android:textStyle=“bold” />
- </LinearLayout>
- <Button
- android:id=“@+id/btn_cancel”
- android:layout_width=“fill_parent”
- android:layout_height=“35dp”
- android:layout_marginTop=“20dip”
- android:background=“@drawable/select_photo_bg”
- android:paddingBottom=“5dp”
- android:paddingTop=“5dp”
- android:text=“取消”
- android:textColor=“#ffff0000”
- android:textStyle=“bold” />
- </LinearLayout>
- </RelativeLayout>
接着是获取图片Activity里的代码SelectPhotoActivity:
- public class SelectPhotoActivity extends Activity implements OnClickListener {
- /** 使用照相机拍照获取图片 */
- public static final int SELECT_PIC_BY_TACK_PHOTO = 1;
- /** 使用相册中的图片 */
- public static final int SELECT_PIC_BY_PICK_PHOTO = 2;
- /** 开启相机 */
- private Button btn_take_photo;
- /** 开启图册 */
- private Button btn_pick_photo;
- /** 取消 */
- private Button btn_cancel;
- /** 获取到的图片路径 */
- private String picPath;
- private Intent lastIntent;
- private Uri photoUri;
- /** 从Intent获取图片路径的KEY */
- public static final String KEY_PHOTO_PATH = “photo_path”;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_select_photo);
- btn_take_photo = (Button) findViewById(R.id.btn_take_photo);
- btn_pick_photo = (Button) findViewById(R.id.btn_pick_photo);
- btn_cancel = (Button) findViewById(R.id.btn_cancel);
- lastIntent = getIntent();
- btn_take_photo.setOnClickListener(this);
- btn_pick_photo.setOnClickListener(this);
- btn_cancel.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btn_take_photo : // 开启相机
- takePhoto();
- break;
- case R.id.btn_pick_photo : // 开启图册
- pickPhoto();
- break;
- case R.id.btn_cancel : // 取消操作
- this.finish();
- break;
- default :
- break;
- }
- }
- /**
- * 拍照获取图片
- */
- private void takePhoto() {
- // 执行拍照前,应该先判断SD卡是否存在
- String SDState = Environment.getExternalStorageState();
- if (SDState.equals(Environment.MEDIA_MOUNTED)) {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// “android.media.action.IMAGE_CAPTURE”
- /***
- * 需要说明一下,以下操作使用照相机拍照,拍照后的图片会存放在相册中的 这里使用的这种方式有一个好处就是获取的图片是拍照后的原图
- * 如果不实用ContentValues存放照片路径的话,拍照后获取的图片为缩略图不清晰
- */
- ContentValues values = new ContentValues();
- photoUri = this.getContentResolver().insert(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
- intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, photoUri);
- startActivityForResult(intent, SELECT_PIC_BY_TACK_PHOTO);
- } else {
- Toast.makeText(getApplicationContext(), “内存卡不存在”,
- Toast.LENGTH_SHORT).show();
- }
- }
- /***
- * 从相册中取图片
- */
- private void pickPhoto() {
- Intent intent = new Intent();
- intent.setType(“image/*”);
- intent.setAction(Intent.ACTION_GET_CONTENT);
- startActivityForResult(intent, SELECT_PIC_BY_PICK_PHOTO);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- finish();
- return super.onTouchEvent(event);
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_OK) {
- doPhoto(requestCode, data);
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
- /**
- * 选择图片后,获取图片的路径
- *
- * @param requestCode
- * @param data
- */
- private void doPhoto(int requestCode, Intent data) {
- if (requestCode == SELECT_PIC_BY_PICK_PHOTO) {// 从相册取图片,有些手机有异常情况,请注意
- if (data == null) {
- Toast.makeText(getApplicationContext(), “选择图片文件出错”,
- Toast.LENGTH_SHORT).show();
- return;
- }
- photoUri = data.getData();
- if (photoUri == null) {
- Toast.makeText(getApplicationContext(), “选择图片文件出错”,
- Toast.LENGTH_SHORT).show();
- return;
- }
- }
- String[] pojo = {MediaStore.Images.Media.DATA};
- Cursor cursor = managedQuery(photoUri, pojo, null, null, null);
- if (cursor != null) {
- int columnIndex = cursor.getColumnIndexOrThrow(pojo[0]);
- cursor.moveToFirst();
- picPath = cursor.getString(columnIndex);
- cursor.close();
- }
- if (picPath != null
- && (picPath.endsWith(“.png”) || picPath.endsWith(“.PNG”)
- || picPath.endsWith(“.jpg”) || picPath.endsWith(“.JPG”))) {
- lastIntent.putExtra(KEY_PHOTO_PATH, picPath);
- setResult(Activity.RESULT_OK, lastIntent);
- finish();
- } else {
- Toast.makeText(getApplicationContext(), “选择图片文件不正确”,
- Toast.LENGTH_SHORT).show();
- }
- }
- }
因为这Activity是要设置成Dialog模式的,所以需要在清单文件中设置一下style,/res/values/styles.xml里添加如下:
- <!– 选取照片的Activity的样式风格,采取对话框的风格 –>
- <style name=“AnimBottom” parent=“@android:style/Animation”>
- <item name=“android:windowEnterAnimation”>@anim/push_bottom_in</item>
- <item name=“android:windowExitAnimation”>@anim/push_bottom_out</item>
- </style>
- <style name=“DialogStyleBottom” parent=“android:Theme.Dialog”>
- <item name=“android:windowAnimationStyle”>@style/AnimBottom</item>
- <item name=“android:windowFrame”>@null</item>
- <!– 边框 –>
- <item name=“android:windowIsFloating”>false</item>
- <!– 是否浮现在activity之上 –>
- <item name=“android:windowIsTranslucent”>true</item>
- <!– 半透明 –>
- <item name=“android:windowNoTitle”>true</item>
- <!– 无标题 –>
- <item name=“android:windowBackground”>@android:color/transparent</item>
- <!– 背景透明 –>
- <item name=“android:backgroundDimEnabled”>true</item>
- <!– 模糊 –>
- </style>
在Activity的节点下,设置这个style:
- <activity
- android:name=“com.example.croppictrue.SelectPhotoActivity”
- android:screenOrientation=“portrait”
- android:theme=“@style/DialogStyleBottom” >
- </activity>
添加权限:
- <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE” />
运行效果如下:
2.通过拍照或者图册获取图片(需要剪裁)
上面第一种方式获取图片是没有经过剪裁的,但是大多项目需求是需要剪裁图片后再使用,例如修改用户头像等等功能。那么,下面,就奉上剪裁图片的代码吧:
- public class CropPictureActivity extends Activity {
- /** ImageView对象 */
- private ImageView iv_photo;
- private String[] items = new String[]{“选择本地图片”, “拍照”};
- /** 头像名称 */
- private static final String IMAGE_FILE_NAME = “image.jpg”;
- /** 请求码 */
- private static final int IMAGE_REQUEST_CODE = 0;
- private static final int CAMERA_REQUEST_CODE = 1;
- private static final int RESULT_REQUEST_CODE = 2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_crop);
- iv_photo = (ImageView) findViewById(R.id.iv_photo);
- iv_photo.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showDialog();
- }
- });
- }
- /**
- * 显示选择对话框
- */
- private void showDialog() {
- new AlertDialog.Builder(this)
- .setTitle(“设置头像”)
- .setItems(items, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- switch (which) {
- case 0 :
- Intent intentFromGallery = new Intent();
- intentFromGallery.setType(“image/*”); // 设置文件类型
- intentFromGallery
- .setAction(Intent.ACTION_GET_CONTENT);
- startActivityForResult(intentFromGallery,
- IMAGE_REQUEST_CODE);
- break;
- case 1 :
- Intent intentFromCapture = new Intent(
- MediaStore.ACTION_IMAGE_CAPTURE);
- // 判断存储卡是否可以用,可用进行存储
- String state = Environment
- .getExternalStorageState();
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- File path = Environment
- .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
- File file = new File(path, IMAGE_FILE_NAME);
- intentFromCapture.putExtra(
- MediaStore.EXTRA_OUTPUT,
- Uri.fromFile(file));
- }
- startActivityForResult(intentFromCapture,
- CAMERA_REQUEST_CODE);
- break;
- }
- }
- })
- .setNegativeButton(“取消”, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- }).show();
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- // 结果码不等于取消时候
- if (resultCode != RESULT_CANCELED) {
- switch (requestCode) {
- case IMAGE_REQUEST_CODE :
- startPhotoZoom(data.getData());
- break;
- case CAMERA_REQUEST_CODE :
- // 判断存储卡是否可以用,可用进行存储
- String state = Environment.getExternalStorageState();
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- File path = Environment
- .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
- File tempFile = new File(path, IMAGE_FILE_NAME);
- startPhotoZoom(Uri.fromFile(tempFile));
- } else {
- Toast.makeText(getApplicationContext(),
- “未找到存储卡,无法存储照片!”, Toast.LENGTH_SHORT).show();
- }
- break;
- case RESULT_REQUEST_CODE : // 图片缩放完成后
- if (data != null) {
- getImageToView(data);
- }
- break;
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
- /**
- * 裁剪图片方法实现
- *
- * @param uri
- */
- public void startPhotoZoom(Uri uri) {
- Intent intent = new Intent(“com.android.camera.action.CROP”);
- intent.setDataAndType(uri, “image/*”);
- // 设置裁剪
- intent.putExtra(“crop”, “true”);
- // aspectX aspectY 是宽高的比例
- intent.putExtra(“aspectX”, 1);
- intent.putExtra(“aspectY”, 1);
- // outputX outputY 是裁剪图片宽高
- intent.putExtra(“outputX”, 340);
- intent.putExtra(“outputY”, 340);
- intent.putExtra(“return-data”, true);
- startActivityForResult(intent, RESULT_REQUEST_CODE);
- }
- /**
- * 保存裁剪之后的图片数据
- *
- * @param picdata
- */
- private void getImageToView(Intent data) {
- Bundle extras = data.getExtras();
- if (extras != null) {
- Bitmap photo = extras.getParcelable(“data”);
- Drawable drawable = new BitmapDrawable(this.getResources(), photo);
- iv_photo.setImageDrawable(drawable);
- }
- }
- }
效果图:
在这个Activity里为了简便处理,我没有在选择图片时候start一个Dialog风格的Activity了,就直接一个普通的对话框提示用户选择,效果也许。其实实现的原理都比较简单,实现图片的剪裁就是发一个Intent请求,调用设备上所有具有剪裁图片功能的app去剪裁图片,我的设备上除了android系统自带的图库以外,还装有“快图浏览”这个app,这个app也自带一个图片剪裁的功能,所有当选择好图片后,会出现一个选择提示,用户可以根据提示选择到底使用哪个app提供的剪裁功能区剪裁图片。
以上代码均在模拟器上测试过,由于模拟器对相机支持的不好,所以就没有演示打开相机拍摄图片了,有兴趣的朋友可以先请下载这个Demo的源码,运行在手机上试试看效果如何,如若疏漏之后,欢迎大家批评指正!