前两天看了下别人保存图片的方式,又看了郭神的第一行代码的拍照和打开相册,又研究了下对图片的裁剪,然后合在一起做了个demo,以后要用直接用就可以了。先说说这个代码比网上其他的代码好的地方。
* 在打开摄像头拍照时兼容了7.0获取uri的方式;
* 打开相册通过uri获取bitmap时使用郭神的方法和框架提供的方法,查了下资料没发现这两种方法的区别以及对比,后面会说;
* 裁剪框架裁剪图片框架中减轻了很多项目中用不到的东西,等下看效果;
* 保存图片把前两天博客中的方法保存图片至相册提炼出来,提供了两种方法选择,只保存图片至系统图库和保存图片至自己连理的文件和系统图片都保存;
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>=24){
/**
* 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,100);
}
点击拍照完成返回结果的处理
// case 100: //拍照返回结果
//
// 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,200);
}
这里的代码主要注意一点
// startActivityForResult(Intent.createChooser(intent,"选择图片aaaa"),200);
那位大佬用的是这种,其实没有什么区别,就是我们在打开相册时那个提示语可以我们设定。对返回结果的处理,我们一般都是用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(19)
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(":")[1];
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, 100, 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())));
}
源码地址