超实用的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.完成整个功能的步骤:

  1. 打开相机或者相册
  2. 用onActivityResult()接受返回的头像,并用CircleImageView显示
  3. 把头像存储到本地或者上传到服务器上
  4. 初始化数据的时候,从本地或者服务器上获取存放的头像并显示

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圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值