欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

程序员文章站 2024-02-02 12:34:52
...

1.写在前面

一、 最近笔者在学习写一个小项目的时候,学习到了圆形头像的设置,实现相机、相册选择并裁剪,并且可以到SharedPreferences中去执行存取操作,感觉代码量还是有的,使用起来不是很方便,然后自己进行了改进和封装,实现打开相机、相册选择并裁剪只需要【一行代码】,用onActivityResult()接受返回的数据也只需要【一行代码】,完成整个功能也只需要非常简单的几步而已;并且到SharedPreferences中去执行存取操作也只需要【一行代码】,这样一来使用起来就非常简单方便,因此在这分享给小伙伴们 O(∩_∩)O

二、 不仅如此,在自己实际操作中还遇到了Android 6.0、7.0权限的问题,此处进行了解决,这样就谐和的进行了多机型的适配了。

PS:本博客的中写到【CircleImageUtils】的封装是以Fragment作为父容器进行编写的。

2.预览成果

1.效果图如下所示:

(1)弹出PopWindow       (2)打开相机拍照         (3)打开相册获取图片
超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

2.落实:实现相机、相册选择并裁剪仅需【一行代码】,用onActivityResult()接受返回的数据也只需要【一行代码】完成完整的功能也仅需要简单步骤而已。

1.实现打开相机、相册选择并裁剪:

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

2.用onActivityResult()接受返回的数据,并显示在CircleImageView上

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

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>

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

(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()打开相机的方法中加了两个判断:

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

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()裁剪图片的方法中加了一个判断:

超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)

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