当前位置: 移动技术网 > IT编程>移动开发>Android > Android实现拍照、选择图片并裁剪图片功能

Android实现拍照、选择图片并裁剪图片功能

2019年07月24日  | 移动技术网IT编程  | 我要评论

与卓越同行,野菊花茶的功效与作用,精子大战美女

一、 实现拍照、选择图片并裁剪图片效果

按照之前博客的风格,首先看下实现效果。

    

二、 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-crop库

更多内容大家可以参考专题《android图片处理》进行学习。

以上就是本文的全部内容,希望对大家学习android软件编程有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网