超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)
1.写在前面
一、 最近笔者在学习写一个小项目的时候,学习到了圆形头像的设置,实现相机、相册选择并裁剪,并且可以到SharedPreferences中去执行存取操作,感觉代码量还是有的,使用起来不是很方便,然后自己进行了改进和封装,实现打开相机、相册选择并裁剪只需要【一行代码】,用onActivityResult()接受返回的数据也只需要【一行代码】,完成整个功能也只需要非常简单的几步而已;并且到SharedPreferences中去执行存取操作也只需要【一行代码】,这样一来使用起来就非常简单方便,因此在这分享给小伙伴们 O(∩_∩)O
二、 不仅如此,在自己实际操作中还遇到了Android 6.0、7.0权限的问题,此处进行了解决,这样就谐和的进行了多机型的适配了。
PS:本博客的中写到【CircleImageUtils】的封装是以Fragment作为父容器进行编写的。
2.预览成果
1.效果图如下所示:
(1)弹出PopWindow (2)打开相机拍照 (3)打开相册获取图片
2.落实:实现相机、相册选择并裁剪仅需【一行代码】,用onActivityResult()接受返回的数据也只需要【一行代码】完成完整的功能也仅需要简单步骤而已。
1.实现打开相机、相册选择并裁剪:
2.用onActivityResult()接受返回的数据,并显示在CircleImageView上
3.完成整个功能的步骤:
- 打开相机或者相册
- 用onActivityResult()接受返回的头像,并用CircleImageView显示
- 把头像存储到本地或者上传到服务器上
- 初始化数据的时候,从本地或者服务器上获取存放的头像并显示
3.本章核心(封装源码+使用方法)
1.( 实现相机、相册选择并裁剪 )+( 到SharedPreferences中去存取操作 )两大功能的【封装】的代码如下:
【PS】:把它丢到自己的工具类里面就好了,这里用了自己封装的SharedPreferences,如果自己封装过了,把博主的替换掉了就好,如果没有 ,可以下载本案例的Demo到里面去Copy一下)
/**
* 项目名 : PracticalDemo
* 包名 : cn.dragoliu.practicaldemo.utils
* 文件名 : CircleImageUtils
* 创建者 : Kyle
* 创建时间 : 2018-01-26
* 描述 : 圆形头像工具类
*/
public class CircleImageUtils {
//圆形头像文件的名字
public static final String PHONE_IMAGE_FILE_NAME = "ImgHeadShort.jpg";
//打开相机的请求码
public static final int CAMERA_REQUEST_CODE = 1000;
//打开相册的请求码
public static final int IMAGE_REQUEST_CODE = 1001;
//返回结果的请求码
public static final int RESULT_REQUEST_CODE = 1002;
//圆形头像文件
private static File tempFile = null;
//你要打开的文件的绝对路径
private static Uri imageUri = null;
/**
* 【1.1】用【Base64】把【压缩(圆形头像转化的Bitmap)后得到的(字节数组输出流)】转化为【String字符串】
*
* @param imageView 需要转化为String字符串的圆形头像
* @return 返回String字符串
*/
public static String getCircleImageString(ImageView imageView) {
//第一步:保存为Drawable对象,并转化为bitmap位图
BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
Bitmap bitmap = drawable.getBitmap();
//第二步:将Bitmap压缩成字节数组输出流
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 80, byteStream);
//第三步:利用base64将我们的字节数组输出流转换成String
byte[] bytes = byteStream.toByteArray();
String imageString = new String(Base64.encodeToString(bytes, Base64.DEFAULT));
return imageString;
}
/**
* 【1.2】用Base64把【Bitmap压缩后的字节数组输出流】转化为【String字符串】,
* 并保存到SharedPreferences中去
*
* @param context 上下文
* @param imageView 需要保存到SharedPreferences中去的圆形头像
*/
public static void putCircleImageToShare(Context context, ImageView imageView) {
String imageString = getCircleImageString(imageView);
SPUtils.putString(context, "image_title", imageString);
}
/**
* 【2.1】拿到Base64加密过【String字符串】转化为【字节数组输出流】,最后生成【Bitmap】位图
*
* @param base64Code Base64加密过String字符串
* @return 圆形头像的bitmap位图
*/
public static Bitmap getCircleImageBitmap(String base64Code) {
if (base64Code != null) {
//第一步:利用base64将我们的String转换成字节数组输出流
byte[] bytes = Base64.decode(base64Code, Base64.DEFAULT);
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
//第二步:生成Bitmap
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
return null;
}
/**
* 【2.2】从SharedPreferences拿到Base64加密过【String字符串】并转化为【字节数组输出流】,
* 最后生成【Bitmap】位图,来显示图片
*
* @param context 上下文
* @return 返回圆形头像的Bitmap位图
*/
public static Bitmap getCircleImageFromShare(Context context) {
String imageString = SPUtils.getString(context, "image_title", null);
Bitmap bitmap = getCircleImageBitmap(imageString);
return bitmap;
}
/**
* 打开相机
*
* @param fragment 当前调用【打开相机】功能的fragment
*/
public static void openCamera(Fragment fragment) {
tempFile = new File(Environment.getExternalStorageDirectory(), PHONE_IMAGE_FILE_NAME);
imageUri = Uri.fromFile(tempFile);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
fragment.startActivityForResult(intent, CAMERA_REQUEST_CODE);
}
/**
* 打开相册
*
* @param fragment 当前调用【打开相册】功能的fragment
*/
public static void openPicture(Fragment fragment) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
fragment.startActivityForResult(intent, IMAGE_REQUEST_CODE);
}
/**
* 对相机拍摄后的照片/相册选择的照片进行操作
*
* @param fragment fragment
* @param requestCode 返回的请求码
* @param resultCode 返回的结果码
* @param data 返回的数据
* @param activity 当前调用【打开相册】功能的fragment所在的activity
* @return
*/
public static Bitmap operateActivityResult(Fragment fragment, int requestCode, int resultCode, Intent data, Activity activity) {
if (resultCode != activity.RESULT_CANCELED) {
switch (requestCode) {
//请求相机
case CAMERA_REQUEST_CODE:
tempFile = new File(Environment.getExternalStorageDirectory(), PHONE_IMAGE_FILE_NAME);
startPhotoZoom(fragment, imageUri);
break;
//请求相册
case IMAGE_REQUEST_CODE:
startPhotoZoom(fragment, data.getData());
break;
//接受的返回的结果
case RESULT_REQUEST_CODE:
//有可能选择取消,就没有获取到图片,不能进行裁剪哦
if (data != null) {
//如果原来设置过了图片的话,我们应该把原来的删除
if (tempFile != null) {
tempFile.delete();
}
//拿到圆形头像的bitmap位图
Bitmap bitmap = setImageToView(data);
return bitmap;
} else {
return null;
}
}
}
return null;
}
/**
* 裁剪图片
*
* @param fragment 具体调用此功能的fragment
* @param uri 圆形头像资源的uri
*/
private static void startPhotoZoom(Fragment fragment, Uri uri) {
if (uri == null) {
return;
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
//设置裁剪
intent.putExtra("crop", "true");
//裁剪宽高(比例)
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//设置裁剪图片的质量
intent.putExtra("outputX", 320);
intent.putExtra("outputY", 320);
intent.putExtra("return-data", true);//发送数据
fragment.startActivityForResult(intent, RESULT_REQUEST_CODE);
}
/**
* 把裁剪后的圆形头像显示在ImageView中去
*
* @param data 返回的数据
* @return 返回圆形头像的bitmap位图
*/
private static Bitmap setImageToView(Intent data) {
Bundle bundle = data.getExtras();
if (bundle != null) {
Bitmap bitmap = bundle.getParcelable("data");
return bitmap;
}
return null;
}
}
【Ps】:到此,最精华的部分已经分享完毕,接下来就是使用它的时候啦,价格会十分的清爽 O(∩_∩)O
2.打开相机或打开相册并裁剪功能使用:
具体思路:只要在弹出的PopWindow中对应功能的点击事件中调用【CircleImageUtils类】封装好的【openCamera()】方法或者【openCamera()】,传入一个【上下文】就好了。
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_camera:
//打开相机
CircleImageUtils.openCamera(this);
//隐藏dialog
dialog.dismiss();
break;
case R.id.btn_picture:
//打开相册
CircleImageUtils.openPicture(this);
//隐藏dialog
dialog.dismiss();
break;
case R.id.btn_cancel:
//隐藏dialog
dialog.dismiss();
break;
}
}
3.用onActivityResult()接受返回的头像,并用CircleImageView显示,然后把头像存储到本地或者上传到服务器上:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//得到你相机或相册获取并裁剪的后图片
Bitmap bitmap = CircleImageUtils.operateActivityResult(this,
requestCode,
resultCode,
data,
getActivity());
//设置圆形头像
if (bitmap != null) {
imgMineHeadShot.setImageBitmap(bitmap);
}
//保存头像到SharedPreferences
CircleImageUtils.putCircleImageToShare(getActivity(),imgMineHeadShot);
}
PS:CircleImageUtils.operateActivityResult()使用时传入的参数就多了两个上下文,一个是自身Fragment的上下文,另一个是盛装它的父Activity的上下午,剩下的就是onActivityResult()本身的requestCode,resultCode和data。
4.从本地或者服务器上获取存放的头像并显示:
【PS】:此处显示从本地获取。
private void initData() {
//从SharedPreferences获取到圆形头像
Bitmap bitmap = CircleImageUtils.getCircleImageFromShare(getActivity());
//设置圆形头像
if (bitmap != null) {
imgMineHeadShot.setImageBitmap(bitmap);
}
}
5.扩展 —— 圆形头像类的使用:
github地址为:https://github.com/hdodenhof/CircleImageView
(1)在Gradle中添加依赖
dependencies {
...
compile 'de.hdodenhof:circleimageview:2.2.0'
}
(2)在布局文件中像普通的组件一样使用就好了,这样头像就是圆的啦:
<de.hdodenhof.circleimageview.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/profile_image"
android:layout_width="96dp"
android:layout_height="96dp"
android:src="@drawable/profile"
app:civ_border_width="2dp"
app:civ_border_color="#FF000000"/>
4.兼容Android 7.0
前言:
Android 7.0系统权限进行了修改,是为了提高私有文件的安全性,此设置可以防止私有文件的元数据泄露,如它的大小或者存在性。但此权限更改是有多重副作用的,其中一个就是传递软件包外的file://URI可能给接收器留下无法访问的路径。因此尝试传递file://URI会触发FileUriException。分享私有文件内容的推荐方法是使用FileProvider。待会就要用到。
(1)AndroidManifest.xml 增加provider定义
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="cn.dragoliu.practicaldemo.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
(2) 配置XML文件
【PS】:在res下创建xml文件夹,并创建filepaths.xml文件,名字可以自定义,只要和provider中的android:resource值相同就好。
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="files_root"
path="Android/data/cn.dragoliu.practicaldemo/" />
<external-path
name="camera_photos"
path="." />
</paths>
path:需要临时授权访问的路径(.代表所有路径)
name:就是你给这个访问路径起个名字
(3)更改一点点封装的源码中的openCamera()打开相机的方法:
就在openCamera()打开相机的方法中加了两个判断:
openCamera()打开相机的方法更改后完整代码如下:
/**
* 打开相机
*
* @param fragment 当前调用【打开相机】功能的fragment
*/
public static void openCamera(Fragment fragment) {
tempFile = new File(Environment.getExternalStorageDirectory(), PHONE_IMAGE_FILE_NAME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(fragment.getActivity(),
BuildConfig.APPLICATION_ID + ".fileProvider", tempFile);
} else {
imageUri = Uri.fromFile(tempFile);
}
Intent intent = new Intent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
fragment.startActivityForResult(intent, CAMERA_REQUEST_CODE);
}
(4)更改一点点封装的源码中startPhotoZoom()裁剪图片的方法:
就在startPhotoZoom()裁剪图片的方法中加了一个判断:
就在startPhotoZoom()裁剪图片的方法更改后完整代码如下:
/**
* 裁剪图片
*
* @param fragment 具体调用此功能的fragment
* @param uri 圆形头像资源的uri
*/
private static void startPhotoZoom(Fragment fragment, Uri uri) {
if (uri == null) {
return;
}
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(uri, "image/*");
//设置裁剪
intent.putExtra("crop", "true");
//裁剪宽高(比例)
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//设置裁剪图片的质量
intent.putExtra("outputX", 320);
intent.putExtra("outputY", 320);
intent.putExtra("return-data", true);//发送数据
fragment.startActivityForResult(intent, RESULT_REQUEST_CODE);
}
本章小结
这篇博客到此就结束了,创作此篇的目的也算是达到了,使用起来确实省事了不少,不仅能锻炼封装的思想,并且温故而知新,学到了许多新的东西,像之前学的时候还碰不到权限问题呢,总之希望通过分享它能够给需要的小伙伴一点点帮助,当然由于经验不足,还有很多不足之处,如果有不满意的地方,望请指正 ,不胜感激O(∩_∩)O
Demo链接
下载地址为:超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间
上一篇: 18轮廓-绘制方形,原型
下一篇: Android 圆形头像的两种实现方式