Android实现拍照或从相册中获取图片并且显示圆形图片

版权声明:如果需要引用文中内容,希望可以带上相应的链接地址,万分感谢。地址: https://blog.csdn.net/shaowanyunBLOG/article/details/82252248

一般我们在使用一些app的注册功能时,都被提供了拍照或者是从相册中获取图片这两种选择,同时目前我们的用户选取的头像好像是约定俗成一样,都是圆形图片,按照我前两篇博客《Android利用okhttp实现图片上传之安卓客户端请求怎么写》和《Java后台实现用户登录注册Servlet以及对数据库怎么处理》中的讲解,虽然讲解了客户端怎么上传用户信息(含头像信息),和服务端怎么对客户端上传的图片进行处理,但是总还是觉得少点什么,所以,这次我还是在之前的那个基础之上讲解怎么样实现拍照或从相册中选取图片并且最后显示出圆形图片。

首先的话,我先来把效果展示一下:

1.拍照选取及显示:

2.从相册中选取并显示:

上面这两张图就是我这边实现的效果。

第一个就是打开照相机,然后进行拍照,然后将获取到的图片信息返回到注册界面,并显示出来圆形图片。

第二个是打开相册,选取目录,然后选择图片,并将获取到的图片信息返回到注册界面,并显示出来圆形图片。

那么接下来,我就用代码的方式,配合一定的讲解来说明具体功能的实现。

然后在讲解之前,处于对别人原创作品的尊重,我需要先特别声明的是,本文中实现的功能是基于两个从网上获取的资源进行合并实现而来的,关键的实现代码并不是本人所做,我只是通过把两个资源整合然后实现了我想要的功能,这个郑重声明,并无意侵权。

扫描二维码关注公众号,回复: 3111897 查看本文章

其中一个资源是github上面的一个开源在Android设备上获取照片(拍照或从相册、文件中选择)、裁剪图片、压缩图片的开源工具库。地址是:https://github.com/crazycodeboy/TakePhoto

另外一个是:从网上找的别人写的自定义圆形ImageView:CircleImageView,但是具体的链接地址过了这么长时间,我想不起来了,也找不到了,在此先向原作者说声抱歉。如果有需求,可以和我联系然后我会更改本篇博客内容,或者是删除。

那么,接下来,就正式开始怎么实现这样一个功能。

1. 在开始之前,需要先去上面给出的那个github链接地址,将里面提供的代码下载下来,然后将其中的takephoto_library模块集成到自己的项目中,同时需要在build.gradle文件的dependencies中进行引用。如下图所示:

然后可能在引用的过程中会出现一些资源文件的或者是因为配置不同的错误,不过这只需要进行一定的调试即可,如果有遇到调试中出现错误不知道怎么解决的,可以在评论区留言,然后我看到的话会尽快给予我最大限度的帮助。

2. 引用模块成功之后,那么我们就需要具体的使用了,因为我这里是在注册界面(RegisterActivity)中使用的调用拍照和相册的功能,那么就需要重点关注这些代码了,如下图:

首先需要将自己的Activity继承TakePhotoActivity,并且实现其定义的回调方法,然后这里面还需要注意的就是CustomerHelper,这个应该是与用户设置相关的一个类。

如下:

这个是从作者提供的代码里面的布局文件里面接的一个图,显然这些都是一些设置,但是我很清楚,这些不是我需要的,那么我就需要对CustomerHelper文件进行更改。这里我简单举个例子说明一下,见图:

比如说这里,这里会根据那个配置界面中的“是否显示压缩进度条”和“拍照压缩后是否保存原图”以及“裁剪工具”的对应单选按钮的结果进行设置,那么我这里不希望有那个设置的界面的话,所以这些我都需要直接指定。那么我的更改如下图:

其实很显然,我直接制定我需要的所有的配置即可。比如说,我需要显示进度条,压缩后同样需要保存原图,压缩工具我是用自带的,那么我就把对应的boolean变量指定为对应的值,true或false,把相关的if-else判断直接留下我想要的代码块,但是我个人建议,在使用时,最好是把那些更改所对应的含义都备注起来,关键地方能不删的就不删,毕竟我们看效果的时候还是要调试的,万一上来就咔咔删,到最后还要再去重新CV,比较麻烦。

然后还有最关键的一个就是怎么实现具体的打开照相机和打开相册的功能呢?

显然,有一个方法是至关重要的:custerHelper.onClick(view,getTakePhoto());

这个onClick方法是CustomerHelper自定义的一个点击事件,我们先来看看具体的方法是怎么定义的:

public void onClick(View view, TakePhoto takePhoto) {
    File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
    if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
    }
    Uri imageUri = Uri.fromFile(file);

    configCompress(takePhoto);
    configTakePhotoOption(takePhoto);
    switch (view.getId()) {
        case R.id.btnPickBySelect:
            int limit = Integer.parseInt(etLimit.getText().toString());
            if (limit > 1) {
                if (rgCrop.getCheckedRadioButtonId() == R.id.rbCropYes) {
                    takePhoto.onPickMultipleWithCrop(limit, getCropOptions());
                } else {
                    takePhoto.onPickMultiple(limit);
                }
                return;
            }
            if (rgFrom.getCheckedRadioButtonId() == R.id.rbFile) {
                if (rgCrop.getCheckedRadioButtonId() == R.id.rbCropYes) {
                    takePhoto.onPickFromDocumentsWithCrop(imageUri, getCropOptions());
                } else {
                    takePhoto.onPickFromDocuments();
                }
                return;
            } else {
                if (rgCrop.getCheckedRadioButtonId() == R.id.rbCropYes) {
                    takePhoto.onPickFromGalleryWithCrop(imageUri, getCropOptions());
                } else {
                    takePhoto.onPickFromGallery();
                }
            }
            break;
        case R.id.btnPickByTake:
            if (rgCrop.getCheckedRadioButtonId() == R.id.rbCropYes) {
                takePhoto.onPickFromCaptureWithCrop(imageUri, getCropOptions());
            } else {
                takePhoto.onPickFromCapture(imageUri);
            }
            break;
        default:
            break;
    }
}

那么通过看代码的话,倒也好理解传进去的两个参数是干什么的了,第一个是View的对象,说白了就是控件,那么具体的使用是干什么的呢?显然是通过getId,然后判断用户点击的是“拍照”还是“选择图片”,如图:

但是按照我自己的设计,我不想在注册界面下方再弄这么两个按钮,我是想搞一个对话框类型的选择按钮,当我点击注册上方的那个头像时,弹出如下对话框,如图:

我觉得这样的话,看着会更美观一些。关于这个东西的设计的话,可以看我的这篇博客:《Android实现一个底部弹框》链接地址:https://mp.csdn.net/postedit/82254631

那么回到刚才的问题,第一个参数View,不过是为了获取id然后区分到底是点击了拍照还是相册,但是View这个参数很特殊,有时候不一定那么的好获取到对象,或者是如果对View的理解不深的话,那么那个方法该怎么调用着实犯难。

所以,我想了个办法:修改缺省参数,把第一个参数修改为String type,也就是如下:

public void onClick(String type, TakePhoto takePhoto) {
    File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
    if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
    }
    Uri imageUri = Uri.fromFile(file);

    configCompress(takePhoto);
    configTakePhotoOption(takePhoto);
    switch (type) {
        case "takephoto":
            takePhoto.onPickFromCaptureWithCrop(imageUri, getCropOptions());
            break;
        case "selectphoto":
            int limit = 1;//最多选择多少张图片
            if (limit > 1) {
                takePhoto.onPickMultipleWithCrop(limit, getCropOptions());
                return;
            }
            takePhoto.onPickFromGalleryWithCrop(imageUri, getCropOptions());
            break;
        default:
            break;
    }
}

哎,这样是不是代码就更加的明显了,我在点击“拍照”和“相册”的事件中调用这个方法的同时,把第一个参数分别传入“takephoto”和“selectphoto”,然后这里我直接判断String的值,然后做对应的事,这样的话,就可以顺利的执行作者设计的相关方法了。

然后第二个参数的话,是一个方法getTakePhoto(),这个方法是继承的TakePhotoActivity里面定义的一个共有方法,不用改,直接照着调用就行了,所以,对应的调用如图:

嗯,就是这样子去用了,然后这里面的new Dialogchoosephoto。。。。什么鬼的,可以看我另外一篇博客,,但是这里的关键代码还是我用红线标出的代码,通过这样的方式,就可以让我在点击“拍照”时就可以调用系统相机,点击“相册”时就可以调用系统相册,然后后面的事情,就是框架里面封装的方法了,具体的实现我还没来得及看,不过知道现在对我们来说,知道怎么用就可以了。

当我们拍照或者是从相册中选取完毕之后,都会获得一个最终的结果图像,那么用什么办法获取呢?别着急,看作者的demo代码中的这个ResultActivity里面的用法,如下图:

看着很迷惑,其实就是把图片信息加载到了对应的ImageView了,但是看定义了两个ImageView,很麻烦,其实这里的话,是因为,作者实现的从相册中获取图片是可以获取多张图片的,然后那么自然在显示的时候就要把对应的图片都显示出来,每行两张,然后做循环就把所有的图片显示出来了,所以这里要new两个ImageView去处理多张的显示。

那么可见这里的关键在于变量images的获取,显然是从前面的Activity跳转过来时顺手用Intent传过来的,那么我们回去找找:

啊,找到了,就是在回调方法:takeSuccess()方法中,当用户获取图片成功时,就把对应的图片信息封装到TImage实体类中,然后传到ResultActivity中,然后稍微看下这个TImage是如何定义的:

从参数中,虽然没有备注定义,但是还是可以推测出来,这里是保存的图片的地址,一个是原始图片的地址,一个是压缩之后的图片的地址,那么再回过头看具体的加载图片的方法:

Glide.with(this).load(new File(images.get(images.size() - 1).getCompressPath())).into(imageView1);

拿这个来举例,本质上还是把图片的压缩之后的地址加载到对应的ImageView,想想有道理,肯定是要用压缩过的图片啊。

那么分析到这里的话,就基本上把拍照和从相册中获取的方法的调用以及如何获取结果图像的过程进行了一下梳理,那么接下来就是另外实现圆形图片的过程了。

其实圆形图片说白了也不过是一个ImageView而已,所以我们只需要把下面的CircleImageView的自定义ImageView的文件放到你的项目中,然后在布局的时候制定一下ImageView即可,我先把对应的代码给出来:

public class CircleImageView  extends android.support.v7.widget.AppCompatImageView {
    private Paint mPaint; //画笔

    private int mRadius; //圆形图片的半径

    private float mScale; //图片的缩放比例

    public CircleImageView(Context context) {
        super(context);
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //因为是圆形图片,所以应该让宽高保持一致
        int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
        mRadius = size / 2;
        setMeasuredDimension(size, size);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        mPaint = new Paint();
        Bitmap bitmap = drawableToBitmap(getDrawable());
        //初始化BitmapShader,传入bitmap对象
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        //计算缩放比例
        mScale = (mRadius * 2.0f) / Math.min(bitmap.getHeight(), bitmap.getWidth());
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        bitmapShader.setLocalMatrix(matrix);
        mPaint.setShader(bitmapShader);

        //画圆形,指定好中心点坐标、半径、画笔
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    }

    //写一个drawble转BitMap的方法
    private Bitmap drawableToBitmap(Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bd = (BitmapDrawable) drawable;
            return bd.getBitmap();
        }
        int w = drawable.getIntrinsicWidth();
        int h = drawable.getIntrinsicHeight();
        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, w, h);
        drawable.draw(canvas);
        return bitmap;
    }
}

嗯,然后我们在使用时可以按照这么来使用:

这里有两个点需要强调,一个就是不同的项目,自定义View的地址肯定都是不同的,所以,不管是我们在网上用别人的自定义的View也好,还是我们自己写自定义View也好,这个地址一定要写对,否则,程序会闪退的。然后那个ScaleType = centerCrop的话,我忘了为什么要弄那一句了,不过加上也没关系。

然后讲到这里的话,基本上就把关于如何实现拍照或从相册中获取图片、压缩、剪切然后显示到圆形图片上的操作就结束了,然后下面的话,我把我这里的RegisterActivity和我修改过的CustomeHelper,大家可以参考一下,也可以自己从网上把对应的代码下载下来,根据自己项目的实际需要进行修改,然后如果遇到了什么问题,需要帮助的,或者是我这里有什么地方表述的不到位甚至有错误的,欢迎在评论区留言或指正,先行拜谢。

RegisterActivity(只保留本文相关内容):

public class RegisterActivity extends TakePhotoActivity {

    private com.example.fleamarket.Utils.util.LineEditText et_username, et_password,et_rpwd;
    private Button btn_signup;
    private ImageView img_back;
    private com.example.fleamarket.Utils.util.CircleImageView userlogo;
    private boolean RegisterSuccess = false;
    private Dialog progressDialog;
    private String username,password,rpwd;
    private String host;
    private CustomHelper customHelper;
    private boolean ChooseImage = false;
    private int responseCode;
    private String responseInfo;
    private boolean TokenTimeout = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
        View contentView = LayoutInflater.from(this).inflate(R.layout.activity_register, null);
        setContentView(contentView);
        host = getResources().getString(R.string.host);
        customHelper = CustomHelper.of(contentView);


        userlogo = findViewById(R.id.userlogo);
        userlogo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Dialogchoosephoto(RegisterActivity.this){
                    @Override
                    public void btnPickByTake(){
                        ChooseImage = true;
                        //拍照
                        customHelper.onClick("takephoto", getTakePhoto());
                    }
                    @Override
                    public void btnPickBySelect() {
                        ChooseImage = true;
                        //相册
                        customHelper.onClick("selectphoto", getTakePhoto());
                    }

                }.show();
            }
        });
    }

    @Override
    public void takeCancel() {
        super.takeCancel();
        Log.i("Marketlog","RegisterActivity : takeCancel");
    }

    @Override
    public void takeFail(TResult result, String msg) {
        super.takeFail(result, msg);
        Log.i("Marketlog","RegisterActivity : takeFail");
    }

    @Override
    public void takeSuccess(TResult result) {
        Log.i("Marketlog","RegisterActivity : takeSuccess");
        super.takeSuccess(result);
        for (int i = 0, j = result.getImages().size(); i < j - 1; i += 2) {
            Glide.with(this).load(new File(result.getImages().get(i).getCompressPath())).into(userlogo);
            Glide.with(this).load(new File(result.getImages().get(i + 1).getCompressPath())).into(userlogo);
        }
        if (result.getImages().size() % 2 == 1) {
            Glide.with(this).load(new File(result.getImages().get(result.getImages().size() - 1).getCompressPath())).into(userlogo);
        }
    }

}

我修改过的CustomeHelper:

public class CustomHelper {
    private View rootView;

    public static CustomHelper of(View rootView) {
        return new CustomHelper(rootView);
    }

    private CustomHelper(View rootView) {
        this.rootView = rootView;
    }

    public void onClick(String type, TakePhoto takePhoto) {
        File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        Uri imageUri = Uri.fromFile(file);

        configCompress(takePhoto);
        configTakePhotoOption(takePhoto);
        switch (type) {
            case "takephoto":
                takePhoto.onPickFromCaptureWithCrop(imageUri, getCropOptions());
                break;
            case "selectphoto":
                int limit = 1;//最多选择多少张图片
                if (limit > 1) {
                    takePhoto.onPickMultipleWithCrop(limit, getCropOptions());
                    return;
                }
                takePhoto.onPickFromGalleryWithCrop(imageUri, getCropOptions());
                break;
            default:
                break;
        }
    }

    private void configTakePhotoOption(TakePhoto takePhoto) {
        TakePhotoOptions.Builder builder = new TakePhotoOptions.Builder();
        //使用自带相册?
        builder.setWithOwnGallery(false);
//        纠正照片角度?
//        builder.setCorrectImage(true);
        takePhoto.setTakePhotoOptions(builder.create());
    }

    private void configCompress(TakePhoto takePhoto) {
        int maxSize = 10240;//大小不超过
        int width = 200;//大小不超过多宽
        int height = 200;//大小不超过多高
        boolean showProgressBar = true;//是否显示压缩进度条
        boolean enableRawFile = true;//压缩后是否保存原图
        CompressConfig config;
        //压缩工具:自带
        config = new CompressConfig.Builder().setMaxSize(maxSize)
                .setMaxPixel(width >= height ? width : height)
                .enableReserveRaw(enableRawFile)
                .create();

        //压缩工具:鲁班
//        LubanOptions option = new LubanOptions.Builder().setMaxHeight(height).setMaxWidth(width).setMaxSize(maxSize).create();
//        config = CompressConfig.ofLuban(option);
//        config.enableReserveRaw(enableRawFile);

        takePhoto.onEnableCompress(config, showProgressBar);
    }

    private CropOptions getCropOptions() {
        int height = 200;//高
        int width = 200;//宽
        boolean withWonCrop = true;//压缩工具是否为第三方
        CropOptions.Builder builder = new CropOptions.Builder();
        builder.setOutputX(width).setOutputY(height);
        builder.setWithOwnCrop(withWonCrop);
        return builder.create();
    }

}

最后

猜你喜欢

转载自blog.csdn.net/shaowanyunBLOG/article/details/82252248