项目地址: android-notes/auto-scroll-capture
简介:跟 miui 一样的自动滚动截屏
miui 自动滚动长截屏效果
画
- 给滚动控件外面嵌套一个
FrameLayout
- (
LinearLayout
- 等也可以)
- 手动调用
FrameLayout
- 的
draw
- 方法把
view
- 画到
bitmap
- 上
Bitmap bitmap = Bitmap.createBitmap(container.getWidth(), container.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
container.draw(canvas);
具体参考 DrawScrollViewActDrawListViewAct
滚
- 通过不断改变 motionEvent 的 y 值并手动调用 view 的
dispatchTouchEvent
- 方法实现
view
- 滚动
private void autoScroll() {
final int delay = 16;
final MotionEvent motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis()
, SystemClock.uptimeMillis()
, MotionEvent.ACTION_DOWN
, listView.getWidth() / 2
, listView.getHeight() / 2
, 0);
listView.dispatchTouchEvent(motionEvent);//先分发 MotionEvent.ACTION_DOWN 事件
listView.postDelayed(new Runnable() {
@Override
public void run() {
motionEvent.setAction(MotionEvent.ACTION_MOVE); //延时分发 MotionEvent.ACTION_MOVE 事件
//改变 y 坐标,越大滚动越快,但太大可能会导致掉帧
motionEvent.setLocation((int) motionEvent.getX(), (int) motionEvent.getY() - 10);
listView.dispatchTouchEvent(motionEvent);
listView.postDelayed(this, delay);
}
}, delay);
}
参考 ScrollAct
自动滚动效果
截屏
- 每自动滚动完一屏幕调用
view.draw()
- 把
view
- 画到
bitmap
- 上,最后拼接 bitmap
参考 AutoScreenShotsAct
为什么要嵌套一层 view
listview 不嵌套时,不管是否滚动,都能得到正确的结果
但 scrollview 滚动后即使顶部的已经看不到了,但调用 scrollview 的 draw 时还是会把 scrollview 不可见的地方画进去
为了通用起间,我们给 view 外面嵌套了一层 view
代码实现
关键逻辑如下,但有些细节还需要具体对待
private void autoScroll() {
final int delay = 16;//延时 16 毫秒分发滑动事件
final int step = 10;//每次滑动距离 5 像素,可以根据需要调整(若卡顿的话实际滚动距离可能小于 5)
final MotionEvent motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis()
, SystemClock.uptimeMillis()
, MotionEvent.ACTION_DOWN
, listView.getWidth() / 2
, listView.getHeight() / 2
, 0);
//先分发 MotionEvent.ACTION_DOWN 事件,我们指定为按下位置是 listview 的中间位置,当然其他位置也可以
listView.dispatchTouchEvent(motionEvent);
/*
注意:
查看 Listview 源码可知 滑动距离大于 ViewConfiguration.get(view.getContext()).getScaledTouchSlop()时 listview 才开始滚动
private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int deltaY = y - mMotionY;
final int distance = Math.abs(deltaY);
final boolean overscroll = mScrollY != 0;
if ((overscroll || distance > mTouchSlop) && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
...
return true;
}
return false;
}
*/
motionEvent.setAction(MotionEvent.ACTION_MOVE);
motionEvent.setLocation(motionEvent.getX(), motionEvent.getY() - (ViewConfiguration.get(listView.getContext()).getScaledTouchSlop()));
listView.dispatchTouchEvent(motionEvent);
final int startScrollY = (int) motionEvent.getY();
listView.postDelayed(new Runnable() {
@Override
public void run() {
if (isScreenShots == false) {//注意:我们无法通过滚动距离来判断是否滚动到了最后,所以需要通过其他方式停止滚动
drawRemainAndAssemble(startScrollY, (int) motionEvent.getY());
return;
}
//滚动刚好一整屏时画到 bitmap 上
drawIfNeeded(startScrollY, (int) motionEvent.getY());
motionEvent.setAction(MotionEvent.ACTION_MOVE); //延时分发 MotionEvent.ACTION_MOVE 事件
/*
改变 motionEvent 的 y 坐标,nextStep 越大滚动越快,但太大可能会导致掉帧,导致实际滚动距离小于我们滑动的距离
因为我们是根据(curScrollY - startScrollY) % container.getHeight() == 0 来判定是否刚好滚动了一屏幕的,
所以快要滚动到刚好一屏幕位置时,修改 nextStep 的值,使下次滚动后刚好是一屏幕的距离。
当然 nextStep 也可以一直是 1,这时就不需要凑整了,但这样会导致滚动的特别慢
*/
int nextStep;
int gap = (startScrollY - (int) motionEvent.getY() + step) % container.getHeight();
if (gap > 0 && gap < step) {
nextStep = step - gap;
} else {
nextStep = step;
}
motionEvent.setLocation((int) motionEvent.getX(), (int) motionEvent.getY() - nextStep);
listView.dispatchTouchEvent(motionEvent);
listView.postDelayed(this, delay);
}
}, delay);
}
private void drawRemainAndAssemble(int startScrollY, int curScrollY) {
//最后的可能不足一屏幕,需要单独处理
if ((curScrollY - startScrollY) % container.getHeight() != 0) {
Bitmap film = Bitmap.createBitmap(container.getWidth(), container.getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas();
canvas.setBitmap(film);
container.draw(canvas);
int part = (startScrollY - curScrollY) / container.getHeight();
int remainHeight = startScrollY - curScrollY - container.getHeight() * part;
Bitmap remainBmp = Bitmap.createBitmap(film, 0, container.getHeight() - remainHeight, container.getWidth(), remainHeight);
bitmaps.add(remainBmp);
}
assembleBmp();
}
private void assembleBmp() {
int h = 0;
for (Bitmap bitmap : bitmaps) {
h += bitmap.getHeight();
}
//如果你需要透明度或者对图片质量要求很高的话请使用 Config.ARGB_8888
Bitmap bitmap = Bitmap.createBitmap(container.getWidth(), h, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
for (Bitmap b : bitmaps) {
canvas.drawBitmap(b, 0, 0, null);
canvas.translate(0, b.getHeight());
}
ViewGroup.LayoutParams params = img.getLayoutParams();
params.width = bitmap.getWidth() * 2;
params.height = bitmap.getHeight() * 2;
img.requestLayout();
img.setImageBitmap(bitmap);
}
private void drawIfNeeded(int startScrollY, int curScrollY) {
if ((curScrollY - startScrollY) % container.getHeight() == 0) {
//正好滚动满一屏
//为了更通用,我们是把 ListView 的父布局(和 ListView 宽高相同)画到了 bitmap 上
Bitmap film = Bitmap.createBitmap(container.getWidth(), container.getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas();
canvas.setBitmap(film);
container.draw(canvas);
bitmaps.add(film);
}
}