趁着国庆有时间我们来聊一聊最常用的选取用户图片一系列的功能,go!
1.图片的选取
拍照
我们之前设置拍照保存的文件地址的Uri,都是直接Intent.putExtra(MediaStore.EXTRA_OUTPUT,文件保存的Uri路径),但是7.0之后,对用户权限提高了保护,之前那种方式行不通了,所以我们要做7.0的判断,用FileProvider获取设置保存的文件Uri,然后放到Intent.putExtra(MediaStore.EXTRA_OUTPUT,文件保存的Uri路径)中,代码如下:
//相机拍照的一个标识,后面用
TAKEPAHTO = 1;
// 启动系统相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//7.0以下设置保存图片的地址
Uri norTakePhotoSaveAdr;
// 判断7.0android系统
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//临时添加一个拍照权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// //通过FileProvider获取保存图片file的uri(先创建file,然后获取File的Uri)
takePhotoSaveAdr = FileProvider.getUriForFile(MainActivity.this,
"com.hxzk.bj.photodemo", new File(Environment.getExternalStorageDirectory(), "savephoto.webp"));
//MediaStore.EXTRA_OUTPUT-此设置需要一个保存视频的路径和文件名的Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, takePhotoSaveAdr);
} else {
norTakePhotoSaveAdr = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "savephoto.webp"));
intent.putExtra(MediaStore.EXTRA_OUTPUT, norTakePhotoSaveAdr);
}
//PHOTO_TAKEPHOTO,相机的一个请求码,返回时要用
startActivityForResult(intent, PHOTO_TAKEPHOTO);
相册
相比较拍照,相册要简单一点,代码中都有注释,直接看:
//拍照的一个表示
TAKEPAHTO = 0;
//调用系统图库,选择图片
//Intent.ACTION_PICK 意思是选择数据,其具体表达有:
// Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
//intent.setType("image/*"); 获取本地图片
// intent.setType("video/*"); 获取本地视频
//intent.setType("audio/*") 获取本地音乐
// Intent intent = new Intent(Intent.ACTION_PICK);
// intent.setType(ContactsContract.Contacts.CONTENT_TYPE); //获取联系人
// startActivityForResult(intent, PICK_CONTACT);
//第二种写法
Intent intent = new Intent(Intent.ACTION_PICK, null);
//其中External为sdcard下的多媒体文件,Internal为system下的多媒体文件。
//使用INTERNAL_CONTENT_URI只能显示存储在内部的照片
intent.setDataAndType(
MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
//返回结果和标识
startActivityForResult(intent, PHOTO_PHOTOALBUM);
.拍照或选取图片的返回结果
不论是拍照还是选取图片,我们都要在Activity的onActivityResult中去获取返回结果,然后进行下一步操作。这里提一下,我们在跳转相册或拍照都有一个requestCode,如图:
startActivityForResult(intent, PHOTO_PHOTOALBUM);
这个,我们全局定义了三个,分别是拍照,相册,裁剪的标识:
//三个常量全局标识
//图库
private static final int PHOTO_PHOTOALBUM = 0;
//拍照
private static final int PHOTO_TAKEPHOTO = 1;
//裁剪
private static final int PHOTO_PHOTOCLIP = 2;
在onActivityResult中就要用来区分了:
没啥说的!
2.%20图片的裁剪和压缩
图片的裁剪(减小了图片在手机中文件的大小(因为裁剪只是部分嘛),比如1m变为128kb)
图片的裁剪我们主要看一下starPhotoZoom()这个裁剪方法,代码如下:
%20public%20void%20startPhotoZoom(Uri%20uri)%20{
%20Log.e("uri=====",%20""%20+%20uri);
%20//com.android.camera.action.CROP,这个action是调用系统自带的图片裁切功能
%20Intent%20intent%20=%20new%20Intent("com.android.camera.action.CROP");
%20intent.setDataAndType(uri,%20"image/*");//裁剪的图片uri和图片类型
%20intent.putExtra("crop",%20"true");//设置允许裁剪,如果不设置,就会跳过裁剪的过程,还可以设置putExtra("crop",%20"circle")
%20intent.putExtra("aspectX",%201);//裁剪框的%20X%20方向的比例,需要为整数
%20intent.putExtra("aspectY",%201);//裁剪框的%20Y%20方向的比例,需要为整数
%20intent.putExtra("outputX",%2060);//返回数据的时候的X像素大小。
%20intent.putExtra("outputY",%2060);//返回数据的时候的Y像素大小。
%20//uritempFile为Uri类变量,实例化uritempFile
%20if%20(Build.VERSION.SDK_INT%20>=%20Build.VERSION_CODES.N)%20{
%20if%20(TAKEPAHTO%20==%201)%20{//如果是7.0的拍照
%20//开启临时访问的读和写权限
%20intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION%20|%20Intent.FLAG_GRANT_READ_URI_PERMISSION);
%20//针对7.0以上的操作
%20intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT,%20uri));
%20uriClipUri%20=%20uri;
%20}%20else%20{//如果是7.0的相册
%20//设置裁剪的图片地址Uri
%20uriClipUri%20=%20Uri.parse("file://"%20+%20"/"%20+%20Environment.getExternalStorageDirectory().getPath()%20+%20"/"%20+%20"clip.webp");
%20}
%20}%20else%20{
%20uriClipUri%20=%20Uri.parse("file://"%20+%20"/"%20+%20Environment.getExternalStorageDirectory().getPath()%20+%20"/"%20+%20"clip.webp");
%20}
%20Log.e("uriClipUri=====",%20""%20+%20uriClipUri);
%20//Android%20对Intent中所包含数据的大小是有限制的,一般不能超过%201M,否则会使用缩略图%20,所以我们要指定输出裁剪的图片路径
%20intent.putExtra(MediaStore.EXTRA_OUTPUT,%20uriClipUri);
%20intent.putExtra("return-data",%20false);//是否将数据保留在Bitmap中返回
%20intent.putExtra("outputFormat",%20Bitmap.CompressFormat.JPEG.toString());//输出格式,一般设为Bitmap格式及图片类型
intent.putExtra("noFaceDetection", true);//人脸识别功能
startActivityForResult(intent, PHOTO_PHOTOCLIP);//裁剪完成的标识
}
图片的压缩(减小了图片加载时bitmap的大小,避免oom,但图片本身在手机中大小没有变)
我直接上方法吧:
/**
* 图片压缩的方法(只是内存减少,避免oom,图片本身在disk盘体积不变)
* 显示的Bitmap占用的内存少一点,还是需要去设置加载的像素长度和宽度(变成缩略图)
*/
public void compressPhto(File mFile){
// BitmapFactory这个类就提供了多个解析方法(decodeResource、decodeStream、decodeFile等)用于创建Bitmap。
// 比如如果图片来源于网络,就可以使用decodeStream方法;
// 如果是sd卡里面的图片,就可以选择decodeFile方法;
// 如果是资源文件里面的图片,就可以使用decodeResource方法等
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 获取当前图片的边界大小
//BitmapFactory.decodeResource(getResources(), R.drawable.bg, options);
BitmapFactory.decodeFile(mFile.getAbsolutePath(),options);
int outHeight = options.outHeight; //获取图片本身的高像素
int outWidth = options.outWidth;//获取图片本身的宽的像素
String outMimeType = options.outMimeType;
options.inJustDecodeBounds = false;
//inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方
//对于inSampleSize值的大小有要求,最好是整数且2的倍数
options.inSampleSize = caculateSampleSize(options, 500, 500);
//etPath()得到的是构造file的时候的路径。getAbsolutePath()得到的是全路径
String path =mFile.getPath();
String absPath=mFile.getAbsolutePath();
Bitmap bitmap = BitmapFactory.decodeFile(absPath,options);
ivUserPhoto.setImageBitmap(bitmap);
//尺寸压缩结果
ivSize.setImageBitmap(bitmap);
}
/**
* 计算出所需要压缩的大小
* @param options
* @param reqWidth 希望的图片宽大小
* @param reqHeight 希望的图片高大小
* @return
*/
private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int sampleSize = 1;
int picWidth = options.outWidth;
int picHeight = options.outHeight;
if (picWidth > reqWidth || picHeight > reqHeight) {
int halfPicWidth = picWidth / 2;
int halfPicHeight = picHeight / 2;
while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
sampleSize *= 2;
}
}
return sampleSize;
}
3.图片的适配
7.0适配问题
不知道,大家注意裁剪方法里的这一段代码没?
还有拍照的这一段代码:
为什么要这么写呢?
%20原来7.0一下版本我们,直接调用相机获取的图片地址是:
%20file:///storage/emulated/0/temp.webp的文件
%20然而7.0之后就变成:
%20content://........文件,使用%20content://代替了%20file:///
%20这是因为:Android%20为了提高私有文件的安全性,从%207.0%20开始对外传递file://类型的uri会触发FileUriExposedException。因此,在分享私有文件时必须使用FileProvider。
%20那么如果在使用之前的方法就会报错,我们要给程序在manifest文件中加入FileProvider:
%20我们看看provider_paths这个文件中都有啥?
这注解写的,不会,就转行吧。
裁剪完成
直接加载图片显示
4.图片的上传
上传图片
%20但还有中情况是我们要上传加载的图片,我也给大家提供了方法:
Bitmap%20photoBitmap;
File%20file;
/**
%20*%20上传图片
%20*/
public%20void%20upDateFile()%20{
%20try%20{
%20//裁剪后的图像转成BitMap
%20photoBitmap%20=%20BitmapFactory.decodeStream(getContentResolver().openInputStream(uriClipUri));
%20}%20catch%20(FileNotFoundException%20e)%20{
%20e.printStackTrace();
%20}
%20//创建路径
%20String%20path%20=%20Environment.getExternalStorageDirectory()
%20.getPath()%20+%20"/Pic";
%20//获取外部储存目录
%20file%20=%20new%20File(path);
%20//创建新目录,%20创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。
%20file.mkdirs();
%20//以当前时间重新命名文件
%20long%20time%20=%20System.currentTimeMillis();
%20//生成新的文件
%20file%20=%20new%20File(file.toString()%20+%20"/"%20+%20time+%20".webp");
%20//创建输出流
%20OutputStream%20out%20=%20null;
%20try%20{
%20out%20=%20new%20FileOutputStream(file.getPath());
%20}%20catch%20(FileNotFoundException%20e)%20{
%20e.printStackTrace();
%20}
%20//压缩文件,返回结果,true说明成功了
%20boolean%20bCompress%20=%20photoBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
}
5.其他知识点
- 点击用户头像弹窗不是我们常用的PupUpWindow,而是自定义的BottomSheetDialog(待优化).
- 用户头像被我做成了圆角,这个也是自定义ImageView,大家可以下载查看源码。
暂时这么多,以后补充!
6.结尾
学无止境,让学习成为一种享受。
最后放上关于Android前沿技术方面的学习大纲及详细的图片加载框架设计方面的内容,需要更加详细的关注+私信回复“安卓资料”。
以上学习方向我们有自己的高清思维方向导图,架构师有自己讲解的架构视频分享(包括高级UI、性能优化、架构师课程、 NDK、混合式开发:ReactNative+Weex等多个Android技术知识的架构视频资料和各种电子书籍阅读),视频资料获取方式:关注+转发+私信回复“安卓资料”免费获取!