1 寫在前面
前兩天看了下别人儲存圖檔的方式,又看了郭神的第一行代碼的拍照和打開相冊,又研究了下對圖檔的裁剪,然後合在一起做了個demo,以後要用直接用就可以了。先說說這個代碼比網上其他的代碼好的地方。
* 在打開攝像頭拍照時相容了7.0擷取uri的方式;
* 打開相冊通過uri擷取bitmap時使用郭神的方法和架構提供的方法,查了下資料沒發現這兩種方法的差別以及對比,後面會說;
* 裁剪架構裁剪圖檔架構中減輕了很多項目中用不到的東西,等下看效果;
* 儲存圖檔把前兩天部落格中的方法儲存圖檔至相冊提煉出來,提供了兩種方法選擇,隻儲存圖檔至系統圖庫和儲存圖檔至自己連理的檔案和系統圖檔都儲存;
2 效果
3 實作
a. 拍照并擷取bitmap,主要注意剛才我拍完照片後照片在圖庫并沒有看到,因為我這裡拍照完成後圖檔的的位址使用的是
File photoFile = new File(getExternalCacheDir(),"hehe.jpg");
其中這個getExternalCacheDir()并沒有牽涉到sd卡,因為6.0之後讀取sd卡是危險權限,要申請權限,這裡避免麻煩,就直接使用這個緩存的位址sdcard/android/包名/cache。還有一點就是7.0以後,擷取uri對象不能使用以前的getUriForFile(),因為會報FileUriExposedException,這個我沒驗證過,但是郭神的書裡是這樣說的。這裡我直接上代碼,代碼裡注釋寫的很清楚。
/**
* 打開相機
*/
public void takePhoto(){
//将照片檔案儲存在緩存檔案夾中,注意這裡使用的是緩存檔案夾,位址應該是sdcard/android/包名/cache,這樣子拍照完後在圖庫是看不到拍照照片的
//如果想在圖庫看到照片 則這裡應該用 Environment.getExternalStorageDirectory()
File photoFile = new File(getExternalCacheDir(),"hehe.jpg");
if(photoFile.exists()){
photoFile.delete();
}else{
try {
photoFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 運作到這,photoFile已經存在,這裡需要獲得這個檔案的uri
* 分兩種情況,android7.0以上和以下
*/
if(Build.VERSION.SDK_INT>=){
/**
* FileProvider.getUriForFile(),這個方法中需要填寫三個參數,
* 第一個Context,
* 第二個S
* tring 任意
* 第三個File
*/
photoUri = FileProvider.getUriForFile(this, "guozhaohui.com.picturecropeasy", photoFile);
}else{
photoUri = Uri.fromFile(photoFile);
}
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,photoUri);
startActivityForResult(intent,);
}
點選拍照完成傳回結果的處理
// case : //拍照傳回結果
//
// if(resultCode==RESULT_OK){
//
// try {
// Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(photoUri));
// iv.setImageBitmap(bitmap);
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// }
//
// }
b. 打開相冊,裁剪圖檔并擷取bitmap。先把這個裁剪架構效果貼出來吧,因為如果單純的打開相冊擷取bitmap不是很難。
架構位址位址
這裡面東西很多,但是我們項目中能用到的不是很多,是以如果可以删減那就好了,我這裡在點選 他的基礎上又去掉一些東西,是以最好自己跑起來體驗體驗,順便提一句那位大神的github位址好像不能在as中直接clone,我搞了很久,gradle一直卡住估計是qiang的原因,我是下載下傳源碼導入的。
打開相冊
/**
* 打開相冊
*/
public void takeAlbum(){
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
// startActivityForResult(Intent.createChooser(intent,"選擇圖檔aaaa"),200);
startActivityForResult(intent,);
}
這裡的代碼主要注意一點
那位大佬用的是這種,其實沒有什麼差別,就是我們在打開相冊時那個提示語可以我們設定。對傳回結果的處理,我們一般都是用switch,但是我發現要和大佬的架構結合,不能這樣寫,是以前面我的代碼是注釋調的。先上簡單的吧,郭神書中的寫法很直覺。
//
// case 200: //相冊傳回結果
//
// if(resultCode==RESULT_OK){
//
//// if(Build.VERSION.SDK_INT>=19){ //版本4.4以上選取圖檔傳回的uri處理方式
////
//// handleImgOver(data);
////
//// }else{ //版本4.4以下選取圖檔傳回的uri處理方式
//// handleImgBefore(data);
//// }
//
// }
// break;
因為從4.4開始,選取相冊傳回的圖檔不再傳回真事的Uri了,而是一個封裝過的uri,因為4.4以上的手機需要對這個uri解析。
/**
* 4.4以上對傳回的Intent處理,擷取圖檔的path
* @param data
*/
@TargetApi()
public void handleImgOver(Uri data){
String imagePath = null;
Uri uri = data;
if(DocumentsContract.isDocumentUri(this,uri)){
//如果是document類型的uri,則通過documentid處理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())){
String id = docId.split(":")[];
String selection = MediaStore.Images.Media._ID+"="+id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
}else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
}else if("content".equalsIgnoreCase(uri.getScheme())){
//如果是content類型的uri,則使用普通方式處理
imagePath = getImagePath(uri, null);
}else if("file".equalsIgnoreCase(uri.getScheme())){
//如果是file類型的uri,直接擷取圖檔的路徑
imagePath = uri.getPath();
}
showImg(imagePath);
}
/**
* 4.4以下對Intent的處理,擷取img的path
* @param data
*/
public void handleImgBefore(Uri data){
Uri uri = data;
String imgPath = getImagePath(uri, null);
showImg(imgPath);
}
/**
* 此方法用于uri是content類型,通過這個方法可以擷取路徑path
* @param uri
* @param selection
* @return
*/
public String getImagePath(Uri uri, String selection){
String path = null;
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if(cursor!=null){
if(cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
前面我說了,那位大佬擷取bitmap的方法并不是郭神的這種,就一行就解決了
// Bitmap bmp;
// try {
// bmp = MediaStore.Images.Media.getBitmap(getContentResolver(), resultUri);
// iv.setImageBitmap(bmp);
然後我檢視了下源碼
public static final Bitmap getBitmap(ContentResolver cr, Uri url)
throws FileNotFoundException, IOException {
InputStream input = cr.openInputStream(url);
Bitmap bitmap = BitmapFactory.decodeStream(input);
input.close();
return bitmap;
}
其實很明顯可以看到,這個方法的核心就是前面拍照代碼一樣,知道檔案的位址
InputStream input = cr.openInputStream(url);
Bitmap bitmap = BitmapFactory.decodeStream(input);
是以總結來說,如果我們如果隻是打開相冊并且擷取bitmap,這個檔案uri的位址并不是我們定義的,剛好我們需要結果uri來擷取path,但是這個架構中,我們實作寫好了裁剪完成後圖檔的儲存位址,是以可以這樣直接寫。
// 剪切後圖像檔案
private Uri mDestinationUri;
mDestinationUri = Uri.fromFile(new File(getCacheDir(), "cropImage.jpeg"));
注意剛才這個getCacheDir(),和上面那個getExternalCacheDir(),都是樣的,随便不需要申請讀寫sd權限,但是在manifest中還是要配置的。
還有一點重要的要注意,我們要改裁剪activity的一些東西,就在這個類
UCropActivity,是以我們如果要改變裁剪框的樣式改變,比如正方形的,有無邊框的,都是在這裡設定
private void initView() {
mUCropView = (UCropView) this.findViewById(R.id.weixin_act_ucrop);
saveTv = (TextView) this.findViewById(R.id.weixin_act_crop_tv_save);
mGestureCropImageView = mUCropView.getCropImageView();
mOverlayView = mUCropView.getOverlayView();
// 設定允許縮放
mGestureCropImageView.setScaleEnabled(true);
// 設定禁止旋轉
mGestureCropImageView.setRotateEnabled(false);
// 設定剪切後的最大寬度
// mGestureCropImageView.setMaxResultImageSizeX(300);
// 設定剪切後的最大高度
// mGestureCropImageView.setMaxResultImageSizeY(300);
// 設定外部陰影顔色
mOverlayView.setDimmedColor(Color.parseColor("#AA000000"));
// 設定周圍陰影是否為橢圓(如果false則為矩形)
mOverlayView.setOvalDimmedLayer(true);
// 設定顯示裁剪邊框
mOverlayView.setShowCropFrame(false);
// 設定不顯示裁剪網格
mOverlayView.setShowCropGrid(false);
}
c. 儲存圖檔比較簡單,這篇博文點選合理中說的比較亂,是以我根據說明,提出了兩個方法
/**
* 僅把圖檔儲存在系統圖庫中
* @param context
* @param bmp
*/
public void saveImgToSystem(Context context, Bitmap bmp){
// 直接把圖檔插入到系統圖庫,不儲存在本地自己建立的檔案夾中,避免産生兩張一樣的圖檔
MediaStore.Images.Media.insertImage(context.getContentResolver(), bmp, "title", "description");
//發送廣播,提醒重新整理
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File("/"+ Environment.getExternalStorageDirectory()+"/image.jpg"))));
}
/**
* 既儲存在系統圖庫中也儲存在自己建立的圖庫檔案夾中
* @param context
* @param bmp
*/
public void saveImgToDouble(Context context, Bitmap bmp){
// 首先儲存圖檔
File appDir = new File(Environment.getExternalStorageDirectory(), "hehe");
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, , fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 其次把檔案插入到系統圖庫
try {
MediaStore.Images.Media.insertImage(context.getContentResolver(),
file.getAbsolutePath(), fileName, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 最後通知圖庫更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + file.getPath())));
}
3 源碼
源碼位址