android长截屏原理及实现代码
小米系统自带的长截屏应该很多人都用过,效果不错。当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏。
该篇就介绍一下长截屏的原理
上篇中介绍了android屏幕共享实现方式,该篇的原理和上一篇基本一致。
获取view影像
当我们想得到一个view的影像时,我们可以调用系统api,得到view的bitmap,但有时可能得不到。我们可以通过另一种方式得到。
首先创建一个和view一样大小的bitmap
bitmap bmp = bitmap.createbitmap(view.getwidth(), view.getheight(), bitmap.config.rgb_565);
然后把view绘制到bmp上
canvas canvas = new canvas(); canvas.setbitmap(bmp); view.draw(canvas);
执行完上面代码后bmp上就是view的影像了。
制造滚动事件,促使view滚动
我们可以创建一个motionevent,然后定时修改motionevent的y值,并分发给view,从而促使view上下滚动。当然我们也可以定时修改x值促使view左右滚动。
代码大致如下
final motionevent motionevent = motionevent.obtain(systemclock.uptimemillis(), systemclock.uptimemillis(), motionevent.action_down, view.getwidth() / 2, view.getheight() / 2, 0); view.postdelayed(new runnable() { @override public void run() { motionevent.setaction(motionevent.action_move); motionevent.setlocation((int) motionevent.getx(), (int) motionevent.gety() - 1); //把事件分发给view view.dispatchtouchevent(motionevent); view.postdelayed(this, delay); } }, delay);
注意:从分发down事件到结束都要使用同一个motionevent对象,只需要不断改变x或y值。
每次x或y的值相对于上次改动不能过大,若过大,view实际滚动距离可能达不到为motionevent设置的值(因view滚动时卡顿导致)。
截屏
当为motionevent设置的x或y值正好时当前view的大小时,创建新的bitmap,通过上述方法把view绘制到bitmap上,想要停止截屏时拼接所有bitmap即可。
备注
当我们想要把listview长截屏时,需要为listview外面嵌套一层和listview一样大小的view,以上的所有操作都在嵌套的这层view上操作。当我们调用嵌套的这层view的draw(new canvas(bmp))时会把当前看到的这块listview绘制到bmp上,不管listview嵌套了多少层子view都可以绘制到当前bmp上。
由于listview中根据滑动的距离是否大于viewconfiguration.get(view.getcontext()).getscaledtouchslop() )来确定要不要滚动,所以一开始我们要特殊处理下,为什么是viewconfiguration.get(view.getcontext()).getscaledtouchslop() )可以查看listview的事件分发相关函数得到(dispatchtouchevent),让listview认为是开始滚动,这样才能保证以后分发的滑动距离和实际滚动距离一致。
listview也要通知是否滚动到了最后,不然如果没有手动停止的话,虽然还是在一直分发滚动事件,但listview不再滚动,导致最终截图后后面全是重复的最后一屏幕。
附 实现大致方式代码,有待优化
package com.example.wanjian.test; import android.graphics.bitmap; import android.graphics.canvas; import android.graphics.color; import android.os.environment; import android.os.systemclock; import android.view.motionevent; import android.view.view; import android.view.viewconfiguration; import android.widget.linearlayout; import android.widget.toast; import java.io.file; import java.io.fileoutputstream; import java.lang.ref.weakreference; import java.util.arraylist; import java.util.list; /** * created by wanjian on 16/8/18. */ public class scrollableviewrecutil { public static final int vertical = 0; private static final int delay = 2; private list<bitmap> bitmaps = new arraylist<>(); private int orientation = vertical; private view view; private boolean isend; private onrecfinishedlistener listener; public scrollableviewrecutil(view view, int orientation) { this.view = view; this.orientation = orientation; } public void start(final onrecfinishedlistener listener) { this.listener = listener; final motionevent motionevent = motionevent.obtain(systemclock.uptimemillis(), systemclock.uptimemillis(), motionevent.action_down, view.getwidth() / 2, view.getheight() / 2, 0); view.dispatchtouchevent(motionevent); motionevent.setaction(motionevent.action_move); //滑动距离大于viewconfiguration.get(view.getcontext()).getscaledtouchslop()时listview才开始滚动 motionevent.setlocation(motionevent.getx(), motionevent.gety() - (viewconfiguration.get(view.getcontext()).getscaledtouchslop() + 1)); view.dispatchtouchevent(motionevent); motionevent.setlocation(motionevent.getx(), view.getheight() / 2); view.postdelayed(new runnable() { @override public void run() { if (isend) { //停止时正好一屏则全部绘制,否则绘制部分 if ((view.getheight() / 2 - (int) motionevent.gety()) % view.getheight() == 0) { bitmap bitmap = rec(); bitmaps.add(bitmap); } else { bitmap origbitmap = rec(); int y = view.getheight() / 2 - (int) motionevent.gety(); bitmap bitmap = bitmap.createbitmap(origbitmap, 0, view.getheight() - y % view.getheight(), view.getwidth(), y % view.getheight()); bitmaps.add(bitmap); origbitmap.recycle(); } //最后一张可能高度不足view的高度 int h = view.getheight() * (bitmaps.size() - 1); bitmap bitmap = bitmaps.get(bitmaps.size() - 1); h = h + bitmap.getheight(); bitmap result = bitmap.createbitmap(view.getwidth(), h, bitmap.config.rgb_565); canvas canvas = new canvas(); canvas.setbitmap(result); for (int i = 0; i < bitmaps.size(); i++) { bitmap b = bitmaps.get(i); canvas.drawbitmap(b, 0, i * view.getheight(), null); b.recycle(); } listener.onrecfinish(result); return; } if ((view.getheight() / 2 - (int) motionevent.gety()) % view.getheight() == 0) { bitmap bitmap = rec(); bitmaps.add(bitmap); } motionevent.setaction(motionevent.action_move); //模拟每次向上滑动一个像素,这样可能导致滚动特别慢,实际使用时可以修改该值,但判断是否正好滚动了 //一屏幕就不能简单的根据 (view.getheight() / 2 - (int) motionevent.gety()) % view.getheight() == 0 来确定了。 //可以每次滚动n个像素,当发现下次再滚动n像素时就超出一屏幕时可以改变n的值,保证下次滚动后正好是一屏幕, //这样就可以根据(view.getheight() / 2 - (int) motionevent.gety()) % view.getheight() == 0来判断要不要截屏了。 motionevent.setlocation((int) motionevent.getx(), (int) motionevent.gety() - 1); view.dispatchtouchevent(motionevent); view.postdelayed(this, delay); } }, delay); } public void stop() { isend = true; } private bitmap rec() { bitmap film = bitmap.createbitmap(view.getwidth(), view.getheight(), bitmap.config.rgb_565); canvas canvas = new canvas(); canvas.setbitmap(film); view.draw(canvas); return film; } public interface onrecfinishedlistener { void onrecfinish(bitmap bitmap); } }
activity代码
setcontentview(r.layout.activity_main4); // listview= (listview) findviewbyid(r.id.listview); listview.setadapter(new baseadapter() { @override public int getcount() { return 100; } @override public object getitem(int position) { return null; } @override public long getitemid(int position) { return 0; } @override public view getview(int position, view convertview, viewgroup parent) { if (convertview==null){ button button= (button) layoutinflater.from(getapplication()).inflate(r.layout.item,listview,false); button.settext(""+position); return button; } ((button)convertview).settext(""+position); return convertview; } }); // file file=new file(environment.getexternalstoragedirectory(),"aaa"); file.mkdirs(); for (file f:file.listfiles()){ f.delete(); } listview.getviewtreeobserver().addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() { @override public void ongloballayout() { listview.getviewtreeobserver().removeglobalonlayoutlistener(this); start(); } });
private void start(){ final view view=findviewbyid(r.id.view); final scrollableviewrecutil scrollableviewrecutil=new scrollableviewrecutil(view,scrollableviewrecutil.vertical); scrollableviewrecutil.start(new scrollableviewrecutil.onrecfinishedlistener() { @override public void onrecfinish(bitmap bitmap) { file f= environment.getexternalstoragedirectory(); system.out.print(f.getabsolutefile().tostring()); toast.maketext(getapplicationcontext(),f.getabsolutepath(),toast.length_long).show(); try { bitmap.compress(bitmap.compressformat.jpeg,60,new fileoutputstream(new file(f,"rec"+system.currenttimemillis()+".jpg"))); toast.maketext(getapplicationcontext(),"success",toast.length_long).show(); }catch (exception e){ e.printstacktrace(); } } }); // scrollableviewrecutil view.postdelayed(new runnable() { @override public void run() { scrollableviewrecutil.stop(); } },90*1000); }
布局
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/view" android:orientation="vertical" > <listview android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="#e1e1e1" android:dividerheight="2dp" ></listview> </linearlayout>
效果图
屏幕
最终截屏
可以看到毫无拼接痕迹。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。