Android 长截屏原理

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

时间: 2024-10-19 09:39:27

Android 长截屏原理的相关文章

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

http://blog.csdn.net/wbwjx/article/details/46674157       Android长截屏-- ScrollView,ListView及RecyclerView截屏 https://github.com/BoBoMEe/AndroidDev/blob/master/common/common/src/main/java/com/bobomee/android/common/util/ScreenUtil.java /* * Copyright (C)

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

Android精选源码 一个动画效果的播放控件,播放,暂停,停止之间的动画 用 RxJava 实现 Android 指纹识别代码 Android仿滴滴打车(滴滴UI)源码 Android高仿哔哩哔哩动画客户端bilibili源码 android八数码拼图游戏源码 高仿最美创意的一款APP视频应用源码 android恋爱管家完整源码 仿miui自动滚动截屏.长截屏功能实现源码 android拼图游戏源码 Android打造不一样的圆盘签到效果 Android简单易用的TextView装饰库 一个超

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

1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多年前的了,在android4.*版本后,android对于源码进行了更正,使得以前的方法都不能够使用. 感谢cjd6568358这名网友,我们一起讨论,最终由他实现了android系统截屏功能,为了让以后想要这个功能的coder可以少走一些弯路,我们整理的代码做成开源项目. 2.思路 其实主要思路还

Android自动截屏小脚本

Android自动截屏小脚本(脱离手工操作,自动保存到PC上) @echo offecho * 截图文件将保存在 E:\takeshont下,以当前日期+时间命名.echo =================================================echo * 如果在停止截图后,无法删除或者上传,可到任务管理器中删除adb.exe进程echo =================================================================

解决Android studio截屏出错

我的系统是w8.1 64,在eclipse上截屏不会出错,到了android studio上结果出错了 输出: Unexpected error while obtaining screenshot: java.lang.IllegalStateException: @NotNull method com/android/tools/idea/ddms/screenshot/DeviceArtDescriptor.getArtDescriptor must not return null 谷歌之

Android手机截屏

刚开始打算做一个简单的截屏程序时,以为很轻松就能搞定. 在Activity上放一个按钮,点击完成截屏操作,并将数据以图片形式保存在手机中. 动手之前,自然是看书和网上各种查资料.结果发现了解的知识越多,就越发感觉不对劲. 截屏,总以为其类似于其他小应用的开发,有现成的接口或者只需要稍微改动就能达到预期的效果. 一般讲解Android的书籍并没有提到截屏的内容,网上的文章很多,但也没有哪篇能是真正完整,能把解决思路说清楚的. 总结的比较合理的一篇文章题目为“Android截屏学习经历”,出自“ht

android --手机截屏

方式一.调用 GetandSaveCurrentImage()方法即可 /** * 获取和保存当前屏幕的截图 */ private void GetandSaveCurrentImage() { // 构建Bitmap WindowManager windowManager = getWindowManager(); Display display = windowManager.getDefaultDisplay(); int w = display.getWidth(); int h = d

Android自定义截屏功能,类似QQ截屏

因为公司业务需求 需要对一个屏幕进行截屏,但自带的截屏功能是远远不够项目的功能需求 ,我们是做一个画板软件 ,需要的像QQ那样截屏之后 ,可以看到我们自定义的工具,有画笔,按钮等等 .android自带的功能非常简单,只需要Intent隐式调用就完全足够了,但他是系统的应用 ,界面固定,无法定制修改.实现方法跟办法有很多种,下面记录下我实现的方法 .我是这样一个思路 ,重写一个View组件 ,在OnDraw里面只负责不画图形(包括半透明的四个矩形,亮框矩形,亮框上的四个小圆点),Ontouch方

Android 系统截屏与系统内置DVR录像冲突,导致SystemUI重启的问题解决与分享

上周六加班在解决一个关于SystemUI内嵌的DVR录像与系统截屏操作冲突的问题,介于问题的复杂性,所以我把这个分享出来便 于以后自己更加的理解,又方便以后遇到此问题的同行能够提供一些帮助,若有疑问可向鄙人的博客提供你的宝贵意见! 首先我们需要找到系统截屏的按键定义,并且知道它在哪里执行的,先摈弃从硬件底层的协议,我们直接从framework层开始 讲,因为底层底层硬件返回的结果由.c.o.h这些文件,再由Binder aidl 将结果给到framework,所以我们就从开始从framework