项目地址: 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 不嵌套时,不管是否滚动,都能得到正确的结果

android滚动截长屏 安卓系统滚动截屏_android滚动截长屏

但 scrollview 滚动后即使顶部的已经看不到了,但调用 scrollview 的 draw 时还是会把 scrollview 不可见的地方画进去 

android滚动截长屏 安卓系统滚动截屏_android滚动截长屏_02

为了通用起间,我们给 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);
        }
    }