https://android-notes.github.io/2016/12/03/android%E9%95%BF%E6%88%AA%E5%B1%8F%E5%8E%9F%E7%90%86/   android长截屏原理

小米系统自带的长截屏应该很多人都用过,效果不错。当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏。

该篇就介绍一下长截屏的原理

上篇中介绍了android屏幕共享实现方式,该篇的原理和上一篇基本一致。

获取view影像

当我们想得到一个view的影像时,我们可以调用系统api,得到view的bitmap,但有时可能得不到。我们可以通过另一种方式得到。

首先创建一个和view一样大小的bitmap

1
Bitmap bmp = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);

然后把view绘制到bmp上

1
2
3
4
5
6
 
Canvas canvas = new Canvas();
 
canvas.setBitmap(bmp);
 
view.draw(canvas);

执行完上面代码后bmp上就是view的影像了。

制造滚动事件,促使view滚动

我们可以创建一个MotionEvent,然后定时修改MotionEvent的y值,并分发给view,从而促使view上下滚动。当然我们也可以定时修改x值促使view左右滚动。

代码大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
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不再滚动,导致最终截图后后面全是重复的最后一屏幕。

附 实现大致方式代码,有待优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
 
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代码
 
```java
 
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();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
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);
}

布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
<?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>

效果图

  • 屏幕
    屏幕
  • 最终截屏

截屏

可以看到毫无拼接痕迹。

来自我的博客

http://blog.csdn.net/qingchunweiliang/article/details/52248643

Android 长截屏原理的更多相关文章

  1. Android长截屏-- ScrollView,ListView及RecyclerView截屏

    http://blog.csdn.net/wbwjx/article/details/46674157       Android长截屏-- ScrollView,ListView及RecyclerV ...

  2. android指纹识别、拼图游戏、仿MIUI长截屏、bilibili最美创意等源码

    Android精选源码 一个动画效果的播放控件,播放,暂停,停止之间的动画 用 RxJava 实现 Android 指纹识别代码 Android仿滴滴打车(滴滴UI)源码 Android高仿哔哩哔哩动 ...

  3. Android系统截屏的实现(附代码)

    1.背景                     写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_ ...

  4. android后台截屏实现(2)--screencap源码修改

    首先找到screencap类在Android源码中的位置,/442/frameworks/base/cmds/screencap/screencap.cpp 源码如下: /* * Copyright ...

  5. Android代码截屏

    本文来源:http://myhpu2008.iteye.com/blog/999779 这种方法应该只能对当前Activity本身进行截屏,因而你只能在你应用程序中参照该代码对其应用程序本身截屏. i ...

  6. android手机截屏、录屏

    1. 手动截屏,通过其他第三方软件发送截图,或者从手机取出截图 2. 使用命令截图,将截图保存到手机,再拉取到电脑 #!/bin/sh #运行 sh screenshot name picName=$ ...

  7. Android手机截屏

    刚开始打算做一个简单的截屏程序时,以为很轻松就能搞定. 在Activity上放一个按钮,点击完成截屏操作,并将数据以图片形式保存在手机中. 动手之前,自然是看书和网上各种查资料.结果发现了解的知识越多 ...

  8. Android滚动截屏,ScrollView截屏

    在做分享功能的时候,需要截取全屏内容,一屏展示不完的内容,一般我们会用到 ListView 或 ScrollView 一: 普通截屏的实现 获取当前Window 的 DrawingCache 的方式, ...

  9. android后台截屏实现(3)--编译screencap

    修改好之后就要编译了,screencap的编译是要在源码环境中进行的. 将修改后的screencap.cpp文件替换源码中的原始文件,然后修改screencap的Android.mk文件,修改后的文件 ...

随机推荐

  1. Apache HttpComponents 获取页面内容String方式

    /* * ==================================================================== * Licensed to the Apache S ...

  2. Timsort 算法

    转载自:http://blog.csdn.net/yangzhongblog/article/details/8184707 Timsort是结合了合并排序(merge sort)和插入排序(inse ...

  3. 免费CA数字证书的申请、安装、导入、导出

    http://wenku.baidu.com/link?url=oDUw50eCE5zX8tmg4N3-ddYGLt1U5aJYGEN7rk_z7t6LuMHL3M4oBstYBI_dQ1UnCtcK ...

  4. map用法小例子

    一. Map< Key , Value > m_Eg; 一般赋值表示成: TypeElem  value; m_Eg[key]  =  value; 或 m_Eg.insert(make_ ...

  5. (转)ALSA配置文件(alsa.conf, asoundrc, asound.conf)及其自动加载 And HDMI Adiuo

    原文出处:http://blog.sina.com.cn/s/blog_a04184c101010kry.html 警告:错误的EDID会造成HDMI发声异常 #title:box:HDMI Audi ...

  6. API Design Principles -- QT Project

    [the original link] One of Qt’s most reputed merits is its consistent, easy-to-learn, powerfulAPI. T ...

  7. Android学习CursorWrapper与Decorator模式 (转至http://www.cnblogs.com/bastard/archive/2012/05/31/2527944.html)

    一 Decorator模式 意图: 动态的给一个对象添加一些额外的职责.就增加功能来说,Decorator模式相比生成子类更为灵活. 动态的给一个对象,而不是对整个类添加额外职责,说明此模式将采用的结 ...

  8. erlang的简单模拟半包的产生

     gen_tcp:linsten()/2使用的是{packet,2/4/8},则gen_tcp模块在接受或者发送时自动除去包头或者自动加上包头. 本例中使用的是{packet,0}. -module( ...

  9. Kafka学习之一深度解析

    背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能 高吞吐 ...

  10. Office密码破解不求人!

    你用Office吗?你会为你的Office文档加密吗?如果Office密码忘了求人吗?最后一个问题是不是让你很头大,求人办事不是要费钱就是要靠人情,不如自己拥有一款强大的密码破解工具,想要Office ...