2025-05-25

最近有看到有朋友在討論QQ頭像的裁剪上傳是怎麼實現的,吼吼,小馬也沒做過,好奇之下學習下,發現以前項目中有類型的功能,結合官方文檔裡面的解釋,就更好玩瞭,周末,急急忙忙寫的,記錄在博客裡,希望能與大傢交流學習,也懇請高手能解答小馬在代碼註釋中提出的疑問,不管有沒有人回答,小馬先謝謝瞭,一樣的,先看下效果圖(效果圖小馬不解釋瞭,直接流水寫下去,小馬是直接在模擬器裡寫的,能在真機上使用,因為很簡單),再看代碼是怎麼實現的:
一:主佈局界面

二:點擊控件觸發事件後效果圖

三:拍照完之後效果圖

四:裁剪界面效果圖

五:點擊相冊後返回的圖片效果圖

六:裁剪完從相冊PICK的保存後的效果圖 

下面直接來看下主控制類代碼,如下:
  
1. package com.xiaoma.piccut.demo; 
2. 
3. import java.io.File; 
4. import android.app.Activity; 
5. import android.app.AlertDialog; 
6. import android.content.DialogInterface; 
7. import android.content.Intent; 
8. import android.graphics.Bitmap; 
9. import android.graphics.drawable.BitmapDrawable; 
10. import android.graphics.drawable.Drawable; 
11. import android.net.Uri; 
12. import android.os.Bundle; 
13. import android.os.Environment; 
14. import android.provider.MediaStore; 
15. import android.view.View; 
16. import android.view.View.OnClickListener; 
17. import android.widget.Button; 
18. import android.widget.ImageButton; 
19. import android.widget.ImageView; 
20. /** 
21.  * @Title: PicCutDemoActivity.java 
22.  * @Package com.xiaoma.piccut.demo 
23.  * @Description: 圖片裁剪功能測試 
24.  * @author XiaoMa 
25.  */
26. public class PicCutDemoActivity extends Activity implements OnClickListener { 
27. 
28.     private ImageButton ib = null; 
29.     private ImageView iv = null; 
30.     private Button btn = null; 
31.     private String tp = null; 
32.      
33. 
34.     /** Called when the activity is first created. */
35.     @Override
36.     public void onCreate(Bundle savedInstanceState) { 
37.         super.onCreate(savedInstanceState); 
38.         setContentView(R.layout.main); 
39.         //初始化 
40.         init(); 
41.     } 
42.      
43.     /** 
44.      * 初始化方法實現 
45.      */
46.     private void init() { 
47.         ib = (ImageButton) findViewById(R.id.imageButton1); 
48.         iv = (ImageView) findViewById(R.id.imageView1); 
49.         btn = (Button) findViewById(R.id.button1); 
50.         ib.setOnClickListener(this); 
51.         iv.setOnClickListener(this); 
52.         btn.setOnClickListener(this); 
53.     } 
54. 
55.      
56.     /** 
57.      * 控件點擊事件實現 
58.      *  
59.      * 因為有朋友問不同控件的背景圖裁剪怎麼實現, 
60.      * 我就在這個地方用瞭三個控件,隻為瞭自己記錄學習 
61.      * 大傢覺得沒用的可以跳過啦 
62.      */
63.     @Override
64.     public void onClick(View v) { 
65.         switch (v.getId()) { 
66.         case R.id.imageButton1: 
67.             ShowPickDialog(); 
68.             break; 
69.         case R.id.imageView1: 
70.             ShowPickDialog(); 
71.             break; 
72.         case R.id.button1: 
73.             ShowPickDialog(); 
74.             break; 
75. 
76.         default: 
77.             break; 
78.         } 
79.     } 
80. 
81.     /** 
82.      * 選擇提示對話框 
83.      */
84.     private void ShowPickDialog() { 
85.         new AlertDialog.Builder(this) 
86.                 .setTitle("設置頭像…") 
87.                 .setNegativeButton("相冊", new DialogInterface.OnClickListener() { 
88.                     public void onClick(DialogInterface dialog, int which) { 
89.                         dialog.dismiss(); 
90.                         /** 
91.                          * 剛開始,我自己也不知道ACTION_PICK是幹嘛的,後來直接看Intent源碼, 
92.                          * 可以發現裡面很多東西,Intent是個很強大的東西,大傢一定仔細閱讀下 
93.                          */
94.                         Intent intent = new Intent(Intent.ACTION_PICK, null); 
95.                          
96.                         /** 
97.                          * 下面這句話,與其它方式寫是一樣的效果,如果: 
98.                          * intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 
99.                          * intent.setType(""image/*");設置數據類型 
100.                          * 如果朋友們要限制上傳到服務器的圖片類型時可以直接寫如:"image/jpeg 、 image/png等的類型" 
101.                          * 這個地方小馬有個疑問,希望高手解答下:就是這個數據URI與類型為什麼要分兩種形式來寫呀?有什麼區別? 
102.                          */
103.                         intent.setDataAndType( 
104.                                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
105.                                 "image/*"); 
106.                         startActivityForResult(intent, 1); 
107. 
108.                     } 
109.                 }) 
110.                 .setPositiveButton("拍照", new DialogInterface.OnClickListener() { 
111.                     public void onClick(DialogInterface dialog, int whichButton) { 
112.                         dialog.dismiss(); 
113.                         /** 
114.                          * 下面這句還是老樣子,調用快速拍照功能,至於為什麼叫快速拍照,大傢可以參考如下官方 
115.                          * 文檔,you_sdk_path/docs/guide/topics/media/camera.html 
116.                          * 我剛看的時候因為太長就認真看,其實是錯的,這個裡面有用的太多瞭,所以大傢不要認為 
117.                          * 官方文檔太長瞭就不看瞭,其實是錯的,這個地方小馬也錯瞭,必須改正 
118.                          */ 
119.                         Intent intent = new Intent( 
120.                                 MediaStore.ACTION_IMAGE_CAPTURE); 
121.                         //下面這句指定調用相機拍照後的照片存儲的路徑 
122.                         intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri 
123.                                 .fromFile(new File(Environment 
124.                                         .getExternalStorageDirectory(), 
125.                                         "xiaoma.jpg"))); 
126.                         startActivityForResult(intent, 2); 
127.                     } 
128.                 }).show(); 
129.     } 
130. 
131.     @Override
132.     protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
133.         switch (requestCode) { 
134.         // 如果是直接從相冊獲取 
135.         case 1: 
136.             startPhotoZoom(data.getData()); 
137.             break; 
138.         // 如果是調用相機拍照時 
139.         case 2: 
140.             File temp = new File(Environment.getExternalStorageDirectory() 
141.                     + "/xiaoma.jpg"); 
142.             startPhotoZoom(Uri.fromFile(temp)); 
143.             break; 
144.         // 取得裁剪後的圖片 
145.         case 3: 
146.             /** 
147.              * 非空判斷大傢一定要驗證,如果不驗證的話, 
148.              * 在剪裁之後如果發現不滿意,要重新裁剪,丟棄 
149.              * 當前功能時,會報NullException,小馬隻 
150.              * 在這個地方加下,大傢可以根據不同情況在合適的 
151.              * 地方做判斷處理類似情況 
152.              *  
153.              */
154.             if(data != null){ 
155.                 setPicToView(data); 
156.             } 
157.             break; 
158.         default: 
159.             break; 
160. 
161.         } 
162.         super.onActivityResult(requestCode, resultCode, data); 
163.     } 
164.      
165.     /** 
166.      * 裁剪圖片方法實現 
167.      * @param uri 
168.      */
169.     public void startPhotoZoom(Uri uri) { 
170.         /* 
171.          * 至於下面這個Intent的ACTION是怎麼知道的,大傢可以看下自己路徑下的如下網頁 
172.          * yourself_sdk_path/docs/reference/android/content/Intent.html 
173.          * 直接在裡面Ctrl+F搜:CROP ,之前小馬沒仔細看過,其實安卓系統早已經有自帶圖片裁剪功能, 
174.          * 是直接調本地庫的,小馬不懂C C++  這個不做詳細瞭解去瞭,有輪子就用輪子,不再研究輪子是怎麼 
175.          * 制做的瞭…吼吼 
176.          */
177.         Intent intent = new Intent("com.android.camera.action.CROP"); 
178.         intent.setDataAndType(uri, "image/*"); 
179.         //下面這個crop=true是設置在開啟的Intent中設置顯示的VIEW可裁剪 
180.         intent.putExtra("crop", "true"); 
181.         // aspectX aspectY 是寬高的比例 
182.         intent.putExtra("aspectX", 1); 
183.         intent.putExtra("aspectY", 1); 
184.         // outputX outputY 是裁剪圖片寬高 
185.         intent.putExtra("outputX", 150); 
186.         intent.putExtra("outputY", 150); 
187.         intent.putExtra("return-data", true); 
188.         startActivityForResult(intent, 3); 
189.     } 
190.      
191.     /** 
192.      * 保存裁剪之後的圖片數據 
193.      * @param picdata 
194.      */ 
195.     private void setPicToView(Intent picdata) { 
196.         Bundle extras = picdata.getExtras(); 
197.         if (extras != null) { 
198.             Bitmap photo = extras.getParcelable("data"); 
199.             Drawable drawable = new BitmapDrawable(photo); 
200.              
201.             /** 
202.              * 下面註釋的方法是將裁剪之後的圖片以Base64Coder的字符方式上 
203.              * 傳到服務器,QQ頭像上傳采用的方法跟這個類似 
204.              */
205.              
206.             /*ByteArrayOutputStream stream = new ByteArrayOutputStream(); 
207.             photo.compress(Bitmap.CompressFormat.JPEG, 60, stream); 
208.             byte[] b = stream.toByteArray(); 
209.             // 將圖片流以字符串形式存儲下來 
210.              
211.             tp = new String(Base64Coder.encodeLines(b)); 
212.             這個地方大傢可以寫下給服務器上傳圖片的實現,直接把tp直接上傳就可以瞭, 
213.             服務器處理的方法是服務器那邊的事瞭,吼吼 
214.              
215.             如果下載到的服務器的數據還是以Base64Coder的形式的話,可以用以下方式轉換 
216.             為我們可以用的圖片類型就OK啦…吼吼 
217.             Bitmap dBitmap = BitmapFactory.decodeFile(tp); 
218.             Drawable drawable = new BitmapDrawable(dBitmap); 
219.             */
220.             ib.setBackgroundDrawable(drawable); 
221.             iv.setBackgroundDrawable(drawable); 
222.         } 
223.     } 
224. 
225. }
下面來看下裁剪中用到的類,大傢詳細看下頭註釋:
1. package com.xiaoma.piccut.demo; 
2. 
3. 
4. 
5. 
6. /** 
7.  * 下面這些註釋是下載這個類的時候本來就有的,本來要刪除的,但看瞭下竟然是license,吼吼, 
8.  * 好東西,留在註釋裡,以備不時之用,大傢有需要加license的可以到下面的網址找哦 
9.  */
10. 
11. //EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal 
12. //LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html 
13. //GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html 
14. //AL, Apache License, V2.0 or later, http://www.apache.org/licenses 
15. //BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php 
16. /** 
17. * A Base64 encoder/decoder. 
18. * 
19. * <p> 
20. * This class is used to encode and decode data in Base64 format as described in RFC 1521. 
21. * 
22. * <p> 
23. * Project home page: <a href="http://www.source-code.biz/base64coder/java/">www.source-code.biz/base64coder/java</a><br> 
24. * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> 
25. * Multi-licensed: EPL / LGPL / GPL / AL / BSD. 
26. */
27. 
28. /** 
29.  * 這個類在上面註釋的網址中有,大傢可以自行下載下,也可以直接用這個, 
30.  * 公開的Base64Coder類(不用深究它是怎麼實現的, 
31.  * 還是那句話,有輪子直接用輪子),好用的要死人瞭… 
32.  * 小馬也很無恥的引用瞭這個網址下的東東,吼吼… 
33. * @Title: Base64Coder.java 
34. * @Package com.xiaoma.piccut.demo 
35. * @Description: TODO 
36. * @author XiaoMa 
37.  */
38. 
39. public class Base64Coder { 
40. 
41. //The line separator string of the operating system. 
42. private static final String systemLineSeparator = System.getProperty("line.separator"); 
43. 
44. //Mapping table from 6-bit nibbles to Base64 characters. 
45. private static char[]    map1 = new char[64]; 
46. static { 
47.    int i=0; 
48.    for (char c='A'; c<='Z'; c++) map1[i++] = c; 
49.    for (char c='a'; c<='z'; c++) map1[i++] = c; 
50.    for (char c='0'; c<='9'; c++) map1[i++] = c; 
51.    map1[i++] = '+'; map1[i++] = '/'; } 
52. 
53. //Mapping table from Base64 characters to 6-bit nibbles. 
54. private static byte[]    map2 = new byte[128]; 
55. static { 
56.    for (int i=0; i<map2.length; i++) map2[i] = -1; 
57.    for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; } 
58. 
59. /** 
60. * Encodes a string into Base64 format. 
61. * No blanks or line breaks are inserted. 
62. * @param s  A String to be encoded. 
63. * @return   A String containing the Base64 encoded data. 
64. */
65. public static String encodeString (String s) { 
66. return new String(encode(s.getBytes())); } 
67. 
68. /** 
69. * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters. 
70. * This method is compatible with <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>. 
71. * @param in  An array containing the data bytes to be encoded. 
72. * @return    A String containing the Base64 encoded data, broken into lines. 
73. */
74. public static String encodeLines (byte[] in) { 
75. return encodeLines(in, 0, in.length, 76, systemLineSeparator); } 
76. 
77. /** 
78. * Encodes a byte array into Base 64 format and breaks the output into lines. 
79. * @param in            An array containing the data bytes to be encoded. 
80. * @param iOff          Offset of the first byte in <code>in</code> to be processed. 
81. * @param iLen          Number of bytes to be processed in <code>in</code>, starting at <code>iOff</code>. 
82. * @param lineLen       Line length for the output data. Should be a multiple of 4. 
83. * @param lineSeparator The line separator to be used to separate the output lines. 
84. * @return              A String containing the Base64 encoded data, broken into lines. 
85. */
86. public static String encodeLines (byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) { 
87. int blockLen = (lineLen*3) / 4; 
88. if (blockLen <= 0) throw new IllegalArgumentException(); 
89. int lines = (iLen+blockLen-1) / blockLen; 
90. int bufLen = ((iLen+2)/3)*4 + lines*lineSeparator.length(); 
91. StringBuilder buf = new StringBuilder(bufLen); 
92. int ip = 0; 
93. while (ip < iLen) { 
94.    int l = Math.min(iLen-ip, blockLen); 
95.    buf.append (encode(in, iOff+ip, l)); 
96.    buf.append (lineSeparator); 
97.    ip += l; } 
98. return buf.toString(); } 
99. 
100. /** 
101. * Encodes a byte array into Base64 format. 
102. * No blanks or line breaks are inserted in the output. 
103. * @param in  An array containing the data bytes to be encoded. 
104. * @return    A character array containing the Base64 encoded data. 
105. */
106. public static char[] encode (byte[] in) { 
107. return encode(in, 0, in.length); } 
108. 
109. /** 
110. * Encodes a byte array into Base64 format. 
111. * No blanks or line breaks are inserted in the output. 
112. * @param in    An array containing the data bytes to be encoded. 
113. * @param iLen  Number of bytes to process in <code>in</code>. 
114. * @return      A character array containing the Base64 encoded data. 
115. */
116. public static char[] encode (byte[] in, int iLen) { 
117. return encode(in, 0, iLen); } 
118. 
119. /** 
120. * Encodes a byte array into Base64 format. 
121. * No blanks or line breaks are inserted in the output. 
122. * @param in    An array containing the data bytes to be encoded. 
123. * @param iOff  Offset of the first byte in <code>in</code> to be processed. 
124. * @param iLen  Number of bytes to process in <code>in</code>, starting at <code>iOff</code>. 
125. * @return      A character array containing the Base64 encoded data. 
126. */
127. public static char[] encode (byte[] in, int iOff, int iLen) { 
128. int oDataLen = (iLen*4+2)/3;       // output length without padding 
129. int oLen = ((iLen+2)/3)*4;         // output length including padding 
130. char[] out = new char[oLen]; 
131. int ip = iOff; 
132. int iEnd = iOff + iLen; 
133. int op = 0; 
134. while (ip < iEnd) { 
135.    int i0 = in[ip++] & 0xff; 
136.    int i1 = ip < iEnd ? in[ip++] & 0xff : 0; 
137.    int i2 = ip < iEnd ? in[ip++] & 0xff : 0; 
138.    int o0 = i0 >>> 2; 
139.    int o1 = ((i0 &   3) << 4) | (i1 >>> 4); 
140.    int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); 
141.    int o3 = i2 & 0x3F; 
142.    out[op++] = map1[o0]; 
143.    out[op++] = map1[o1]; 
144.    out[op] = op < oDataLen ? map1[o2] : '='; op++; 
145.    out[op] = op < oDataLen ? map1[o3] : '='; op++; } 
146. return out; } 
147. 
148. /** 
149. * Decodes a string from Base64 format. 
150. * No blanks or line breaks are allowed within the Base64 encoded input data. 
151. * @param s  A Base64 String to be decoded. 
152. * @return   A String containing the decoded data. 
153. * @throws   IllegalArgumentException If the input is not valid Base64 encoded data. 
154. */
155. public static String decodeString (String s) { 
156. return new String(decode(s)); } 
157. 
158. /** 
159. * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks. 
160. * CR, LF, Tab and Space characters are ignored in the input data. 
161. * This method is compatible with <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>. 
162. * @param s  A Base64 String to be decoded. 
163. * @return   An array containing the decoded data bytes. 
164. * @throws   IllegalArgumentException If the input is not valid Base64 encoded data. 
165. */
166. public static byte[] decodeLines (String s) { 
167. char[] buf = new char[s.length()+3]; 
168. int p = 0; 
169. for (int ip = 0; ip < s.length(); ip++) { 
170.    char c = s.charAt(ip); 
171.    if (c != ' ' && c != '\r' && c != '\n' && c != '\t') 
172.       buf[p++] = c; } 
173.    while ((p % 4) != 0) 
174.        buf[p++] = '0'; 
175.      
176. return decode(buf, 0, p); } 
177. 
178. /** 
179. * Decodes a byte array from Base64 format. 
180. * No blanks or line breaks are allowed within the Base64 encoded input data. 
181. * @param s  A Base64 String to be decoded. 
182. * @return   An array containing the decoded data bytes. 
183. * @throws   IllegalArgumentException If the input is not valid Base64 encoded data. 
184. */
185. public static byte[] decode (String s) { 
186. return decode(s.toCharArray()); } 
187. 
188. /** 
189. * Decodes a byte array from Base64 format. 
190. * No blanks or line breaks are allowed within the Base64 encoded input data. 
191. * @param in  A character array containing the Base64 encoded data. 
192. * @return    An array containing the decoded data bytes. 
193. * @throws    IllegalArgumentException If the input is not valid Base64 encoded data. 
194. */
195. public static byte[] decode (char[] in) { 
196. return decode(in, 0, in.length); } 
197. 
198. /** 
199. * Decodes a byte array from Base64 format. 
200. * No blanks or line breaks are allowed within the Base64 encoded input data. 
201. * @param in    A character array containing the Base64 encoded data. 
202. * @param iOff  Offset of the first character in <code>in</code> to be processed. 
203. * @param iLen  Number of characters to process in <code>in</code>, starting at <code>iOff</code>. 
204. * @return      An array containing the decoded data bytes. 
205. * @throws      IllegalArgumentException If the input is not valid Base64 encoded data. 
206. */
207. public static byte[] decode (char[] in, int iOff, int iLen) { 
208. if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4."); 
209. while (iLen > 0 && in[iOff+iLen-1] == '=') iLen–; 
210. int oLen = (iLen*3) / 4; 
211. byte[] out = new byte[oLen]; 
212. int ip = iOff; 
213. int iEnd = iOff + iLen; 
214. int op = 0; 
215. while (ip < iEnd) { 
216.    int i0 = in[ip++]; 
217.    int i1 = in[ip++]; 
218.    int i2 = ip < iEnd ? in[ip++] : 'A'; 
219.    int i3 = ip < iEnd ? in[ip++] : 'A'; 
220.    if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) 
221.       throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); 
222.    int b0 = map2[i0]; 
223.    int b1 = map2[i1]; 
224.    int b2 = map2[i2]; 
225.    int b3 = map2[i3]; 
226.    if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) 
227.       throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); 
228.    int o0 = ( b0       <<2) | (b1>>>4); 
229.    int o1 = ((b1 & 0xf)<<4) | (b2>>>2); 
230.    int o2 = ((b2 &   3)<<6) |  b3; 
231.    out[op++] = (byte)o0; 
232.    if (op<oLen) out[op++] = (byte)o1; 
233.    if (op<oLen) out[op++] = (byte)o2; } 
234. return out; } 
235. 
236. //Dummy constructor. 
237. private Base64Coder() {} 
238. 
239. } // end class Base64Coder
           最後,小馬還把小DEMO源碼放在附件裡面,有需要的朋友可以下載下來,共同交流學習,也懇請高人回答下小馬在文章註釋中提出的問題,謝謝,文章是小馬急急忙忙在傢寫的,在網吧發的,暈…不是我有多用功,這邊下雨,討厭下雨,下雨我就鬱悶,來網吧玩的,順帶發下文章,吼吼,該玩的時候死命的玩,該工作的時候死命的工作,年輕時瘋狂,老瞭不後悔,吼吼,加油加油!大傢工作,也註意身體健康,嘿嘿,你懂的,不解釋…哈哈

 摘自  酷_莫名簡單 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *