Android 头像选择(拍照相册裁剪),含7.0的坑

Posted 夏至的稻穗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 头像选择(拍照相册裁剪),含7.0的坑相关的知识,希望对你有一定的参考价值。

作者:夏至,欢迎转载,但请保留这段申明,谢谢。
http://blog.csdn.net/u011418943/article/details/77712662

首先,好规则,看看自己的实现效果:

当然,这个github 各种开源库,这里只讲 android 自带的功能。

其实这个也不难,关键点无非就2个:

  • 7.0 之后相机的 uri 获取
  • 裁剪时的 uri 获取

这里可以放一下底部 popupwindow 的布局,另外可以看我的 popupwindow封装:
拒绝无用功,封装一个通用的 PopupWindow

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/pop_root_ly"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:orientation="vertical">
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="5dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/pop_pic"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:gravity="center"
                android:text="@string/pic"
                android:textColor="@color/black"
                android:textSize="18sp" />
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="?android:attr/listDivider"
                android:padding="2dp" />
            <TextView
                android:id="@+id/pop_camera"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:gravity="center"
                android:text="@string/camera"
                android:textColor="@color/black"
                android:textSize="18sp" />
        </LinearLayout>
    </android.support.v7.widget.CardView>
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/pop_cancel"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center"
            android:text="取消"
            android:textColor="@color/black"
            android:textSize="18sp" />
    </android.support.v7.widget.CardView>
</LinearLayout>

而头像,我用的时开源框架,circleiamgeview,这个用来处理圆形头像就可以了,这里就不贴出来了,链接如下:

compile 'de.hdodenhof:circleimageview:2.1.0'

其中点击事件如下:
图片:

/*Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image*//**//*");
startActivityForResult(intent,ToolUtils.SCAN_OPEN_PHONE);*/
   //由于模拟器图库的刷新问题,采用如下打开方式,实际开发请采用上面这种
    Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, ToolUtils.SCAN_OPEN_PHONE);

相机:

 /**
     * 打开相册
     */
    private void cameraPic() 
        //创建一个file,用来存储拍照后的照片
        File outputfile = new File(mActivity.getExternalCacheDir(),"output.png");
        try 
            if (outputfile.exists())
                outputfile.delete();//删除
            
            outputfile.createNewFile();
         catch (Exception e) 
            e.printStackTrace();
        
        Uri imageuri ;
        if (Build.VERSION.SDK_INT >= 24)
            imageuri = FileProvider.getUriForFile(mActivity,
                    "com.rachel.studyapp.fileprovider", //可以是任意字符串
                    outputfile);
        else
            imageuri = Uri.fromFile(outputfile);
        
        //启动相机程序
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT,imageuri);
        startActivityForResult(intent,ToolUtils.PHONE_CAMERA);
    

在拍照这里,7.0 是需要做处理的,google 在7.0 之后,考虑到安全性的问题,用 fileProvider 来封装 uri了,所以,这里我们也处理一下,mCameraUri 是用来保存拍照后,照片的 uri,可以简单理解成该图片的索引。

注意!!!如果是6.0的手机,请添加权限申请,我这里已经封装好,就不贴出来干扰大家了。

既然上面填写了 fileprovider 了,那么用过content provider的都知道,我们得配置 provider 属性了,如下,在你的 Androidmanifest.xml 下添加:

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.rachel.studyapp.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <!--提供共享路径-->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

上面得 authorities 必须跟你刚才在 getUriForFile 得authorities 一致,exported 这里填写 false,不然又得来权限问题了,grantUriPermissions 表示授予临时访问,然后再在下面添加一个 mete-data 标签,用来提供 fileprovider 的共享路径。
file_paths 名字随便取,然后在新建一个 xml 文件夹,席间 file_paths 文件填写如下代码:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <external-path path="" name="camera_photos" />
    </paths>
</resources>

解释如下:

  • external-path 表示用来指定共享路径的
  • name 随便取,只是一个标签,买个关子,下面会解释
  • path 这个比较重要,如果不设置,则表示将整个 SD 卡进行共享,然后制定了,比如 path=”Pictrues”那么,就只共享 sd卡下的 Pictures 文件夹

既然都配置好了,我们把上面的 mCameraUri 打印一下,如下所示:

cameraPic: content://com.rachel.studyapp.fileprovider/camera_photos/Android/data/com.rachel.studyapp/cache/output.png

看到这个 camera_photos 了吧,其实它就是一个 虚拟目录,可以不管的。这个就是 7.0 之后 uri 经过封装后的样子。

拍照之后,就是要处理获取的图片了,在 onActivityReslut 中的处理也非常简单,既然不管是从相册,还是从相册,我们都获取到了正确的 uri,那么,在获取到 这个 uri 之后,启动裁剪的 activity 就可以了,如下:

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK)
            switch (requestCode)
                case ToolUtils.SCAN_OPEN_PHONE: //从相册图片后返回的uri
                    //启动裁剪
                    startActivityForResult(CutForPhoto(data.getData()),ToolUtils.PHONE_CROP);
                    break;
                case ToolUtils.PHONE_CAMERA: //相机返回的 uri
                    //启动裁剪
                    String path = mActivity.getExternalCacheDir().getPath();
                    String name = "output.png";
                    startActivityForResult(CutForCamera(path,name),ToolUtils.PHONE_CROP);
                    break;
                case ToolUtils.PHONE_CROP:
                    try 
                        //获取裁剪后的图片,并显示出来
                        Bitmap bitmap = BitmapFactory.decodeStream(
                                mActivity.getContentResolver().openInputStream(mCutUri));
                        mUserLogoIcon.setImageBitmap(bitmap);
                     catch (FileNotFoundException e) 
                        e.printStackTrace();
                    
                    break;
            
        
    

可以看到,裁剪分为两个,一个是从相册中获取,另一个则是拍照之后,再来裁剪的。不一样的地方,往下看:
相册裁剪:

/**
     * 图片裁剪
     * @param uri
     * @return
     */
    @NonNull
    private Intent CutForPhoto(Uri uri) 
        try 
            //直接裁剪
            Intent intent = new Intent("com.android.camera.action.CROP");
            //设置裁剪之后的图片路径文件
            File cutfile = new File(Environment.getExternalStorageDirectory().getPath(),
                    "cutcamera.png"); //随便命名一个
            if (cutfile.exists()) //如果已经存在,则先删除,这里应该是上传到服务器,然后再删除本地的,没服务器,只能这样了
                cutfile.delete();
            
            cutfile.createNewFile();
            //初始化 uri
            Uri imageUri = uri; //返回来的 uri
            Uri outputUri = null; //真实的 uri
            Log.d(TAG, "CutForPhoto: "+cutfile);
            outputUri = Uri.fromFile(cutfile);
            mCutUri = outputUri;
            Log.d(TAG, "mCameraUri: "+mCutUri);
            // crop为true是设置在开启的intent中设置显示的view可以剪裁
            intent.putExtra("crop",true);
            // aspectX,aspectY 是宽高的比例,这里设置正方形
            intent.putExtra("aspectX",1);
            intent.putExtra("aspectY",1);
            //设置要裁剪的宽高
            intent.putExtra("outputX", ToolUtils.dip2px(mActivity,200)); //200dp
            intent.putExtra("outputY",ToolUtils.dip2px(mActivity,200));
            intent.putExtra("scale",true);
            //如果图片过大,会导致oom,这里设置为false
            intent.putExtra("return-data",false);
            if (imageUri != null) 
                intent.setDataAndType(imageUri, "image/*");
            
            if (outputUri != null) 
                intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
            
            intent.putExtra("noFaceDetection", true);
            //压缩图片
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            return intent;
         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    

相机裁剪:

 /**
     * 拍照之后,启动裁剪
     * @param camerapath 路径
     * @param imgname img 的名字
     * @return
     */
    @NonNull
    private Intent CutForCamera(String camerapath,String imgname) 
        try 

            //设置裁剪之后的图片路径文件
            File cutfile = new File(Environment.getExternalStorageDirectory().getPath(),
                    "cutcamera.png"); //随便命名一个
            if (cutfile.exists()) //如果已经存在,则先删除,这里应该是上传到服务器,然后再删除本地的,没服务器,只能这样了
                cutfile.delete();
            
            cutfile.createNewFile();
            //初始化 uri
            Uri imageUri = null; //返回来的 uri
            Uri outputUri = null; //真实的 uri
            Intent intent = new Intent("com.android.camera.action.CROP");
            //拍照留下的图片
            File camerafile = new File(camerapath,imgname);
            if (Build.VERSION.SDK_INT >= 24) 
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                imageUri = FileProvider.getUriForFile(mActivity,
                        "com.rachel.studyapp.fileprovider",
                        camerafile);
             else 
                imageUri = Uri.fromFile(camerafile);
            
            outputUri = Uri.fromFile(cutfile);
            //把这个 uri 提供出去,就可以解析成 bitmap了
            mCutUri = outputUri;
            // crop为true是设置在开启的intent中设置显示的view可以剪裁
            intent.putExtra("crop",true);
            // aspectX,aspectY 是宽高的比例,这里设置正方形
            intent.putExtra("aspectX",1);
            intent.putExtra("aspectY",1);
            //设置要裁剪的宽高
            intent.putExtra("outputX", ToolUtils.dip2px(mActivity,200));
            intent.putExtra("outputY",ToolUtils.dip2px(mActivity,200));
            intent.putExtra("scale",true);
            //如果图片过大,会导致oom,这里设置为false
            intent.putExtra("return-data",false);
            if (imageUri != null) 
                intent.setDataAndType(imageUri, "image/*");
            
            if (outputUri != null) 
                intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
            
            intent.putExtra("noFaceDetection", true);
            //压缩图片
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            return intent;
         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    

上面两个,相似度极高,不同的是,相册是直接获取 uri,而相机,则是通过拍照之后,获取的 uri,你这里可以合并一下,只是参数不一样而已,这里我为了让大家有个对比的效果,就分开出来了。

上面一个,注意到用了 intent.putExtra(“return-data”,false);如果这里设置为 true,那么,返回是就是一个 btimap,但这样做在内存不足的情况下,会报 OOM 的,所以,像这种大图片的,我们一般是在本地生成一个裁剪之后的图片,然后获取到这个裁剪之后的 uri,再解析出来,才是正确的做法,所以这里设置为false,并用 mcuturi 来作为这个本地存储的 uri。

这样就讲完了。
这里推荐一下,github,我觉得还不错的两个裁剪开源库,SmartCropper,链接如下:

https://github.com/pqpo/SmartCropper

TakePhoto :

https://github.com/crazycodeboy/TakePhoto/

以上是关于Android 头像选择(拍照相册裁剪),含7.0的坑的主要内容,如果未能解决你的问题,请参考以下文章

安卓开发——拍照裁剪并保存为头像报错:裁剪图片无法保存的

Android--利用相机或相册截取用户头像(解决了miui无法截取,以及部分机型拍照无返回Uri)

Android中通过访问本地相册或者相机设置用户头像

Android 拍照从相册获取及裁剪的相关实现

相册选择头像或者拍照 上传头像以NSData 图片二进制格式 表单上传

Android7.0调用系统相机拍照读取系统相册照片+CropImageView剪裁照片