Android实现拍照、选择图片并裁剪图片功能
一、 实现拍照、选择图片并裁剪图片效果
按照之前博客的风格,首先看下实现效果。
二、 ucrop项目应用
想起之前看到的yalantis/ucrop效果比较绚,但是研究源码之后发现在定制界面方面还是有一点的限制,于是在它的基础上做了修改android-crop,把定制界面独立出来,让用户去*设置。下图为使用android-crop实现的模仿微信选择图片并裁剪demo。
三、 实现思路
比较简单的选择设备图片裁剪,并将裁剪后的图片保存到指定路径;
调用系统拍照,将拍照图片保存在sd卡,然后裁剪图片并将裁剪后的图片保存在指定路径。
流程图如下所示:
四、 选择框实现
这里通过popupwindow来实现,当然也可以根据需求采用其他方式实现。实现效果如下图所示:
1. xml布局
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical"> <linearlayout android:id="@+id/pop_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:background="#444" android:gravity="center_horizontal" android:orientation="vertical"> <button android:id="@+id/picture_selector_take_photo_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="10dip" android:layout_marginright="10dip" android:layout_margintop="10dp" android:background="#4d69ff" android:padding="10dp" android:text="拍照" android:textcolor="#cec9e7" android:textsize="18sp" android:textstyle="bold" /> <button android:id="@+id/picture_selector_pick_picture_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="10dip" android:layout_marginright="10dip" android:layout_margintop="5dp" android:background="#4d69ff" android:padding="10dp" android:text="从相册选择" android:textcolor="#cec9e7" android:textsize="18sp" android:textstyle="bold" /> <button android:id="@+id/picture_selector_cancel_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginbottom="15dip" android:layout_marginleft="10dip" android:layout_marginright="10dip" android:layout_margintop="20dp" android:background="@android:color/white" android:padding="10dp" android:text="取消" android:textcolor="#373447" android:textsize="18sp" android:textstyle="bold" /> </linearlayout> </relativelayout>
2. 代码编写
public selectpicturepopupwindow(context context) { super(context); layoutinflater inflater = (layoutinflater) context.getsystemservice(context.layout_inflater_service); mmenuview = inflater.inflate(r.layout.layout_picture_selector, null); takephotobtn = (button) mmenuview.findviewbyid(r.id.picture_selector_take_photo_btn); pickpicturebtn = (button) mmenuview.findviewbyid(r.id.picture_selector_pick_picture_btn); cancelbtn = (button) mmenuview.findviewbyid(r.id.picture_selector_cancel_btn); // 设置按钮监听 takephotobtn.setonclicklistener(this); pickpicturebtn.setonclicklistener(this); cancelbtn.setonclicklistener(this); }
创建selectpicturepopupwindow的时候设置按钮的监听。这里编写一个选择监听接口:
/** * 选择监听接口 */ public interface onselectedlistener { void onselected(view v, int position); }
回调的参数为点击的按钮view以及当前按钮的索引,那么只要在选择监听里面返回接口的回调就可以啦。
@override public void onclick(view v) { switch (v.getid()) { case r.id.picture_selector_take_photo_btn: if(null != monselectedlistener) { monselectedlistener.onselected(v, 0); } break; case r.id.picture_selector_pick_picture_btn: if(null != monselectedlistener) { monselectedlistener.onselected(v, 1); } break; case r.id.picture_selector_cancel_btn: if(null != monselectedlistener) { monselectedlistener.onselected(v, 2); } break; } }
popupwindow的初始化创建、监听设置好之后,只要提供显示与隐藏两个方法就可以了。
/** * 把一个view控件添加到popupwindow上并且显示 * * @param activity */ public void showpopupwindow(activity activity) { popupwindow = new popupwindow(mmenuview, // 添加到popupwindow viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content); popupwindow.setbackgrounddrawable(new colordrawable(color.transparent)); popupwindow.showatlocation(activity.getwindow().getdecorview(), gravity.center | gravity.bottom, 0, 0); popupwindow.setanimationstyle(android.r.style.animation_inputmethod); // 设置窗口显示的动画效果 popupwindow.setfocusable(false); // 点击其他地方隐藏键盘 popupwindow popupwindow.update(); }
/** * 移除popupwindow */ public void dismisspopupwindow() { if (popupwindow != null && popupwindow.isshowing()) { popupwindow.dismiss(); popupwindow = null; } }
ok,到这里选择框的实现就完成了。
五、使用选择框
通过我们上面对选择框的封装,使用起来就比较简单了,只需要初始化及设置选择的监听就可以啦。
1.初始化选择框
mselectpicturepopupwindow = new selectpicturepopupwindow(mcontext); mselectpicturepopupwindow.setonselectedlistener(this);
2.设置选择框监听
@override public void onselected(view v, int position) { switch (position) { case 0: // todo: "拍照"按钮被点击了 break; case 1: // todo: "从相册选择"按钮被点击了 break; case 2: // todo: "取消"按钮被点击了 break; } }
然后在fragment上进行封装,我们取名为pictureselectfragment。
六、拍照并保存图片
调用系统的拍照,并把拍摄的图片保存到指定位置。
@override public void onselected(view v, int position) { switch (position) { case 0: // "拍照"按钮被点击了 mselectpicturepopupwindow.dismisspopupwindow(); intent takeintent = new intent(mediastore.action_image_capture); //下面这句指定调用相机拍照后的照片存储的路径 takeintent.putextra(mediastore.extra_output, uri.fromfile(new file(mtempphotopath))); startactivityforresult(takeintent, camera_request_code); break; case 1: // todo: "从相册选择"按钮被点击了 break; case 2: // todo: "取消"按钮被点击了 break; } }
这里的指定位置为sd卡本目录下
mtempphotopath = environment.getexternalstoragedirectory() + file.separator + "photo.jpeg";
当拍摄照片完成时会回调到onactivityresult,我们在这里处理图片的裁剪就可以了。
@override public void onactivityresult(int requestcode, int resultcode, intent data) { if (resultcode == mactivity.result_ok) { switch (requestcode) { case camera_request_code: // todo: 调用相机拍照 break; } } super.onactivityresult(requestcode, resultcode, data); }
七、相册选择图片
调用系统的选择图片
@override public void onselected(view v, int position) { switch (position) { case 0: // "拍照"按钮被点击了 mselectpicturepopupwindow.dismisspopupwindow(); intent takeintent = new intent(mediastore.action_image_capture); // 下面这句指定调用相机拍照后的照片存储的路径 takeintent.putextra(mediastore.extra_output, uri.fromfile(new file(mtempphotopath))); startactivityforresult(takeintent, camera_request_code); break; case 1: // "从相册选择"按钮被点击了 mselectpicturepopupwindow.dismisspopupwindow(); intent pickintent = new intent(intent.action_pick, null); // 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型" pickintent.setdataandtype(mediastore.images.media.external_content_uri, "image/*"); startactivityforresult(pickintent, gallery_request_code); break; case 2: // todo: "取消"按钮被点击了 break; } }
当拍选择图片完成时会回调到onactivityresult,在这里处理选择的返回结果。
@override public void onactivityresult(int requestcode, int resultcode, intent data) { if (resultcode == mactivity.result_ok) { switch (requestcode) { case camera_request_code: // todo: 调用相机拍照 break; case gallery_request_code: // todo: 直接从相册获取 break; } } super.onactivityresult(requestcode, resultcode, data); }
八、使用crop裁剪图片
裁剪图片,这里设置宽高比为1:1,最大尺寸为512*512,当然可以根据自己的需求来设置。
/** * 裁剪图片方法实现 * * @param uri */ public void startcropactivity(uri uri) { ucrop.of(uri, mdestinationuri) .withaspectratio(1, 1) .withmaxresultsize(512, 512) .withtargetactivity(cropactivity.class) .start(mactivity, this); }
cropactiivty裁剪完成时会回调到onactivityresult,在这里处理选择的返回结果。
@override public void onactivityresult(int requestcode, int resultcode, intent data) { if (resultcode == mactivity.result_ok) { switch (requestcode) { case camera_request_code: // 调用相机拍照 file temp = new file(mtempphotopath); startcropactivity(uri.fromfile(temp)); break; case gallery_request_code: // 直接从相册获取 startcropactivity(data.getdata()); break; case ucrop.request_crop: // todo: 裁剪图片结果 break; case ucrop.result_error: // todo: 裁剪图片错误 break; } } super.onactivityresult(requestcode, resultcode, data); }
cropactivity的界面如下所示:
当然也可以轻松设计成如下两图:
1. xml布局
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:fab="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:cliptopadding="true" android:fitssystemwindows="true"> <include layout="@layout/toolbar_layout" /> <framelayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/toolbar" android:background="#000"> <com.kevin.crop.view.ucropview android:id="@+id/weixin_act_ucrop" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> </framelayout> <android.support.design.widget.coordinatorlayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.floatingactionbutton android:id="@+id/crop_act_save_fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="@dimen/fab_margin" android:src="@mipmap/ic_done_white" fab:fabsize="normal" /> </android.support.design.widget.coordinatorlayout> </relativelayout>
可以发现非常简单,只有一个主要的cropview,这就是ucrop框架为我们提供的。
2. 代码编写
@override protected void initviews() { inittoolbar(); mgesturecropimageview = mucropview.getcropimageview(); moverlayview = mucropview.getoverlayview(); // 设置允许缩放 mgesturecropimageview.setscaleenabled(true); // 设置禁止旋转 mgesturecropimageview.setrotateenabled(false); // 设置外部阴影颜色 moverlayview.setdimmedcolor(color.parsecolor("#aa000000")); // 设置周围阴影是否为椭圆(如果false则为矩形) moverlayview.setovaldimmedlayer(false); // 设置显示裁剪边框 moverlayview.setshowcropframe(true); // 设置不显示裁剪网格 moverlayview.setshowcropgrid(false); final intent intent = getintent(); setimagedata(intent); }
private void setimagedata(intent intent) { uri inputuri = intent.getparcelableextra(ucrop.extra_input_uri); moutputuri = intent.getparcelableextra(ucrop.extra_output_uri); if (inputuri != null && moutputuri != null) { try { mgesturecropimageview.setimageuri(inputuri); } catch (exception e) { setresultexception(e); finish(); } } else { setresultexception(new nullpointerexception("both input and output uri must be specified")); finish(); } // 设置裁剪宽高比 if (intent.getbooleanextra(ucrop.extra_aspect_ratio_set, false)) { float aspectratiox = intent.getfloatextra(ucrop.extra_aspect_ratio_x, 0); float aspectratioy = intent.getfloatextra(ucrop.extra_aspect_ratio_y, 0); if (aspectratiox > 0 && aspectratioy > 0) { mgesturecropimageview.settargetaspectratio(aspectratiox / aspectratioy); } else { mgesturecropimageview.settargetaspectratio(cropimageview.source_image_aspect_ratio); } } // 设置裁剪的最大宽高 if (intent.getbooleanextra(ucrop.extra_max_size_set, false)) { int maxsizex = intent.getintextra(ucrop.extra_max_size_x, 0); int maxsizey = intent.getintextra(ucrop.extra_max_size_y, 0); if (maxsizex > 0 && maxsizey > 0) { mgesturecropimageview.setmaxresultimagesizex(maxsizex); mgesturecropimageview.setmaxresultimagesizey(maxsizey); } else { log.w(tag, "extra_max_size_x and extra_max_size_y must be greater than 0"); } } }
以上为cropview的配置,更多配置请参考项目源码。
最重要的,裁剪保存图片:
private void cropandsaveimage() { outputstream outputstream = null; try { final bitmap croppedbitmap = mgesturecropimageview.cropimage(); if (croppedbitmap != null) { outputstream = getcontentresolver().openoutputstream(moutputuri); croppedbitmap.compress(bitmap.compressformat.jpeg, 85, outputstream); croppedbitmap.recycle(); setresulturi(moutputuri, mgesturecropimageview.gettargetaspectratio()); finish(); } else { setresultexception(new nullpointerexception("cropimageview.cropimage() returned null.")); } } catch (exception e) { setresultexception(e); finish(); } finally { bitmaploadutils.close(outputstream); } }
pictureselectfragment处理裁剪成功的返回值
/** * 处理剪切成功的返回值 * * @param result */ private void handlecropresult(intent result) { deletetempphotofile(); final uri resulturi = ucrop.getoutput(result); if (null != resulturi && null != monpictureselectedlistener) { bitmap bitmap = null; try { bitmap = mediastore.images.media.getbitmap(mactivity.getcontentresolver(), resulturi); } catch (filenotfoundexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } monpictureselectedlistener.onpictureselected(resulturi, bitmap); } else { toast.maketext(mcontext, "无法剪切选择图片", toast.length_short).show(); } }
处理裁剪失败的返回值
/** * 处理剪切失败的返回值 * * @param result */ private void handlecroperror(intent result) { deletetempphotofile(); final throwable croperror = ucrop.geterror(result); if (croperror != null) { log.e(tag, "handlecroperror: ", croperror); toast.maketext(mcontext, croperror.getmessage(), toast.length_long).show(); } else { toast.maketext(mcontext, "无法剪切选择图片", toast.length_short).show(); } }
这里设置了选择的回调接口,便于封装抽取。
/** * 图片选择的回调接口 */ public interface onpictureselectedlistener { /** * 图片选择的监听回调 * * @param fileuri * @param bitmap */ void onpictureselected(uri fileuri, bitmap bitmap); }
经过五、六、七步骤,我们的pictureselectfragment就搞定了,在使用的时候只要继承它,几行代码就搞定了。
九、pictureselectfragment使用
// 设置图片点击监听 mpictureiv.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { selectpicture(); } });
// 设置裁剪图片结果监听 setonpictureselectedlistener(new onpictureselectedlistener() { @override public void onpictureselected(uri fileuri, bitmap bitmap) { mpictureiv.setimagebitmap(bitmap); string filepath = fileuri.getencodedpath(); string imagepath = uri.decode(filepath); toast.maketext(mcontext, "图片已经保存到:" + imagepath, toast.length_long).show(); } });
ok,经过我们上面的封装及基类抽取,在使用的时候还是非常简单的。
十、下载
更多内容大家可以参考专题《android图片处理》进行学习。
以上就是本文的全部内容,希望对大家学习android软件编程有所帮助。
上一篇: php自定义函数转换html标签示例
下一篇: React的qs插件