安卓个人中心头像模块(从相册选择和照相功能,适配7.0)

**

开篇:

**
1.讲解Intent中的四个重要属性——Action、Data、Category、Extras
2.关于 Android 7.0 适配中 FileProvider 部分的总结
3.Environment.getExternalStorageState介绍
4.思路:
/*
*整理具体思路
* .获取权限
* .选择是拍照还是相册
* .返回uri
* .进行裁剪
* .显示头像
* 关键要能理清图片的路径
*/

运行效果:

安卓个人中心头像模块(从相册选择和照相功能,适配7.0)

导入依赖

    //glide 图片加载
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
    //动态权限申请库
    implementation 'pub.devrel:easypermissions:1.3.0'

代码部分


package com.ycb.baseicon.utils;

import android.os.Environment;

import java.io.File;
import java.util.UUID;


public class FileStorage {
    private File cropIconDir;
    private File iconDir;

    public FileStorage() {
        //MEDIA_MOUNTED  SD卡正常使用 TRUE TRUE TRUE TRUE TRUE
        //只有在SD卡状态为MEDIA_MOUNTED时/mnt/sdcard目录才是可读可写,并且可以创建目录及文件。
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            File external = Environment.getExternalStorageDirectory();//获取sd卡 路径
            String rootDir = "/" + "Base";
            cropIconDir = new File(external, rootDir + "/crop");
            if (!cropIconDir.exists()) { // 检测是否有这个目录存在
                cropIconDir.mkdirs();   //创建

            }
            iconDir = new File(external, rootDir + "/icon");
            if (!iconDir.exists()) {
                iconDir.mkdirs();

            }
        }
    }
    //裁剪存放目录
    public File createCropFile() {
        String fileName = "";
        if (cropIconDir != null) {
            //UUID  设备唯一标识码
            fileName = UUID.randomUUID().toString() + ".png";
        }
        return new File(cropIconDir, fileName);  // cropIconDir: File对象类型的目录路径    fileName:文件名或目录名。
    }

    //头像存放目录
    public File createIconFile() {
        String fileName = "";
        if (iconDir != null) {
            fileName = UUID.randomUUID().toString() + ".png";
        }
        return new File(iconDir, fileName);
    }

}

MainActivity(解释都写好了)

package com.ycb.baseicon;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.ycb.baseicon.PopupWindow.CommonPopupWindow;
import com.ycb.baseicon.PopupWindow.CommonUtil;
import com.ycb.baseicon.utils.FileStorage;
import com.ycb.baseicon.utils.PictureUtil;
import com.ycb.baseicon.utils.SPUtils;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends AppCompatActivity implements CommonPopupWindow.ViewInterface, EasyPermissions.PermissionCallbacks {
    private CommonPopupWindow commonPopupWindow;
    private ImageView mIcon;
    private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA};
    //Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示。换句话说:android系统中任何可用的资源(图像、视频、文件)
    // 都可以用uri表示。
    //uri讲解:
    //1.uri属性有以下4部分组成:android:scheme、android:host、android:port、android:path
    //其中host和port2个统称为authority。
    //2.要使authority(host和port)有意义,必须指定scheme;要使path有意义,必须使scheme和authority(host和port)有意义
    private Uri uri;
    private int type;
    Uri cropUri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mIcon = findViewById(R.id.icon);
        String path = SPUtils.getString(this, "icon", "");
        if (path != null) {
            loadCircleImage(this, path, mIcon);
        }
        mIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showAll(v);
            }
        });
    }

    //全屏弹出
    public void showAll(View view) {
        if (commonPopupWindow != null && commonPopupWindow.isShowing()) return;
        View upView = LayoutInflater.from(this).inflate(R.layout.popup_up, null);
        //测量View的宽高
        CommonUtil.measureWidthAndHeight(upView);
        commonPopupWindow = new CommonPopupWindow.Builder(this)
                .setView(R.layout.popup_up)
                .setWidthAndHeight(ViewGroup.LayoutParams.MATCH_PARENT, upView.getMeasuredHeight())
                .setBackGroundLevel(0.5f)//取值范围0.0f-1.0f 值越小越暗
                .setAnimationStyle(R.style.AnimUp)
                .setViewOnclickListener(this)
                .create();
        commonPopupWindow.showAtLocation(findViewById(android.R.id.content), Gravity.BOTTOM, 0, 0);
    }

    @Override
    public void getChildView(View view, int layoutResId) {
        switch (layoutResId) {
            case R.layout.popup_up:
                Button btn_take_photo = (Button) view.findViewById(R.id.btn_take_photo);
                Button btn_select_photo = (Button) view.findViewById(R.id.btn_select_photo);
                Button btn_cancel = (Button) view.findViewById(R.id.btn_cancel);
                btn_take_photo.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        type = 1;                 
                        getPermission();    //写个if  判断是不是在6.0以上版本   不是直接调用方法
                        if (commonPopupWindow != null) {
                            commonPopupWindow.dismiss();
                        }
                    }
                });
                btn_select_photo.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        type = 2;
                        getPermission();
                        if (commonPopupWindow != null) {
                            commonPopupWindow.dismiss();
                        }
                    }
                });
                btn_cancel.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (commonPopupWindow != null) {
                            commonPopupWindow.dismiss();
                        }
                    }
                });
                view.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        if (commonPopupWindow != null) {
                            commonPopupWindow.dismiss();
                        }
                        return true;
                    }
                });
                break;
        }
    }


    //获取权限
    public void getPermission() {
        //检测是否有权限
        if (EasyPermissions.hasPermissions(this, permissions)) {
            switch (type) {
                case 1:
                    getCamera();
                    break;
                case 2:
                    getPhotoAlbum();
                    break;
            }
        } else {
            EasyPermissions.requestPermissions(this, "用于读取相册和拍照功能", 1, permissions);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    //权限申请成功
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        switch (type) {
            case 1:
                getCamera();
                break;
            case 2:
                getPhotoAlbum();
                break;
        }
    }

    //权限申请失败时的回调
    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        showToast("请在应用管理里面对应用进行重新授权");
        finish();
    }
    
    /* intent 的重要属性
     * Action:Action属性的值为一个字符串,它代表了系统中已经定义了一系列常用的动作。
     * 通过setAction()方法或在清单文件AndroidManifest.xml中设置。默认为:DEFAULT。
     *Data:Data通常是URI格式定义的操作数据。例如:tel:// 。通过setData()方法设置。
     *Category:Category属性用于指定当前动作(Action)被执行的环境。通过addCategory()方法或在清单文件AndroidManifest.xml中设置。
     *默认为:CATEGORY_DEFAULT。
     *Extras:Extras属性主要用于传递目标组件所需要的额外的数据。通过putExtras()方法设置。
     */
     //相册
    public void getPhotoAlbum() {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_PICK);
        intent.setType("image/*");  //type  指定获取 image类型的所有文件
        startActivityForResult(intent, 2);
        //系统相册选图返回的Uri是可以直接使用的,不需要也不能使用FileProvider进行转换
    }

    //照相功能
    /*思路
     * 首先是调用相机  意图
     * 第二步 获取图片路径
     * 最后保存并返回
     */
    private void getCamera() {
        File file = new FileStorage().createCropFile()
                /*   方法                               描述
        File(File dir, String name) File对象类型的目录路径,name为文件名或目录名。
        File(String path)   path为新File对象的路径。
        File(String dirPath, String name)   dirPath为指定的文件路径,name为文件名或目录名。
        File(URI uri)   使用URI指定路径来创建新的File对象。*/
        
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            uri = FileProvider.getUriForFile(this, "com.ycb.baseicon.fileProvider", file);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//临时授予读写权限
        } else {
            //低版本路径转成uri
            uri = Uri.fromFile(file);
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);  //将图片保存在这个位置
        startActivityForResult(intent, 1);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        switch (requestCode) {
            //照相
            case 1:
                //此处不可写成 data.getData
                // 因为上面通过putExtra  将地址存在了 uri  这个指定的路径里面
                startPhotoZoom(uri);
                break;
            //相册
            case 2:
                //从相册返回的uri  可以看在getPhotoAlbum处的注解
                startPhotoZoom(data.getData());
                // content                  media
                Log.i("文件地址", data.getScheme() + " " + data.getData().getAuthority() +
                        data.getData().getHost() + data.getData().getPort() + "  " + data.getData().getPath() + "\n" + data.getData());
                //content://media/external/images/media/1036430   path: /external/images/media/1036430
                break;
            //裁剪
            case 3:
                Bundle bundle = data.getExtras();
                if (bundle != null) {
                    //裁剪处有对照表  键名为data   类型是 parcelable   value (因为写的是true )  bitmap
                    Bitmap bitmap = bundle.getParcelable("data");
                    String path = saveImage(bitmap);
                    loadCircleImage(this, path, mIcon);
                    SPUtils.putString(this, "icon", path);
                }
                Log.i("Uri", cropUri.toString());
          /*      if (cropUri!=null){
                    Bitmap bitmap ;  当使用false时
                    try {   这种方法提示获取的地址不存在
                        bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(cropUri));
                        loadCircleImage(this,bitmap,mIcon);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }

                }*/
                break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    //裁剪方法 
    private void startPhotoZoom(Uri uri) {
        File file = new FileStorage().createCropFile();
        cropUri = Uri.fromFile(file);  //file 类型转成了 uri 类型 最后在下面使用   最终将裁剪后的图片保存在这个指定的位置
        //调用系统裁剪的意图
        Intent intent = new Intent("com.android.camera.action.CROP");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
/*      crop	    String	   发送裁剪信号
        aspectX	    int	       X方向上的比例
        aspectY	    int	       Y方向上的比例
        outputX	    int	       裁剪区的宽
        outputY 	int	       裁剪区的高
        scale	    boolean	   是否保留比例
        return-data	boolean	   是否将数据保留在Bitmap中返回
        data	   Parcelable  相应的Bitmap数据
        circleCrop	String	   圆形裁剪区域?
        MediaStore.EXTRA_OUTPUT ("output")	URI	 将URI指向相应的file:///...,详见代码示例
        outputFormat	String	输出格式,一般设为Bitmap格式:Bitmap.CompressFormat.JPEG.toString()
        noFaceDetection	boolean	是否取消人脸识别功能*/
        intent.setDataAndType(uri, "image/*");  //设置data (uri) 和type  类型
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1); // 裁剪框比例
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", 300); // 输出图片大小
        intent.putExtra("outputY", 300);
        intent.putExtra("scale", true);
        //Intent 的data域最大传递的值的大小约为1M,所以图片的BITMAP当超过1M时就会失败 : 无法传递大图  false 传递uri
        intent.putExtra("return-data", true);  //true  表示返回的是bitmap对象  为true的情况下 一般在图片尺寸480*480 崩溃
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri);  //将 图像转移保存到  -》 croupUri   :   uri位置
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("noFaceDetection", true); // no face detection
        startActivityForResult(intent, 3);
    }


    public void showToast(String msg) {
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    /**
     * 加载圆形图片
     */
    public static void loadCircleImage(Context context, String path, ImageView imageView) {
        // RequestOptions  扩展glide  自定义加载方式
        RequestOptions options = new RequestOptions()
                .centerCrop()
                .circleCrop()//设置圆形
                .diskCacheStrategy(DiskCacheStrategy.ALL);
        Glide.with(context).load(path).apply(options).into(imageView);
    }

    //获取图像的String类型path 地址   可以用于保存或者上传到服务器
    public String saveImage(Bitmap bmp) {
        File file = new FileStorage().createIconFile();
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);  //图片压缩
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
}

总结

个人认为最终裁剪的图片可以把它的path地址上传到服务器上面,然后在把服务器接口返回的图片地址保存起来,用glide显示

代码下载