花了兩天時間看了下android的圖片裁剪功能的實現。其實剛開始做這個我挺虛的,以為整個功能都需要自己寫出來,但查了些資料,發現android已經提供了裁剪功能,需要的話自己調用就成了。soga,這下輕松多了。
原文地址請保留http://www.cnblogs.com/rossoneri/p/3976530.html
首先推薦幾篇博客
要想弄明白裁剪功能,這系列博客非常重要,你可以不看我下面總結的,但你一定要看他這系列的幾篇文章。
Android 圖片裁剪功能實現詳解(類似QQ自定義頭像裁剪)
這篇也不錯,比較喜歡他的注釋。雖然也有些誤導,比如說他有一段對setData,setType和setDataAndType方法的區別疑問,他說兩種寫法一樣效果,我就信了,害得我找bug找了兩個小時,一直懷疑別的參數出問題,實際上是這兩個方法的差別。這一點后面會說。
其他的相關博客有很多,但基本上大同小異,包括我這篇。有了上面的兩個博客,就可以大概搞懂這方面的原理了。
我要寫的,就是多寫一些注釋,改變一些寫法,增加點說明,積累點經驗,為了自己以后方便重溫自己做過的東西,而已。
不再浪費你我的時間,開始了。
丑得不能忍的分割區
RyanHoo的Demo寫的很詳細。但要學習,我習慣先把代碼簡化,看的邏輯清楚些。我選擇了最適應自己需求的選擇大圖片裁剪的部分代碼
我測試的簡化代碼
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 tools:context=".MainActivity" > 6 7 <Button 8 android:id="@+id/button" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:text="click me!" /> 12 13 <ImageView 14 android:id="@+id/imageview" 15 android:layout_width="match_parent" 16 android:layout_height="match_parent" 17 android:scaleType="centerInside" /> 18 19 </LinearLayout>
1 public class MainActivity extends Activity implements OnClickListener { 2 3 private Uri imageUri; 4 private static final String IMAGE_FILE_LOCATION = "file:///sdcard/temp.jpg"; 5 private Button btn; 6 private ImageView iv; 7 8 @Override 9 protected void onCreate(Bundle savedInstanceState) { 10 super.onCreate(savedInstanceState); 11 setContentView(R.layout.activity_main); 12 btn = (Button) findViewById(R.id.button); 13 btn.setOnClickListener(this); 14 imageUri = Uri.parse(IMAGE_FILE_LOCATION); 15 iv = (ImageView) findViewById(R.id.imageview); 16 } 17 18 @Override 19 public boolean onCreateOptionsMenu(Menu menu) { 20 // Inflate the menu; this adds items to the action bar if it is present. 21 getMenuInflater().inflate(R.menu.activity_main, menu); 22 return true; 23 } 24 25 @Override 26 public void onClick(View v) { 27 // TODO Auto-generated method stub 28 29 // 試着改成打開自己寫的圖片瀏覽器 30 switch (v.getId()) { 31 case R.id.button: 32 // 這段代碼使用ACTION_GET_CONTENT和ACTION_PICK效果相同 33 Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); 34 // Intent intent = new Intent(Intent.ACTION_PICK, null); 35 36 // 如果使用com.android.camera.action.CROP 則直接打開裁剪照片的activity 那么可以用自己的圖片瀏覽器選擇圖片 傳入參數並使用之 37 // Intent intent = new Intent("com.android.camera.action.CROP"); 38 39 // 如果不設置type,則 ACTION_GET_CONTENT 會彈出異常FATAL EXCEPTION:main android.content.ActivityNotFoundException 40 // 而 ACTION_PICK 會彈出可用程序列表 但沒有打開圖片相關的程序(在我的兩個設備上是這樣) 41 intent.setType("image/*"); 42 43 // 設置在開啟的Intent中設置顯示的view可裁剪 44 // 這段代碼里設置成false也能裁剪啊。。。這是為什么?懂的給我講講了 45 // 這段注釋掉就不會跳轉到裁剪的activity 46 intent.putExtra("crop", "true"); 47 48 // 設置x,y的比例,截圖方框就按照這個比例來截 若設置為0,0,或者不設置 則自由比例截圖 49 intent.putExtra("aspectX", 2); 50 intent.putExtra("aspectY", 1); 51 52 // 裁剪區的寬和高 其實就是裁剪后的顯示區域 若裁剪的比例不是顯示的比例,則自動壓縮圖片填滿顯示區域。若設置為0,0 就不顯示。若不設置,則按原始大小顯示 53 intent.putExtra("outputX", 200); 54 intent.putExtra("outputY", 100); 55 56 // 不知道有啥用。。可能會保存一個比例值 需要相關文檔啊 57 intent.putExtra("scale", true); 58 59 // true的話直接返回bitmap,可能會很占內存 不建議 60 intent.putExtra("return-data", false); 61 // 上面設為false的時候將MediaStore.EXTRA_OUTPUT即"output"關聯一個Uri 62 intent.putExtra("output", imageUri); 63 // 看參數即可知道是輸出格式 64 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); 65 // 面部識別 這里用不上 66 intent.putExtra("noFaceDetection", false); 67 68 // 想從Activity中獲得返回數據,在啟動Activity時候使用startActivityForResult方法 69 // 1為請求代碼,可以是任意值,個人感覺用資源id會比較清楚,而且不會重復 比如當前控件的R.id.button 70 startActivityForResult(intent, 1); 71 break; 72 default: 73 break; 74 } 75 } 76 77 @Override 78 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 79 // TODO Auto-generated method stub 80 super.onActivityResult(requestCode, resultCode, data); 81 if (resultCode != Activity.RESULT_OK) {// result is not correct 82 return; 83 } else { 84 switch (requestCode) { 85 case 1: 86 if (imageUri != null) { 87 Bitmap bitmap = decodeUriAsBitmap(imageUri); 88 // 把解析到的位圖顯示出來 89 iv.setImageBitmap(bitmap); 90 } 91 break; 92 default: 93 break; 94 } 95 96 } 97 } 98 99 private Bitmap decodeUriAsBitmap(Uri uri) { 100 Bitmap bitmap = null; 101 try { 102 // 先通過getContentResolver方法獲得一個ContentResolver實例, 103 // 調用openInputStream(Uri)方法獲得uri關聯的數據流stream 104 // 把上一步獲得的數據流解析成為bitmap 105 bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); 106 } catch (FileNotFoundException e) { 107 e.printStackTrace(); 108 return null; 109 } 110 return bitmap; 111 } 112 }
其實用法看前面的博客就已經很清楚了,這里主要部分就是Intent附加數據的具體含義解釋與使用方法,我都盡量寫在代碼的注釋當中了。
再丑也得忍的分割線
我后來想只調用裁剪窗口,而選圖片的時候使用自己寫的圖片選擇器,那么這個參數怎么傳,怎么調用裁剪activity呢?
使用裁剪功能用"com.android.camera.action.CROP"就可以。
傳圖片的話有兩個方法,一個是intent直接傳bitmap數據,另一個是傳uri。
1 private void startCropIntent(String path) throws FileNotFoundException { 2 Bitmap bmp = BitmapFactory.decodeFile(path); 5 Intent intent = new Intent("com.android.camera.action.CROP"); 9 // Intent傳輸的bytes不能超過40k。不建議這樣 無法處理大圖 10 intent.putExtra("data", bmp); 11 // intent.setData(uri); 12 // intent.setType("image/*"); 13 intent.setDataAndType(imageUri, "image/*"); 14 intent.putExtra("crop", "true"); 15 intent.putExtra("aspectX", 2); 16 intent.putExtra("aspectY", 1); 17 intent.putExtra("outputX", 300); 18 intent.putExtra("outputY", 150); 19 // 設置為true直接返回bitmap 20 intent.putExtra("return-data", true); 24 startActivityForResult(intent, 1); 25 } 26 27 @Override 28 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 29 super.onActivityResult(requestCode, resultCode, data); 30 if (resultCode != Activity.RESULT_OK) {// result is not correct 31 return; 32 } else { 33 switch (requestCode) { 34 case 1: 40 Bundle bundle = data.getExtras(); 41 Bitmap bitmap = bundle.getParcelable("data"); 42 iv.setImageBitmap(bitmap); 43 44 break; 45 46 default: 47 break; 48 } 49 } 51 }
這里參數path是選擇的圖片的絕對路徑。
這種方法有局限性,因為intent傳遞的數據不超過40k,只能選擇40k以下的圖片裁剪
還是使用uri比較好
1 private void startCropIntent(String path) throws FileNotFoundException { 2 Bitmap bmp = BitmapFactory.decodeFile(path); 3 4 File file = new File(path); 5 Intent intent = new Intent("com.android.camera.action.CROP"); 7 Uri uri = Uri.fromFile(file);// parse(pathUri);13 intent.setDataAndType(uri, "image/*"); 14 intent.putExtra("crop", "true"); 15 intent.putExtra("aspectX", 2); 16 intent.putExtra("aspectY", 1); 17 intent.putExtra("outputX", 300); 18 intent.putExtra("outputY", 150); 19 // 設置為true直接返回bitmap 20 intent.putExtra("return-data", false); 21 // 上面設為false的時候將MediaStore.EXTRA_OUTPUT關聯一個Uri 22 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); 23 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); 24 startActivityForResult(intent, 1); 25 } 26 27 @Override 28 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 29 super.onActivityResult(requestCode, resultCode, data); 30 if (resultCode != Activity.RESULT_OK) {// result is not correct 31 return; 32 } else { 33 switch (requestCode) { 34 case 1: 35 if (imageUri != null) { 36 Bitmap bitmap = decodeUriAsBitmap(imageUri); 37 // 把解析到的位圖顯示出來 38 iv.setImageBitmap(bitmap); 39 } 44 break; 45 46 default: 47 break; 48 } 49 } 50 51 } 52 53 private Bitmap decodeUriAsBitmap(Uri uri) { 54 Bitmap bitmap = null; 55 try { 59 bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); 60 } catch (FileNotFoundException e) { 61 e.printStackTrace(); 62 return null; 63 } 64 return bitmap; 65 }
這里需要注意
intent.setData(uri);intent.setType("image/*");和
intent.setDataAndType(uri, "image/*");是有區別的。
我開始以為兩個方法一樣的,但看了源碼就清楚了。
1 public Intent setDataAndType(Uri data, String type) { 2 mData = data; 3 mType = type; 4 return this; 5 }
1 public Intent setData(Uri data) { 2 mData = data; 3 mType = null; 4 return this; 5 }
1 public Intent setType(String type) { 2 mData = null; 3 mType = type; 4 return this; 5 }
好了,調用系統裁剪就這么些內容,這方面除了文檔太難找,也沒什么難的。