Android 拍照和相册选取+图片压缩功能(兼容6.0以下,6.0, 7.0,8.0)

      将拍照和相册选图封装成Demo,图片压缩用的是Android 开源压缩框架——Luban。先上效果动图:

                                                             

                                                                                    效果图

        

       一言以蔽之,拍照和相册选图这个开发中常见的功能,版本之间的重要区别是:

             6.0以下的版本:只要在AndroidManifest中注明相机的使用权限,SDCard的读写2权限,就能在正常的写功能了

             6.0:6.0以后Google不再允许开发者直接或许应用的权限,需要在用户知情的情况下授予权限,这便是运行时权限。相机,读,写3个权限除了要在AndroidManifest先声明,在写功能代码的时候还要做动态申请的操作。

             7.0+: 众所周知Android四大组件之一内容提供器的作用,便是在不同的应用程序之间实现数据共享, Android 从 N 版本开始禁止通过 file://Uri 的方式在不同 App 之间共享文件,如此以来7.0版本以下直接获取图片文件路径的方法行不通了,所以我们写的应用,就需要通过内容提供器的方式,来获取系统文件管理器中目标文件的路径。

         主要源码如下:

  MainActivity:

package p.com.lubandemo.activity;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import java.io.File;

import p.com.lubandemo.R;
import p.com.lubandemo.rx.RxBus;
import p.com.lubandemo.rx.RxBusBaseMessage;
import p.com.lubandemo.rx.RxCode;
import p.com.lubandemo.util.STContext;
import p.com.lubandemo.view.FaceBlackPopWindow;
import rx.Subscription;
import rx.functions.Action1;
import top.zibin.luban.Luban;
import top.zibin.luban.OnCompressListener;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Handler uiHandler;
    private Context context;
    private FaceBlackPopWindow popupWindow;
    private Button  btn_popwindow;
    MainActivity activity;

    //动态写权限所用
    public static boolean writeAccepted = false;
    public static final int TAKE_PICTURE = 0;
    public static final int PHOTO_REQUEST_GALLERY = 1;        // 从相册中选择
    public static Uri phototUri = null;
    //7.0+ 拍照源路径所用
    String loadUrl = "";
    //所选相册图片的路径
    String albumPath = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        uiHandler = new Handler();
        activity = this;
        btn_popwindow = findViewById(R.id.btn_popwindow);
        context = MainActivity.this;
        btn_popwindow.setOnClickListener(this);
        Subscription subscription = RxBus.getDefault()
                .toObservable(RxBusBaseMessage.class)
                .subscribe(new Action1() {
                    @Override
                    public void call(RxBusBaseMessage message) {
                        if (message.getCode() == RxCode.FACE_BLACK_DISMISS) {

                            uiHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    /**
                                     更新UI
                                     */
                                    dismissPop();
                                }
                            });
                        }
                    }
                });
    }

    //6.0+运行时权限---写权限请求回掉函数(相机用)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        switch (requestCode) {
            case 1:
                writeAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
    }


    private void dismissPop()
    {
        if(popupWindow!=null &&popupWindow.isShowing())
        {
            popupWindow.dismiss();
        }
    }


    private void showFaceBlackPop()
    {
        popupWindow = new FaceBlackPopWindow(activity,activity.getWindow());
        //弹出修改/次增加人脸黑名单弹出窗
        //控件,为了popWindow一个基准``````````````
        Button lin_face_warn = btn_popwindow;
        popupWindow.showWindow(lin_face_warn);
    }

   // 压缩后图片文件存储位置
    private String getPath() {
        String path = STContext.getInst().imageFilePath;
        File file = new File(path);
        if (file.mkdirs()) {
            return path;
        }
        return path;
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) { // 如果返回码是可以用的
            switch (requestCode) {
                case TAKE_PICTURE:
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                        //6.0以及以下的处理   luban 图片压缩的开源库
                        Luban.with(context)
                                .load(phototUri.getPath())                                   // 传人要压缩的图片列表
                                .ignoreBy(100)                                  // 忽略不压缩图片的大小
                                .setTargetDir(getPath())                        // 设置压缩后文件存储位置
                                .setCompressListener(new OnCompressListener() { //设置回调
                                    @Override
                                    public void onStart() {
                                        // TODO 压缩开始前调用,可以在方法内启动 loading UI
                                    }

                                    @Override
                                    public void onSuccess(File file) {
                                        // TODO 压缩成功后调用,返回压缩后的图片文件
                                        //通知PopWindow绘制图片
                                        RxBus.getDefault().post(RxCode.FACE_BLACK_IMAGE_CHANGE, file.getPath());
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        // TODO 当压缩过程出现问题时调用
                                    }
                                }).launch();    //启动压缩
                    } else {

                        //7.0+的处理
                        String[] arr = phototUri.getPath().split("/");
                        loadUrl = "";
                        for (int i = 2; i < arr.length; i++) {
                            if (i == arr.length - 1) {
                                loadUrl += arr[i];
                            } else {
                                loadUrl += arr[i] + "/";
                            }

                        }
                        Luban.with(context)
                                .load(Environment.getExternalStorageDirectory() + "/" + loadUrl)    // 传人要压缩的图片列表 (生成照片存储的绝对路径)
                                .ignoreBy(100)                                  // 忽略不压缩图片的大小
                                .setTargetDir(getPath())                        // 设置压缩后文件存储位置
                                .setCompressListener(new OnCompressListener() { //设置回调
                                    @Override
                                    public void onStart() {
                                        // TODO 压缩开始前调用,可以在方法内启动 loading UI
                                    }

                                    @Override
                                    public void onSuccess(File file) {
                                        // TODO 压缩成功后调用,返回压缩后的图片文件
                                        //通知PopWindow绘制图片
                                        RxBus.getDefault().post(RxCode.FACE_BLACK_IMAGE_CHANGE, file.getPath());
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        // TODO 当压缩过程出现问题时调用
                                    }
                                }).launch();    //启动压缩

                    }
                    break;
                case PHOTO_REQUEST_GALLERY:

                    //获取所选相册的图片路径
                    Uri albumUri = data.getData();
                    Uri selectedImage = data.getData();
                    String[] filePathColumns = {MediaStore.Images.Media.DATA};
                    Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null);
                    c.moveToFirst();
                    int columnIndex = c.getColumnIndex(filePathColumns[0]);
                    albumPath = c.getString(columnIndex);
                    c.close();

                    //6.0以及以下的处理
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                        //6.0以及以下的处理
                        Luban.with(context)
                                .load(albumPath)                                   // 传人要压缩的图片列表 (相册所选照片的绝对路径)
                                .ignoreBy(100)                                  // 忽略不压缩图片的大小
                                .setTargetDir(getPath())                        // 设置压缩后文件存储位置
                                .setCompressListener(new OnCompressListener() { //设置回调
                                    @Override
                                    public void onStart() {
                                        // TODO 压缩开始前调用,可以在方法内启动 loading UI
                                    }

                                    @Override
                                    public void onSuccess(File file) {
                                        // TODO 压缩成功后调用,返回压缩后的图片文件
                                        //通知PopWindow绘制图片
                                        RxBus.getDefault().post(RxCode.FACE_BLACK_IMAGE_CHANGE, file.getPath());
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        // TODO 当压缩过程出现问题时调用
                                    }
                                }).launch();    //启动压缩
                    } else {

                        //7.0+的处理
                        Luban.with(context)
                                .load(albumPath)    // 传人要压缩的图片列表
                                .ignoreBy(100)                                  // 忽略不压缩图片的大小
                                .setTargetDir(getPath())                        // 设置压缩后文件存储位置
                                .setCompressListener(new OnCompressListener() { //设置回调
                                    @Override
                                    public void onStart() {
                                        // TODO 压缩开始前调用,可以在方法内启动 loading UI
                                    }
                                    @Override
                                    public void onSuccess(File file) {
                                        // TODO 压缩成功后调用,返回压缩后的图片文件
                                        //通知PopWindow绘制图片
                                        RxBus.getDefault().post(RxCode.FACE_BLACK_IMAGE_CHANGE, file.getPath());
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        // TODO 当压缩过程出现问题时调用
                                    }
                                }).launch();    //启动压缩
                    }

                    break;
            }
        }
    }


    @Override
    public void onClick(View view) {
        switch (view.getId())
        {
            case R.id.btn_popwindow:
                showFaceBlackPop();
                break;
        }
    }
}

   其中运行时权限的回调:

   //6.0+运行时权限---写权限请求回掉函数(相机用)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        switch (requestCode) {
            case 1:
                writeAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
    }

 FaceBlackPopWindow源码:

package p.com.lubandemo.view;

import android.Manifest;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import p.com.lubandemo.R;
import p.com.lubandemo.activity.MainActivity;
import p.com.lubandemo.rx.RxBus;
import p.com.lubandemo.rx.RxBusBaseMessage;
import p.com.lubandemo.rx.RxCode;
import rx.Subscription;
import rx.functions.Action1;

import static p.com.lubandemo.activity.MainActivity.PHOTO_REQUEST_GALLERY;


/**
 * Created by ThinkPad on 2017/12/7.
 */
//人脸黑名单添加和修改  的popWindow
public class FaceBlackPopWindow extends PopupWindow implements View.OnClickListener{

    private View contentView;
    private Activity context;
    private ImageView img_face;
    private View anchorView;
    //传过来的bean(修改的话传值,创建的话传null)
    private Button btn_cancel;
    private Button btn_sure;
    private LinearLayout lin_update_icon;
    private Handler uiHandler;
    //头像修改完存储的路径
    private String iconUrl = "";



    public FaceBlackPopWindow(Activity _context, final Window window) {

        this.context = _context;
        uiHandler = new Handler();
        //发起读写设备存储空间的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            context.requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
        contentView = LayoutInflater.from(context).inflate(R.layout.black_face_popwindow, null);
        this.setContentView(contentView);
        this.setTouchable(true);
        this.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        img_face = contentView.findViewById(R.id.img_face);
        btn_cancel = contentView.findViewById(R.id.btn_cancel);
        btn_sure = contentView.findViewById(R.id.btn_sure);
        lin_update_icon = contentView.findViewById(R.id.lin_update_icon);
        this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        this.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
        //背景阴影
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.alpha = 0.5f;
        window.setAttributes(lp);
        // 在dismiss中恢复透明度
        this.setOnDismissListener(new OnDismissListener() {
            @Override
            public void onDismiss() {
                WindowManager.LayoutParams lp = window.getAttributes();
                lp.alpha = 1f;
                window.setAttributes(lp);
            }
        });
        this.setBackgroundDrawable(new BitmapDrawable());
        this.setFocusable(false);// 如此设置,popwindow消失后不会弹出软键盘
        //点击外部消失
        this.setOutsideTouchable(true);
        //设置可以点击
        this.setTouchable(true);
        //进入退出的动画
        this.setAnimationStyle(R.style.mypopwindow_anim_style);
        this.setBackgroundDrawable(new BitmapDrawable());
        //软键盘不会挡着popupwindow
        this.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
        //设置PopuoWindow弹出窗体需要软键盘
        this.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);

        btn_cancel.setOnClickListener(this);
        btn_sure.setOnClickListener(this);
        lin_update_icon.setOnClickListener(this);

        Subscription subscription = RxBus.getDefault()
                .toObservable(RxBusBaseMessage.class)
                .subscribe(new Action1() {
                    @Override
                    public void call(final RxBusBaseMessage message) {
                        if (message.getCode() == RxCode.FACE_BLACK_IMAGE_CHANGE) {

                            uiHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    /**
                                     更新UI
                                     */
                                    img_face.setBackgroundResource(0);
                                    iconUrl = message.getObject().toString();
                                    /**  Glide  渲染 **/
                                    //设置图片圆角角度
                                    RoundedCorners roundedCorners = new RoundedCorners(30);
                                    //通过RequestOptions扩展功能
                                    RequestOptions options = RequestOptions.bitmapTransform(roundedCorners).override(300, 300)
                                            //圆形
                                            .circleCrop();
                                    Glide.with(img_face.getContext())
                                            .load(iconUrl).apply(options).into(img_face);

                                }
                            });
                        }
                    }

                } );


    }


    public void showWindow(View view) {
        if (!this.isShowing()) {
            if (Build.VERSION.SDK_INT < 24) {
                this.showAsDropDown(view);
            } else {
                // 获取控件的位置,安卓系统>7.0
                int[] location = new int[2];
                view.getLocationOnScreen(location);
                this.showAtLocation(view, Gravity.CENTER, 0, 0);
            }
        }
    }


    public boolean onTouchEvent(MotionEvent event) {

// TODO Auto-generated method stub

        return false;
    }


    /**
     * 显示修改头像的对话框
     */
    protected void showChoosePicDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("上传图片");
        String[] items = { "拍照","相册" };
        builder.setNegativeButton("取消", null);
        builder.setItems(items, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case MainActivity.TAKE_PICTURE: // 拍照
                        Intent openCameraIntent = new Intent(
                                MediaStore.ACTION_IMAGE_CAPTURE);
                        if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.M)
                        {
                            try {
                                //创建一个路径用于存生成的照片
                                MainActivity.phototUri = Uri.fromFile(createMediaFile(".png"));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //系统拍照后,存到phototUri
                            openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, MainActivity.phototUri);
                            context.startActivityForResult(openCameraIntent, MainActivity.TAKE_PICTURE);
                        }else
                        {
                            //适配大于6.0的版本
                            File cameraPhoto = null;
                            try {
                                //创建一个路径用于存生成的照片
                                cameraPhoto = new File(createMediaFile(".png"),"");
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //生成照片的路径通过FileProvider创建一个content类型的Uri
                            MainActivity.phototUri = FileProvider.getUriForFile(
                                    context,
                                    context.getPackageName() + ".fileprovider",
                                    cameraPhoto);
                            openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, MainActivity.phototUri);
                            context.startActivityForResult(openCameraIntent, MainActivity.TAKE_PICTURE);
                        }
                        break;

                    case PHOTO_REQUEST_GALLERY:
                        Album();
                        break;
                }
            }
        });
        builder.create().show();
    }


    /**
     * 调起相册
     */
    private void Album(){
        Intent albumIntent = new Intent(Intent.ACTION_PICK);
        albumIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        context.startActivityForResult(albumIntent,PHOTO_REQUEST_GALLERY);
    }


    private File createMediaFile(String suffixName) throws IOException {

        if (MainActivity.writeAccepted||Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                //Android系统默认路径   Environment.DIRECTORY_MOVIES:电影保存的位置
                File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_MOVIES), "CameraDemo");
                if (!mediaStorageDir.exists()) {
                    if (!mediaStorageDir.mkdirs()) {
                        return null;
                    }
                }
                // Create an image file name   时间戳处理命名
                String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
                String imageFileName = "VID_" + timeStamp;
                String suffix = suffixName;
                File mediaFile = new File(mediaStorageDir + File.separator + imageFileName + suffix);
                return mediaFile;

            }
        }
        return null;
    }




    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_cancel:

                this.dismiss();

                break;
            case R.id.lin_update_icon:

                //弹出拍照,照片弹窗
                showChoosePicDialog();
                break;
        }
    }
}

       由上可知,针对7.0以上版本路径的变更,上面代码获取路径使用了FileProvider,FileProvider其实就是内容提供器 的一个子类,用于应用之间共享文件,使用FlieProvider需要2个先前操作:

     1.AndroidManifest中注册

 
            
        

    2. 定义xml文件,文件名随便起,名字和provider匹配



    

  简单的说,xml文件的任务就是对具体文件路径的映射,指定了可以访问的文件存储的区域和路径。上面的写法的意思是,代码可以从外部存储根路径Environment.getExternalStorageDirectory()开始,使用当前路径文件夹和其子路径文件夹,如果我换了一种写法:



    

     那么代码只能用Environment.getExternalStorageDirectory()/downloads开始的文件夹路径了,上层路径都做了限制不能映射。

     然后代码中相机即时拍出照片的具体路径,通过FileProvider创建一个content类型的Uri,以供对外其他应用(我们写的应用)进行使用:

                      //生成照片的路径通过FileProvider创建一个content类型的Uri
                        
                      //适配大于6.0的版本
                            File cameraPhoto = null;
                            try {
                                //创建一个路径用于存生成的照片
                                cameraPhoto = new File(createMediaFile(".png"),"");
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //生成照片的路径通过FileProvider创建一个content类型的Uri
                            MainActivity.phototUri = FileProvider.getUriForFile(
                                    context,
                                    context.getPackageName() + ".fileprovider",
                                    cameraPhoto);

   为了方便在手机上查找,相机照出来的照片文件夹我设置成了movie的系统路径下,关于压缩框架luban会另起一篇进行记录总结,Demo已上传:https://download.csdn.net/download/crystal_xing/10891256

 

 

你可能感兴趣的:(android)