Android实现矩形区域截屏的方法
对屏幕进行截屏并裁剪有两种方式:早截图和晚截图。早截图,就是先截取全屏,再让用户对截取到的图片进行修改;与之相对的,晚截图,就是先让用户在屏幕上划好区域,再进行截图和裁剪。其实两者并没有什么太大的区别,这篇就说说怎么实现晚截图。
晚截图可以分成三步:
1. 在屏幕上标出截图的矩形区域
2. 调用系统接口截屏
3. 对截图进行裁剪
效果图如下:
第一步、在屏幕上标识出截图区域
首先确定标识截图区域所需要的功能:
1. 手指拖动形成矩形区域;
2. 可以拖动已经划好的矩形区域进行移动;
3. 可以拖动矩形区域的边框调整大小;
4. 选择完成以后,有“确认”和“取消”功能,“确认”时可以获得选取的区域位置。需要注意的是,按钮的位置应该能够自适应,比如选框几乎占据全屏的情况下,应该把按钮放到选框内部。
最简单的方式就是写一个自定义view,根据touch的位置执行不同的功能即可。实现很简单,只要细心把每一种状态就行,代码请看bigbang项目的marksizeview类。
第二步、调用系统接口截屏
截屏必须在activity中进行,因为需要调用startactivityforresult()。不过也可以把mmediaprojectionmanager传到service中进行后续处理。
还要注意的是activity本身在截屏的时候应该是透明的,不能对要截取得内容有影响。
直接看代码:
public class screencaptureactivity extends activity { private static final string tag = screencaptureactivity.class.getname(); private mediaprojectionmanager mmediaprojectionmanager; private int request_media_projection = 1; private simpledateformat dateformat; private string pathimage; private windowmanager mwindowmanager; private imagereader mimagereader; private mediaprojection mmediaprojection; private int mresultcode; private intent mresultdata; private virtualdisplay mvirtualdisplay; private string strdate; private int windowwidth; private int windowheight; private string nameimage; private int mscreendensity; @requiresapi(api = build.version_codes.lollipop) @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); mmediaprojectionmanager = (mediaprojectionmanager) getapplication().getsystemservice(context.media_projection_service); createvirtualenvironment(); startactivityforresult(mmediaprojectionmanager.createscreencaptureintent(), request_media_projection); } @targetapi(build.version_codes.lollipop) @override public void onactivityresult(int requestcode, int resultcode, intent data) { if (requestcode == request_media_projection) { if (resultcode != activity.result_ok) { return; } else if (data != null && resultcode != 0) { mresultcode = resultcode; mresultdata = data; startvirtual(); new handler(looper.getmainlooper()).postdelayed(new runnable() { @override public void run() { startcapture(); } },100); } } } @requiresapi(api = build.version_codes.kitkat) private void createvirtualenvironment() { dateformat = new simpledateformat("yyyy_mm_dd_hh_mm_ss"); strdate = dateformat.format(new date()); pathimage = environment.getexternalstoragedirectory().getpath() + "/pictures/"; nameimage = pathimage + strdate + ".png"; mmediaprojectionmanager = (mediaprojectionmanager) getapplication().getsystemservice(context.media_projection_service); mwindowmanager = (windowmanager) getapplication().getsystemservice(context.window_service); windowwidth = mwindowmanager.getdefaultdisplay().getwidth(); windowheight = mwindowmanager.getdefaultdisplay().getheight(); displaymetrics metrics = new displaymetrics(); mwindowmanager.getdefaultdisplay().getmetrics(metrics); mscreendensity = metrics.densitydpi; mimagereader = imagereader.newinstance(windowwidth, windowheight, 0x1, 2); //imageformat.rgb_565 log.i(tag, "prepared the virtual environment"); } @targetapi(build.version_codes.lollipop) public void startvirtual() { if (mmediaprojection != null) { log.i(tag, "want to display virtual"); virtualdisplay(); } else { log.i(tag, "start screen capture intent"); log.i(tag, "want to build mediaprojection and display virtual"); setupmediaprojection(); virtualdisplay(); } } @targetapi(build.version_codes.lollipop) public void setupmediaprojection() { mmediaprojection = mmediaprojectionmanager.getmediaprojection(mresultcode, mresultdata); log.i(tag, "mmediaprojection defined"); } @targetapi(build.version_codes.lollipop) private void virtualdisplay() { mvirtualdisplay = mmediaprojection.createvirtualdisplay("screen-mirror", windowwidth, windowheight, mscreendensity, displaymanager.virtual_display_flag_auto_mirror, mimagereader.getsurface(), null, null); log.i(tag, "virtual displayed"); } @targetapi(build.version_codes.lollipop) private void startcapture() { strdate = dateformat.format(new java.util.date()); nameimage = pathimage + strdate + ".png"; image image = mimagereader.acquirelatestimage(); int width = image.getwidth(); int height = image.getheight(); final image.plane[] planes = image.getplanes(); final bytebuffer buffer = planes[0].getbuffer(); int pixelstride = planes[0].getpixelstride(); int rowstride = planes[0].getrowstride(); int rowpadding = rowstride - pixelstride * width; bitmap bitmap = bitmap.createbitmap(width + rowpadding / pixelstride, height, bitmap.config.argb_8888); bitmap.copypixelsfrombuffer(buffer); bitmap = bitmap.createbitmap(bitmap, 0, 0, width, height); image.close(); log.i(tag, "image data captured"); //保存截屏结果,如果要裁剪图片,在这里处理bitmap if (bitmap != null) { try { file fileimage = new file(nameimage); if (!fileimage.exists()) { fileimage.createnewfile(); log.i(tag, "image file created"); } fileoutputstream out = new fileoutputstream(fileimage); if (out != null) { bitmap.compress(bitmap.compressformat.png, 100, out); out.flush(); out.close(); intent media = new intent(intent.action_media_scanner_scan_file); uri contenturi = uri.fromfile(fileimage); media.setdata(contenturi); this.sendbroadcast(media); log.i(tag, "screen image saved"); } } catch (filenotfoundexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } } } @targetapi(build.version_codes.lollipop) private void teardownmediaprojection() { if (mmediaprojection != null) { mmediaprojection.stop(); mmediaprojection = null; } log.i(tag, "mmediaprojection undefined"); } }
第三步、对截图进行裁剪
根据第一步得到的截图区域mrect对第二步中得到的截屏结果bitmap进行裁剪:
if (mrect != null) { if (mrect.left < 0) mrect.left = 0; if (mrect.right < 0) mrect.right = 0; if (mrect.top < 0) mrect.top = 0; if (mrect.bottom < 0) mrect.bottom = 0; int cut_width = math.abs(mrect.left - mrect.right); int cut_height = math.abs(mrect.top - mrect.bottom); if (cut_width > 0 && cut_height > 0) { bitmap cutbitmap = bitmap.createbitmap(bitmap, mrect.left, mrect.top, cut_width, cut_height); }
需要注意的是,在调用系统截屏功能的时候,如果手机有navigationbar(虚拟导航栏),windowheight的取值就是不包括navigationbar的高度的,如果不进行调整,就会导致截屏被压缩。如何获取屏幕的真实高度,可以参考android如何判断navigationbar是否显示(获取屏幕真实的高度)。
而且navigationbar还会导致截屏的结果出现边框,边框的颜色是透明的,原因是第二步代码中的rowpadding!=0,截屏如下图所示:
那么如果我们想要对截图的结果进行保存或者裁剪,就必须要去除边框,找出真正的内容区域,也就是在第一个不透明的像素和最后一个不透明像素之间的内容,然后才能对得到的区域进行第三步的裁剪,代码如下:
int[] pixel=new int[width]; bitmap.getpixels(pixel,0,width ,0,0,width,1); int leftpadding=0; int rightpadding=width; for (int i=0;i<pixel.length;i++){ if (pixel[i]!=0){ leftpadding=i; break; } } for (int i=pixel.length-1;i>=0;i--){ if (pixel[i]!=0){ rightpadding=i; break; } } bitmap=bitmap.createbitmap(bitmap,leftpadding, 0, rightpadding-leftpadding, height);
处理后的截图如下:
你可能会觉得既然是rowpadding!=0导致出现边框,而且边框只在右边,为什么不直接把右边rowpadding宽度的内容截掉呢?其实是因为如果不调整windowheight,就会在左边也产生框,所以才用了上面的方法。
完整代码可以参考bigbang项目的marksizeview类、screencaptureactivity类和screencapture类。
上一篇: Leaf 详解
下一篇: DB2编程序技巧 (八)