譯者按:在外企工作的半年多中花了不少時間在國外的網站上搜尋資料,其中有一些相當有含金量的文章,我會陸陸續續翻譯成中文,與大家共用之。初次翻譯,“信達雅”三境界恐怕只到信的層次,望大家見諒!
這篇文章相當經典而實用,想當初我做手機拍照的時候,大多都是在網上抄來抄去的內容,從來沒有人考慮過實際項目中的需求。實際上,拍照傳大圖片,如果 用普通方式會耗用極大的記憶體,Android一個App原則上的16M記憶體限制可以一下子被耗光。Android在拍照上有一個隱藏的設計,如果拍照圖片 過大,只返回一張縮圖。具體到不同手機,都是不一樣的。
-------------------------------------------------------------------------------------
譯文:
概述
我寫這篇文章是為了發表我對MediaStore裁剪圖片功能的一些簡要研究。基本上,如 果你要寫一個應用程式,使用已有的Media Gallery並允許使用者在你的應用裡選取TA的圖片的一部分(可選功能:Face Service)。 可以使用一個Intent做到這個,但是也存在著相應的問題,總的來說也缺少這方面的文檔告訴我們怎麼實現它。 另外,這篇文章基於2.1並且在Nexus One上做了測試。 Nexus One上的實現似乎被這群人寫在了這裡: Media Gellery for Nexus One 。
反饋
這篇文章需要使用基於我的研究所寫的程式。如果你對我推薦的實現方案有所改進,請讓我知道。我會相應的更新這篇文章。
Intent細節
首先,讓我們探討下Intent以及它的特點。在看了一些程式碼範例以後,我發現我可以很輕鬆的使用如下的Intent調用裁剪功能:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);intent.setType(“image/*”);intent.putExtra(“crop”, “true”);…
然而,這是在我缺少附加的文檔,不知道這些選項的具體含義等等情況之下的選擇。所以,我將我的yanj整理成一個表格 ,並寫了一個示範程式,力圖示範控制此功能的所有可供選項。
你可以在你的程式中使用使用My Code,並且擴充它。我會將之附加在這篇文章上。
Exta Options Table for image/* crop:
附加選項 |
資料類型 |
描述 |
crop |
String |
發送裁剪訊號 |
aspectX |
int |
X方向上的比例 |
aspectY |
int |
Y方向上的比例 |
outputX |
int |
裁剪區的寬 |
outputY |
int |
裁剪區的高 |
scale |
boolean |
是否保留比例 |
return-data |
boolean |
是否將資料保留在Bitmap中返回 |
data |
Parcelable |
相應的Bitmap資料 |
circleCrop |
String |
圓形裁剪地區? |
MediaStore.EXTRA_OUTPUT ("output") |
URI |
將URI指向相應的file:///...,詳見程式碼範例 |
現在,最令人困惑的是MediaStore.EXTRA_OUTPUT以及return-data選項。
你主要有兩種方式從這個Intent中取得返回的bitmap:擷取內部資料或者提供一個Uri以便程式可以將資料寫入。
方法1:如 果你將return-data設定為“true”,你將會獲得一個與內部資料關聯的Action,並且bitmap以此方式返回: (Bitmap)extras.getParcelable("data")。注意:如果你最終要擷取的圖片非常大,那麼此方法會給你帶來麻煩,所以你要 控制outputX和outputY保持在較小的尺寸。鑒於此原因,在My Code中沒有使用此方法 ((Bitmap)extras.getParcelable("data"))。
下面是CropImage.java的源碼片段:
// Return the cropped image directly or save it to the specified URI.Bundle myExtras = getIntent().getExtras();if (myExtras != null && (myExtras.getParcelable("data") != null || myExtras.getBoolean("return-data"))){ Bundle extras = new Bundle(); extras.putParcelable("data", croppedImage); setResult(RESULT_OK,(new Intent()).setAction("inline-data").putExtras(extras)); finish();}
方法2: 如果你將return-data設定為“false”,那麼在onActivityResult的Intent資料中你將不會接收到任何Bitmap,相反,你需要將MediaStore.EXTRA_OUTPUT關聯到一個Uri,此Uri是用來存放Bitmap的。
但是還有一些條件,首先你需要有一個短暫的與此Uri相關聯的檔案地址,當然這不是個大問題(除非是那些沒有sdcard的裝置)。
下面是CropImage.java關於操作Uri的源碼片段:
if (mSaveUri != null) { OutputStream outputStream = null; try { outputStream = mContentResolver.openOutputStream(mSaveUri); if (outputStream != null) { croppedImage.compress(mOutputFormat, 75, outputStream); } } catch (IOException ex) { // TODO: report error to caller Log.e(TAG, "Cannot open file: " + mSaveUri, ex); } finally { Util.closeSilently(outputStream); } Bundle extras = new Bundle(); setResult(RESULT_OK, new Intent(mSaveUri.toString()).putExtras(extras));}
程式碼範例:
我已經附上了一些程式碼範例,應該可以讓你測試多種配置。請讓我知道它對你是否有用。
代碼下載: MediaStoreTest
/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);thiz = this;setContentView(R.layout.main);mBtn = (Button) findViewById(R.id.btnLaunch);photo = (ImageView) findViewById(R.id.imgPhoto);mBtn.setOnClickListener(new OnClickListener() {public void onClick(View v) {try {// Launch picker to choose photo for selected contactIntent intent = new Intent(Intent.ACTION_GET_CONTENT, null);intent.setType("image/*");intent.putExtra("crop", "true");intent.putExtra("aspectX", aspectX);intent.putExtra("aspectY", aspectY);intent.putExtra("outputX", outputX);intent.putExtra("outputY", outputY);intent.putExtra("scale", scale);intent.putExtra("return-data", return_data);intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri());intent.putExtra("outputFormat",Bitmap.CompressFormat.JPEG.toString()); // lol, negative boolean noFaceDetection intent.putExtra("noFaceDetection", !faceDetection);if (circleCrop) {intent.putExtra("circleCrop", true);}startActivityForResult(intent, PHOTO_PICKED);} catch (ActivityNotFoundException e) {Toast.makeText(thiz, R.string.photoPickerNotFoundText,Toast.LENGTH_LONG).show();}}});}private Uri getTempUri() {return Uri.fromFile(getTempFile());}private File getTempFile() {if (isSDCARDMounted()) {File f = new File(Environment.getExternalStorageDirectory(),TEMP_PHOTO_FILE);try {f.createNewFile();} catch (IOException e) {// TODO Auto-generated catch blockToast.makeText(thiz, R.string.fileIOIssue, Toast.LENGTH_LONG).show();}return f;} else {return null;}}private boolean isSDCARDMounted() {String status = Environment.getExternalStorageState();if (status.equals(Environment.MEDIA_MOUNTED))return true;return false;}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case PHOTO_PICKED:if (resultCode == RESULT_OK) {if (data == null) {Log.w(TAG, "Null data, but RESULT_OK, from image picker!");Toast t = Toast.makeText(this, R.string.no_photo_picked,Toast.LENGTH_SHORT);t.show();return;}final Bundle extras = data.getExtras();if (extras != null) {File tempFile = getTempFile();// new logic to get the photo from a URIif (data.getAction() != null) {processPhotoUpdate(tempFile);}}}break;}}
附錄:My comments
Thank you so much! The tutorial is great! Actually the secret of cropping photos on Android is using Uri if the photo is in large size and using Bitmap if you want but make sure that the bitmap is not too big.(You can use it for cropping avatar or other requirements with a limited size of the photo. Different phones have different limits. Normally if you want to use a bitmap, the size shouldn't be bigger than 300. Otherwise the Uri is suggested.)