国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

從相機(jī)(相冊(cè))獲取圖片并剪裁的最佳實(shí)踐

2019-11-15 01:03:30
字體:
供稿:網(wǎng)友
從相機(jī)(相冊(cè))獲取圖片并剪裁的最佳實(shí)踐

在開發(fā)一些APP的過程中,我們可能涉及到頭像的處理,比如從手機(jī)或者相冊(cè)獲取頭像,剪裁成自己需要的頭像,設(shè)置或上傳頭像等。網(wǎng)上一些相關(guān)的資料也是多不勝數(shù),但在實(shí)際應(yīng)用中往往會(huì)存在各種問題,沒有一個(gè)完美的解決方案。由于近期項(xiàng)目的需求,就研究了一下,目前看來還沒有什么問題。

這里我們只討論獲取、剪裁與設(shè)置,上傳流程根據(jù)自己的業(yè)務(wù)需求添加。先上一張流程圖:

這圖是用Google Drive的繪圖工具繪制的,不得不贊嘆Google可以把在線編輯工具做得如此強(qiáng)大。好吧,我就是Google的腦殘粉!回到主題,這是我設(shè)計(jì)的思路,接下來進(jìn)行詳細(xì)分析:

1、獲得圖片的途徑無非就兩種,第一是相機(jī)拍攝,第二是從本地相冊(cè)獲取。

2、我在SD卡上創(chuàng)建了一個(gè)文件夾,里面有兩個(gè)Uri,一個(gè)是用于保存拍照時(shí)獲得的原始圖片,一個(gè)是保存剪裁后的圖片。之前我考慮過用同一個(gè)Uri來保存圖片,但是在實(shí)踐中遇到一個(gè)問題,當(dāng)拍照后不進(jìn)行剪裁,那么下次從SD卡拿到就是拍照保存的大圖,不僅丟失了之前剪裁的圖片,還會(huì)因?yàn)榧虞d大圖導(dǎo)致內(nèi)存崩潰。基于此考慮,我選擇了兩個(gè)Uri來分別保存圖片。

3、相機(jī)拍攝時(shí),我們使用Intent調(diào)用系統(tǒng)相機(jī),并將設(shè)置輸出設(shè)置到SDCard/xx/photo_file.jpg,以下是代碼片段:

//調(diào)用系統(tǒng)相機(jī)Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//將拍照結(jié)果保存至photo_file的Uri中,不保留在相冊(cè)中intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imagePhotoUri);startActivityForResult(intentCamera, PHOTO_REQUEST_CAREMA);

在回調(diào)時(shí),我們需要對(duì)photo_file.jpg調(diào)用系統(tǒng)工具進(jìn)行剪裁,并設(shè)置輸出設(shè)置到SDCard/xx/crop_file.jpg,以下是代碼片段:

case PHOTO_REQUEST_CAREMA:  if (resultCode == RESULT_OK) {    //從相機(jī)拍攝保存的Uri中取出圖片,調(diào)用系統(tǒng)剪裁工具    if (imagePhotoUri != null) {      CropUtils.cropImageUri(this, imagePhotoUri, imageUri, ibUserIcon.getWidth(), ibUserIcon.getHeight(), PHOTO_REQUEST_CUT);    } else {      ToastUtils.show(this, "沒有得到拍照?qǐng)D片");    }  } else if (resultCode == RESULT_CANCELED) {    ToastUtils.show(this, "取消拍照");  } else {    ToastUtils.show(this, "拍照失敗");  }  break;
//調(diào)用系統(tǒng)的剪裁處理圖片并保存至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);  //將剪切的圖片保存到目標(biāo)Uri中  intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);  intent.putExtra("return-data", false);  intent.putExtra("outputFormat", Bitmap.Com

最后,我們需要在回調(diào)中取出crop_file.jpg,因?yàn)榧舨脮r(shí),對(duì)圖片已經(jīng)進(jìn)行了壓縮,所以也不用擔(dān)心內(nèi)存的問題,在這里我提供兩個(gè)方法,第一個(gè)是直接獲取原始圖片的Bitmap,第二個(gè)是獲取原始圖片并做成圓形,相信大多數(shù)的人對(duì)后者比較感興趣,哈哈!以下是代碼片段:

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);  // 圓形,所有只用一個(gè)  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、相冊(cè)獲取時(shí),這也是最難的地方。Android 4.4以下的版本,從相冊(cè)獲取的圖片Uri能夠完美調(diào)用系統(tǒng)剪裁工具,或者直接從選取相冊(cè)是帶入剪裁圖片的Intent,而且效果非常完美。但是在Android 4.4及其以上的版本,獲取到的Uri根本無法調(diào)用系統(tǒng)剪裁工具,會(huì)直接導(dǎo)致程序崩潰。我也是研究了很久,才發(fā)現(xiàn)兩者的Uri有很大的區(qū)別,Google官方文檔中讓開發(fā)者使用Intent.ACTION_GET_CONTENT代替以前的Action,并且就算你仍然使用以前的Action,都會(huì)返回一種新型的Uri,我個(gè)人猜測是因?yàn)镚oogle把所有的內(nèi)容獲取分享做成一個(gè)統(tǒng)一的Uri,如有不對(duì),請(qǐng)指正!想通這一點(diǎn)后,問題就變得簡單了,我把這種新型的Uri重新封裝一次,得到以為"file://..."標(biāo)準(zhǔn)的絕對(duì)路勁,傳入系統(tǒng)剪裁工具中,果然成功了,只是這個(gè)封裝過程及其艱難,查閱了很多資料,終于還是拿到了。下面說下具體步驟:

第一、調(diào)用系統(tǒng)相冊(cè),以下是代碼片段:

//調(diào)用系統(tǒng)相冊(cè)  Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);  photoPickerIntent.setType("image/*");  startActivityForResult(photoPickerIntent, PHOTO_REQUEST_GALLERY);

第二、在回調(diào)中,重新封裝Uri,并調(diào)用系統(tǒng)剪裁工具將輸出設(shè)置到crop_file.jpg,調(diào)用系統(tǒng)剪裁工具代碼在拍照獲取的步驟中已經(jīng)貼出,這里就不重復(fù)制造車輪了,重點(diǎn)貼重新封裝Uri的代碼,以下是代碼片段:

case PHOTO_REQUEST_GALLERY:  if (resultCode == RESULT_OK) {    //從相冊(cè)選取成功后,需要從Uri中拿出圖片的絕對(duì)路徑,再調(diào)用剪切    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, "沒有得到相冊(cè)圖片");    }  } else if (resultCode == RESULT_CANCELED) {    ToastUtils.show(this, "從相冊(cè)選取取消");  } else {    ToastUtils.show(this, "從相冊(cè)選取失敗");  }  break;
@SuppressLint("NewApi")public static String getPath(final Context context, final Uri uri) {final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;// DocumentProviderif (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());}

后續(xù)的系統(tǒng)剪裁工具調(diào)用跟拍照獲取步驟一致,請(qǐng)參見上的代碼。

5、所有步驟完成,在Nexus 5設(shè)備中的最新系統(tǒng)中測試通過,在小米、三星等一些設(shè)備中表現(xiàn)也很完美。如果在你的設(shè)備上存在缺陷,一定要跟帖給我反饋,謝謝!

記錄于此,希望能幫助到一些正遇到這種問題的朋友!


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 莱州市| 启东市| 惠州市| 神农架林区| 额济纳旗| 开封市| 深州市| 赤城县| 高邑县| 荥阳市| 马鞍山市| 台安县| 桐乡市| 福建省| 珠海市| 霍州市| 那曲县| 黄骅市| 铜梁县| 楚雄市| 库尔勒市| 土默特左旗| 聂拉木县| 荣昌县| 荣成市| 阜宁县| 汨罗市| 云安县| 荆门市| 长阳| 靖江市| 晋宁县| 永兴县| 扬州市| 噶尔县| 望江县| 洪洞县| 中方县| 阿瓦提县| 曲沃县| 监利县|