长截图实现流程

LongScreenShotTile.handleClick

    @Override
    protected void handleClick() {
        if (ActivityManager.isUserAMonkey()) {
            return;
        }
        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
        setIsInLongshot(1);
        mHost.collapsePanels();
        mHandler.removeCallbacks(mScreenshotRunnable);
        mHandler.postDelayed(mScreenshotRunnable, 500);
    }

mScreenshotRunnable,调用takeScreenshot,关注参数WindowManager.TAKE_LONG_SCREENSHOT_SELECTED_REGION

final Runnable mScreenshotRunnable = new Runnable() {
        @Override
        public void run() {
            takeScreenshot(WindowManager.TAKE_LONG_SCREENSHOT_SELECTED_REGION);
        }
    };

    // Assume this is called from the Handler thread.
    private void takeScreenshot(final int screenshotType) {
        synchronized (mScreenshotLock) {
            if (mScreenshotConnection != null) {
                mContext.unbindService(mScreenshotConnection);
                mScreenshotConnection = null;
            }
            final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
                    SYSUI_SCREENSHOT_SERVICE);
            final Intent serviceIntent = new Intent();
            serviceIntent.setComponent(serviceComponent);
            ServiceConnection conn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mScreenshotLock) {
                        if (mScreenshotConnection != this) {
                            return;
                        }
                        Messenger messenger = new Messenger(service);
                        Message msg = Message.obtain(null, screenshotType);
                        final ServiceConnection myConn = this;
                        Handler h = new Handler(mHandler.getLooper()) {
                            @Override
                            public void handleMessage(Message msg) {
                                synchronized (mScreenshotLock) {
                                    if (mScreenshotConnection == myConn) {
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection = null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);
                                    }
                                }
                            }
                        };
                        msg.replyTo = new Messenger(h);
                        msg.arg1 = msg.arg2 = 0;
                        try {
                            messenger.send(msg);
                        } catch (RemoteException e) {
                        }
                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    notifyScreenshotError();
                }
            };
            if (mContext.bindServiceAsUser(serviceIntent, conn,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.CURRENT)) {
                mScreenshotConnection = conn;
                //mHandler.postDelayed(mScreenshotTimeout, 25000);
            }
        }
    }

流程转到TakeScreenshotService.java中

                case WindowManager.TAKE_LONG_SCREENSHOT_SELECTED_REGION:
                    if (LONG_SCREENSHOT_ENABLED) {
                        mScreenshot.takeRegionScreenshot(finisher);
                    } else {
                       Toast.makeText(TakeScreenshotService.this, "long screenshot disabled",  Toast.LENGTH_SHORT).show();
                    }
                    break;

流程转到mScreenshot,即GlobalScreenshot.java,该类为整个流程的重点类

在这里插入图片描述长截图实际样式图

观察上图布局,是由两个layout组成,黑色背景和图片是一个layout,上面的三个按钮和底部start long screen shot是一个布局。
    //GlobalScreenShot的构造方法。主要就是初始化layout及findViewById,以及设置WindowManager.LayoutParams
    public GlobalScreenshot(Context context) {
        Resources r = context.getResources();
        mContext = context;
        mResolver = mContext.getContentResolver();
        LayoutInflater layoutInflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Inflate the screenshot layout
        mDisplayMatrix = new Matrix();
        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
        mMoreActionLayout = layoutInflater.inflate(R.layout.global_screenshot_more_action, null);
        btnScrollMode = (Button) mMoreActionLayout.findViewById(R.id.btn_scroll_mode);
        btnScrollMode.setVisibility(View.INVISIBLE);
        //btnScrollMode.setText(mContext.getString(R.string.start_long_screen_shot));
        mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
        mScreenshotView = (ScreenShotImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
        //mScreenshotView.setLimitedHeight(r.getDisplayMetrics().heightPixels * 2);
        scrollView = (ScrollView) mScreenshotLayout.findViewById(R.id.scrollview);
        bottomFrame = (FrameLayout) mScreenshotLayout.findViewById(R.id.screenshotview_bottom);
        mSecondScreenshotView = (ScreenShotImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_bottom);
        mSecondScreenshotView.setVisibility(View.GONE);
        mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
        mScreenshotSelectorView = (ScreenshotSelectorView) mMoreActionLayout.findViewById(R.id.screen_shot_select_view);
        mScreenshotLayout.setFocusable(true);
        mScreenshotSelectorView.setFocusable(true);
        mScreenshotSelectorView.setFocusableInTouchMode(true);
        mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // Intercept and ignore all touch events
                return true;
            }
        });
        mMoreActionLayout.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
            @Override
            public void onViewDetachedFromWindow(View v) {
                setIsInLongshot(0);
            }

            @Override
            public void onViewAttachedToWindow(View v) {
                float degrees = getDegreesForRotation(mDisplay.getRotation());
                if (degrees > 0) {
                    stopScreenshot();
                    setIsInLongshot(0);
                    if(mFinisher != null) {
                        mFinisher.run();
                    }
                    Toast.makeText(mContext, R.string.screen_shot_landscape_tips, Toast.LENGTH_SHORT).show();
                }
            }
        });

        // Inflate the partial screenshot layout
        mResolver = mContext.getContentResolver();
        mBitmapResultList = new ArrayList<Bitmap>();

        // Setup the window that we are going to use
        mWindowLayoutParams = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                WindowManager.LayoutParams.TYPE_SCREENSHOT,
                WindowManager.LayoutParams.FLAG_FULLSCREEN
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                PixelFormat.TRANSLUCENT);
        mWindowLayoutParams.setTitle("ScreenshotAnimation");
        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mNotificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mDisplay = mWindowManager.getDefaultDisplay();
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);

        // Get the various target sizes
        mNotificationIconSize =
            r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);

        // Scale has to account for both sides of the bg
        mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
        mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;

        // determine the optimal preview size
        int panelWidth = 0;
        try {
            panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
        } catch (Resources.NotFoundException e) {
        }
        if (panelWidth <= 0) {
            // includes notification_panel_width==match_parent (-1)
            panelWidth = mDisplayMetrics.widthPixels;
        }
        mPreviewWidth = panelWidth;
        mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height);

        // Setup the Camera shutter sound
        mCameraSound = new MediaActionSound();
        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
        /* SPRD:Bug 900116 @{*/
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        /* @} */
    }

然后来看调过来的takeRegionScreenshot(),该方法的关键地方为

takeScreenshotNoAnimation(finisher, 0, 0, mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels);

void takeRegionScreenshot(Runnable finisher) {
        if(isInLongscreenshot()) {
            Log.d(TAG, "current aready in mode!" + isInLongscreenshot());
            return;
        }
        setIsInLongshot(1);
        scrollView.setVisibility(View.VISIBLE);
        mFinisher = finisher;
        Log.d(TAG, "takeRegionScreenshot");
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        String checkRes = null;
        if (degrees > 0) checkRes = mContext.getString(R.string.screen_shot_landscape_tips);
        if (!TextUtils.isEmpty(checkRes)) {
            Toast info = Toast.makeText(mContext, checkRes, Toast.LENGTH_SHORT);
            info.show();
            finisher.run();
            setIsInLongshot(0);
            return;
        }
        mDisplay.getRealMetrics(mDisplayMetrics);
        takeScreenshotNoAnimation(finisher, 0, 0, mDisplayMetrics.widthPixels,
                mDisplayMetrics.heightPixels);

        Log.d(TAG, "(int) mDisplay.getWidth():" + (int) mDisplay.getWidth() + " (int) mDisplay.getHeight():" + (int) mDisplay.getHeight());
        mScreenshotSelectorView = (ScreenshotSelectorView) mMoreActionLayout.findViewById(R.id.screen_shot_select_view);
        mScreenshotSelectorView.setCurrentForLongScreenShot(true);
        mScreenshotSelectorView.setFocusable(true);
        mScreenshotSelectorView.setFocusableInTouchMode(true);
        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        view.updateCurrentSelectionCircle((int) event.getX(), (int) event.getY());
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        view.updateRegionSelection((int) event.getX(), (int) event.getY());
                        return true;
                    case MotionEvent.ACTION_UP:
                        return true;
                }

                return false;
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
                mScreenshotSelectorView.setVisibility(View.VISIBLE);
                mScreenshotSelectorView.requestFocus();
                mSecondScreenshotView.setVisibility(View.GONE);
            }
        });
    }

在来看下takeScreenshotNoAnimation(),该方法首先调用native方法SurfaceControl.screenshot,截图。然后通过WindowManager.addView将之前加载的显示截图的layout添加进来。此处还有一个createScreenshotDropInAnimation,截图看到是将截到的图缩小了显示出来的,这是通过DropIn来做的。

/**
     * Takes a screenshot of the current display and shows an animation.
     */
    void takeScreenshotNoAnimation(Runnable finisher, int x, int y, int width, int height) {
        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
        // only in the natural orientation of the device :!)
        mDisplay.getRealMetrics(mDisplayMetrics);
        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        boolean requiresRotation = (degrees > 0);
        if (requiresRotation) {
            // Get the dimensions of the device in its native orientation
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);
        }

        int rot = mDisplay.getRotation();
        Rect sourceCrop = new Rect(0, 0, (int) dims[0], (int) dims[1]);
        // Take the screenshot
        mScreenBitmap = SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot);
        Log.d(TAG, "takeScreenshotNoAnimation");
        if (mScreenBitmap == null) {
            notifyScreenshotError(mContext, mNotificationManager,
                    R.string.screenshot_failed_to_capture_text);
            finisher.run();
            setIsInLongshot(0);
            Log.d(TAG, "takeScreenshotNoAnimation return");
            return;
        }

        if (requiresRotation) {
            // Rotate the screenshot to the current orientation
            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(ss);
            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
            c.rotate(degrees);
            c.translate(-dims[0] / 2, -dims[1] / 2);
            c.drawBitmap(mScreenBitmap, 0, 0, null);
            c.setBitmap(null);
            // Recycle the previous bitmap
            mScreenBitmap.recycle();
            mScreenBitmap = ss;
        }

        if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {
            /*SPRD bug 670330:Catch IllegalArgumentException*/
            try {
                // Crop the screenshot to selected region
                Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
                mScreenBitmap.recycle();
                mScreenBitmap = cropped;
            } catch (IllegalArgumentException e) {
                // TODO: handle exception
                e.printStackTrace();
                notifyScreenshotError(mContext, mNotificationManager,
                        R.string.screenshot_failed_to_save_unknown_text);
                finisher.run();
                setIsInLongshot(0);
                mScreenBitmap.recycle();
                mScreenBitmap = null;
                return;
            }
            /*@}*/
        }

        // Optimizations
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        mScreenshotView.setImageBitmap(mScreenBitmap);
        mScreenshotLayout.requestFocus();

        // Setup the animation with the screenshot just taken
        if (mScreenshotAnimation != null) {
            if (mScreenshotAnimation.isStarted()) {
                mScreenshotAnimation.end();
            }
            mScreenshotAnimation.removeAllListeners();
        }

        if(mScreenshotLayout.getParent() != null) {
            mWindowManager.removeView(mScreenshotLayout);
        }
        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
        mScreenshotAnimation = new AnimatorSet();
        mScreenshotAnimation.play(screenshotDropInAnim);
        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                float degrees = getDegreesForRotation(mDisplay.getRotation());
                if (degrees > 0) {
                    if(mScreenshotLayout.getParent() != null) {
                        mWindowManager.removeView(mScreenshotLayout);
                    }
                    setIsInLongshot(0);
                    if(mFinisher != null) {
                        mFinisher.run();
                    }
                    Toast.makeText(mContext, R.string.screen_shot_landscape_tips, Toast.LENGTH_SHORT).show();
                    return;
                }
                int[] location = new int[2];
                mScreenshotView.getLocationOnScreen(location);
                if(mMoreActionLayout.getParent() != null) {
                    mWindowManager.removeView(mMoreActionLayout);
                }
                mWindowManager.addView(mMoreActionLayout, mWindowLayoutParams);
                mScreenshotSelectorView.startRegionSelection((int) dims[0], (int) dims[1], location);
                initButtonActions(finisher);

                resetDate();
                sendLongscreenshotBroadcast();
                mScreenshotLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (btnScrollMode.getVisibility() != View.VISIBLE) {
                            setSupportLongScreenshot(supportLongScreenshot);
                        }
                    }
                }, 500);
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotView.buildLayer();
                mScreenshotAnimation.start();
            }
        });
    }
private ValueAnimator createScreenshotDropInAnimation() {
        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
                / SCREENSHOT_DROP_IN_DURATION);
        final float flashDurationPct = 2f * flashPeakDurationPct;
        final Interpolator flashAlphaInterpolator = new Interpolator() {
            @Override
            public float getInterpolation(float x) {
                // Flash the flash view in and out quickly
                if (x <= flashDurationPct) {
                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
                }
                return 0;
            }
        };
        final Interpolator scaleInterpolator = new Interpolator() {
            @Override
            public float getInterpolation(float x) {
                // We start scaling when the flash is at it's peak
                if (x < flashPeakDurationPct) {
                    return 0;
                }
                return (x - flashDurationPct) / (1f - flashDurationPct);
            }
        };
        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                mBackgroundView.setAlpha(0f);
                mBackgroundView.setVisibility(View.VISIBLE);
                scrollView.setAlpha(0f);
                scrollView.setTranslationX(0f);
                scrollView.setTranslationY(0f);
                scrollView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
                scrollView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
                mScreenshotView.setVisibility(View.VISIBLE);
                mScreenshotFlash.setAlpha(0f);
                mScreenshotFlash.setVisibility(View.VISIBLE);
            }
            @Override
            public void onAnimationEnd(android.animation.Animator animation) {
                //mScreenshotFlash.setVisibility(View.GONE);
            }
        });
        anim.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float t = (Float) animation.getAnimatedValue();
                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
                    - scaleInterpolator.getInterpolation(t)
                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
                scrollView.setAlpha(t);
                scrollView.setScaleX(scaleT);
                scrollView.setScaleY(scaleT);
                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
            }
        });
        return anim;
    }

在这一系列动画结束之后,又调用关键方法

    initButtonActions(finisher);
    resetDate();
    sendLongscreenshotBroadcast();
initButtonActions方法就是在初始化mMoreActionLayout,即截图布局中上面三个cancel、share、save和下面的start long screenshot这些按钮的初始化及事件处理。
private void initButtonActions(final Runnable finisher) {
        Button btnCancel = (Button) mMoreActionLayout.findViewById(R.id.btn_cancel);
        Button btnShare = (Button) mMoreActionLayout.findViewById(R.id.btn_share);
        Button btnSave = (Button) mMoreActionLayout.findViewById(R.id.btn_save);

        mMoreActionLayout.findViewById(R.id.more_action_layout).setVisibility(View.VISIBLE);
        btnCancel.setVisibility(View.VISIBLE);
        btnCancel.setText(mContext.getString(com.android.internal.R.string.cancel));
        btnSave.setVisibility(View.VISIBLE);
        btnSave.setText(mContext.getString(R.string.save));
        btnShare.setText(mContext.getString(com.android.internal.R.string.share));
        KeyguardManager keyguardManager =
                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
        if (keyguardManager.isKeyguardLocked()) {
            btnShare.setVisibility(View.INVISIBLE);
        } else {
            btnShare.setVisibility(View.VISIBLE);
        }
        btnCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopLongscreenshot();
                stopScreenshot();
                finisher.run();
            }
        });
        btnShare.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                shareScreenshot(finisher);
                setIsInLongshot(0);
            }
         });
         btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                saveScreenshot(finisher);
            }
        });
        btnScrollMode.setText("");
        btnScrollMode.setEnabled(false);
        btnScrollMode.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isInLongscreenshot) {
                    v.setEnabled(false);
                    startLongscreenshot();
                    ((Button)v).setText(mContext.getString(R.string.stop_long_screen_shot));
                    mMoreActionLayout.findViewById(R.id.more_action_layout).setVisibility(View.GONE);
                    mScreenshotSelectorView.setVisibility(View.GONE);
                    mSecondScreenshotView.setVisibility(View.GONE);
                    float s = scrollView.getScaleX();
                    bottomFrame.setScaleX(s);
                    bottomFrame.setScaleY(s);
                } else {
                    btnScrollMode.setEnabled(false);
                    btnScrollMode.setText("stopping...");
                    stopLongBitmapAnimation();
                    mMoreActionLayout.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            if (isInLongscreenshot) {
                                saveAndShowLongScreenShot();
                                stopLongscreenshot();
                                stopScreenshot();
                            }
                        }
                    }, STOP_LONGSCREENSHOT_TIMEOUT);
                }
            }
        });
    }

稍后我们要关注下开始截图按钮,即btnScrollMode的事件处理。现在往下走还有sendLongscreenshotBroadcast()关键方法。

void sendLongscreenshotBroadcast() {
        ActivityManager mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
        ComponentName cmp  = tasks.get(0).topActivity;
        SaveImageInBackgroundTask.getScreenshotPicInfo(mScreenshotPicInfo);

        String packageName = cmp.getPackageName();
        String activityName = cmp.getClassName();
        Intent intent = new Intent("action.longscreenshot");
        intent.putExtra("package", packageName);
        intent.putExtra("activity", activityName);
        intent.putExtra("filepath", mScreenshotPicInfo.longscreenshotFilePath);
        mContext.sendBroadcast(intent);
        Log.d(TAG, "sendLongscreenshotBroadcast: packageName=" + packageName + " activityName=" + activityName);
    }

我们看到这个方法就是发送了一个action为action.longscreenshot的广播,并携带了相关参数。那个现在的问题就是,谁接受了这个广播???
在这里插入图片描述

关键点来了,在Actvity里面处理的。

这里要说下长截图的思路了。长截图肯定是免不了对可滚动View的处理了,展讯这里的思路就是穷尽法,处理我们认为可以滚动的view。在Activity的onResume中注册监听这个广播。
在这里插入图片描述
Activity接收到长截图广播后的处理,new了一个 LongScreenshotUtil,从名字上看就是一个长截图的工具类。也是Actvity里面处理长截图的。
new完之后就开始bindService。和SystemUI里面的TakeScreenshotService建立联系。
z在这里插入图片描述
在onServiceConnected中有个关键方法updateSupportState()
在这里插入图片描述
在这里插入图片描述
即getScrollableView()决定当前界面是否能够支持长截图
在这里插入图片描述
然后我们来看下什么样的view才叫isScrollableView

/**
         * judge whether a decor contains a scrollable view
         */
        private boolean isScrollableView(ViewGroup viewGroup) {
            boolean result = false;
            if (viewGroup instanceof ScrollView) {
                Log.d(TAG, "viewGroup is ScrollView");
                ScrollView scrollView = (ScrollView) viewGroup;
                View child = scrollView.getChildAt(0);
                //whether scrollView scroll to bottom
                if (child.getMeasuredHeight() <= scrollView.getScrollY() + scrollView.getHeight()) {
                    result = false;
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ScrollView has reached bottom");
                } else {
                    result = true;
                    scrollType = SCROLLVIEW;
                }
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "child.getMeasuredHeight() = " + child.getMeasuredHeight());
                return result;
            } else if (viewGroup instanceof AbsListView) {
                Log.d(TAG, "viewGroup is ListView");
                AbsListView listView = (AbsListView) viewGroup;
                int count = listView.getCount();
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "count = " + count + " getChildCount = " + listView.getChildCount());
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "LastVisiblePosition = " + listView.getLastVisiblePosition());
                if (count > 0) result = true;
                //whether listview scroll to bottom
                if (listView.getLastVisiblePosition() >= count - 1 && count > 0) {
                    int lastItemBottom = listView.getChildAt(listView.getChildCount() - 1).getBottom();
                    int listViewheight = listView.getHeight();
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "lastItemBottom = " + lastItemBottom);
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "listView.getHeight() = " + listViewheight);
                    if (lastItemBottom <= listViewheight) {
                        result = false;
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ListView has reached bottom");
                    }
                }
                if (result) scrollType = LISTVIEW;
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "count = " + count + " LastVisiblePosition = " + listView.getLastVisiblePosition());
                return result;
            } else if (viewGroup instanceof WebView) {
                Log.d(TAG, "viewGroup is WebView");
                WebView webView = (WebView) viewGroup;
                int verticalScrollRange = 0;
                int verticalScrollExtent = 1;
                int offset = 2;
                Class clazz = viewGroup.getClass();
                for (;;) {
                    String classNamepath = clazz.getSimpleName().toLowerCase();//className = com.tencent.widget.armaphongbaolistvie
                    if (classNamepath.equals("webview")) {
                        break;
                    }
                    clazz = clazz.getSuperclass();
                }

                try {
                    Method m1 = clazz.getDeclaredMethod("computeVerticalScrollRange", null);
                    Method m2 = clazz.getDeclaredMethod("computeVerticalScrollExtent", null);
                    Method m3 = clazz.getDeclaredMethod("computeVerticalScrollOffset", null);

                    m1.setAccessible(true);
                    m2.setAccessible(true);
                    m3.setAccessible(true);
                    verticalScrollRange = (Integer)m1.invoke(viewGroup);
                    verticalScrollExtent = (Integer)m2.invoke(viewGroup);
                    offset = (Integer)m3.invoke(viewGroup);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "verticalScrollRange=" + verticalScrollRange + " verticalScrollExtent=" + verticalScrollExtent + " offset=" + offset);
                if(webView.getContentHeight()*webView.getScale()-(webView.getHeight()+webView.getScrollY())==0){
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "WebView ContentHeight=" + webView.getContentHeight()*webView.getScale() + " Height=" + webView.getHeight());
                    //webview has reached bottom
                    result = false;
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "WebView has reached bottom");
                } else {
                    result = true;
                    scrollType = WEBVIEW;
                }

                if(verticalScrollRange-verticalScrollExtent-offset <= 0){
                    //webview has reached bottom
                    result = false;
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "WebView has reached bottom.");
                } else {
                    result = true;
                    scrollType = WEBVIEW;
                }

                return result;
            } else {
                int scrollableViewIndex = checkWhetherScrollableView(viewGroup);
                if (scrollableViewIndex == 1) {
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "viewGroup is self-defined listview");
                    try {
                        Method mGetCount = viewGroup.getClass().getMethod("getCount", null);
                        Method mGetLastVisiblePosition = viewGroup.getClass().getMethod("getLastVisiblePosition", null);
                        int count = (Integer)mGetCount.invoke(viewGroup);
                        int lastVisiblePosition = (Integer)mGetLastVisiblePosition.invoke(viewGroup);
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "LastVisiblePosition = " + lastVisiblePosition + " count = " + count);
                        if (count > 0) result = true;
                        //whether listview scroll to bottom
                        if (lastVisiblePosition >= count - 1 && count > 0) {
                            int lastItemBottom = viewGroup.getChildAt(viewGroup.getChildCount() - 1).getBottom();
                            int listViewHeight = viewGroup.getHeight();
                            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "lastItemBottom = " + lastItemBottom);
                            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "listViewHeight = " + listViewHeight);
                            if (lastItemBottom <= listViewHeight) {
                                result = false;
                                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ListView has reached bottom");
                            }
                        }
                        if (result) scrollType = SELF_DEFINED_LISTVIEW;
                        //if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "count = " + count + " LastVisiblePosition = " + listView.getLastVisiblePosition());
                        return result;
                    } catch (Exception e) {
                        e.printStackTrace();
                        return false;
                    }

                } else if (scrollableViewIndex == 2) {
                    try {
                        String className = viewGroup.getClass().getName();
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "viewGroup = " + className+"  viewGroup.getClass():"+viewGroup.getClass().toString());
                        Log.d(TAG, "viewGroup is self-RecyclerView");
                        Method m1 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollRange", null);
                        Method m2 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollExtent", null);
                        Method m3 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollOffset", null);

                        m1.setAccessible(true);
                        m2.setAccessible(true);
                        m3.setAccessible(true);
                        int verticalScrollRange = (Integer)m1.invoke(viewGroup);
                        int verticalScrollExtent = (Integer)m2.invoke(viewGroup);
                        int maxScrollDistance = verticalScrollRange - verticalScrollExtent;
                        int currentScrollDistance = (Integer)m3.invoke(viewGroup);
                        Log.d(TAG, "maxScrollDistance = " + maxScrollDistance + ", currentScrollDistance = " + currentScrollDistance);
                        if (currentScrollDistance >= maxScrollDistance) {
                            result = false;
                            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "RecyclerView has reached bottom");
                        } else {
                            result = true;
                            scrollType = RECYCLEVIEW;
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                        return false;
                    }
                    return result;
                } else if (scrollableViewIndex == 3) {
                    try {
                        String className = viewGroup.getClass().getName();
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "viewGroup = " + className);
                        Log.d(TAG, "viewGroup is self-WebView");
                        Method m1 = viewGroup.getClass().getMethod("getContentHeight", null);
                        Method m2 = viewGroup.getClass().getMethod("getScale", null);
                        m1.setAccessible(true);
                        m2.setAccessible(true);
                        int contentHeight = (Integer)m1.invoke(viewGroup);
                        float scale = (Float)m2.invoke(viewGroup);
                        int currentScrollDistance = viewGroup.getScrollY();
                        float leftScrollDistance = contentHeight * scale - viewGroup.getHeight() - currentScrollDistance;

                        Log.d(TAG, "leftScrollDistance = " + leftScrollDistance );
                        if (contentHeight * scale - viewGroup.getHeight() - currentScrollDistance <= 1) {
                            result = false;
                            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "WebView has reached bottom");
                        } else {
                            result = true;
                            scrollType = SELF_WEBVIEW;
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                        return false;
                    }
                    return result;
                } else if (scrollableViewIndex == 4) {
                    View child = viewGroup.getChildAt(0);
                    //whether scrollView scroll to bottom
                    if (child.getMeasuredHeight() <= viewGroup.getScrollY() + viewGroup.getHeight()) {
                        result = false;
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ScrollView has reached bottom");
                    } else {
                        result = true;
                        scrollType = SCROLLVIEW;
                    }
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "child.getMeasuredHeight() = " + child.getMeasuredHeight());
                    return result;
                }
            }

            Log.d(TAG, "isScrollableView:result = " + result);
            return result;
        }

可以看到这里其实就是一个穷尽法,有ScrollView、AbsListView、WebView、还有一个else自定义的相关类。

我们这里以Settings主界面为例,我们可以用工具看到Settings主界面可滚动的部分是com.android.settings.dashboard.conditional.FocusRecyclerView,即进入上面isScrollableView的最后一个分支else判断。

这个方法其实是为自定义继承系统可滚动view准备的,这里的判断都是contains。。。并且向上找了两层父view,即当前view,父view,父父view包含listview、recyclerview、webview、scrollview就能进入下面的判断。进入下面的判断就是在分类,并且会一直clazz.getSuperclass()。

private int checkWhetherScrollableView(ViewGroup viewGroup) {
            if ((!viewGroup.getClass().getName().toLowerCase().contains("listview")
                    && !viewGroup.getClass().getSuperclass().getName().toLowerCase().contains("listview")
                    && !viewGroup.getClass().getSuperclass().getSuperclass().getName().toLowerCase().contains("listview")) 
                            &&(!viewGroup.getClass().getName().toLowerCase().contains("recyclerview")
                            && !viewGroup.getClass().getSuperclass().getName().toLowerCase().contains("recyclerview")
                            && !viewGroup.getClass().getSuperclass().getSuperclass().getName().toLowerCase().contains("recyclerview"))
                                    &&(!viewGroup.getClass().getName().toLowerCase().contains("webview")
                                    && !viewGroup.getClass().getSuperclass().getName().toLowerCase().contains("webview")
                                    && !viewGroup.getClass().getSuperclass().getSuperclass().getName().toLowerCase().contains("webview"))
                                            &&(!viewGroup.getClass().getName().toLowerCase().contains("scrollview")
                                            && !viewGroup.getClass().getSuperclass().getName().toLowerCase().contains("scrollview")
                                            && !viewGroup.getClass().getSuperclass().getSuperclass().getName().toLowerCase().contains("scrollview"))){
                return 0;
            }

            int res = 0;
            Class clazz = viewGroup.getClass();
            for (;;) {
                String classNamepath = clazz.getName().toLowerCase();//className = com.tencent.widget.armaphongbaolistvie
                int index = classNamepath.lastIndexOf(".");
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "classNamepath = " + classNamepath + " index = " + classNamepath.lastIndexOf(".")+"  clazz:"+clazz.toString());
                String className = classNamepath.substring(index + 1);
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "className = " + className);
                if (className.equals("listview")) {
                    res = 1;
                    break;
                } else if (className.equals("abslistview")) {
                    res = 1;
                    break;
                } else if (className.equals("viewgroup")) {
                    break;
                } else if (className.equals("adapterview")) {
                    res = 1;
                    break;
                } else if (className.equals("recyclerview")) {
                    res = 2;
                    recyclerViewClass = clazz;
                    if (classNamepath.equals("android.support.v7.widget.recyclerview")) isSelfRecyclerView = false;
                    break;
                } else if (className.equals("webview")) {
                    res = 3;
                    break;
                } else if (className.equals("scrollview")) {
                    res = 4;
                    break;
                }
                clazz = clazz.getSuperclass();
            }

            return res;
        }

以我们本例为例,会得到:
res = 2;
isSelfRecyclerView = false;

else if (scrollableViewIndex == 2) {
                    try {
                        String className = viewGroup.getClass().getName();
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "viewGroup = " + className+"  viewGroup.getClass():"+viewGroup.getClass().toString());
                        Log.d(TAG, "viewGroup is self-RecyclerView");
                        Method m1 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollRange", null);
                        Method m2 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollExtent", null);
                        Method m3 = recyclerViewClass.getDeclaredMethod("computeVerticalScrollOffset", null);

                        m1.setAccessible(true);
                        m2.setAccessible(true);
                        m3.setAccessible(true);
                        int verticalScrollRange = (Integer)m1.invoke(viewGroup);
                        int verticalScrollExtent = (Integer)m2.invoke(viewGroup);
                        int maxScrollDistance = verticalScrollRange - verticalScrollExtent;
                        int currentScrollDistance = (Integer)m3.invoke(viewGroup);
                        Log.d(TAG, "maxScrollDistance = " + maxScrollDistance + ", currentScrollDistance = " + currentScrollDistance);
                        if (currentScrollDistance >= maxScrollDistance) {
                            result = false;
                            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "RecyclerView has reached bottom");
                        } else {
                            result = true;
                            scrollType = RECYCLEVIEW;
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                        return false;
                    }
                    return result;
                } 

得到RecycleView之后会通过反射调用RecycleView的方法去获取当前滚动的距离(如果Settings主界面已经滚动到了底部,那样也是不支持长截图的),最终得出result,返回isScrollableView()的boolean类型的值。然后就一层层返回,isScrollableView()返回到getScrollableView(),在返回到isSupportLongScreenshot(),在返回到updateSupportState()。你是否忘记了我们是从哪里调过来的☺

private void updateSupportState() {
    supportLongScreenshot = !isInMultiWindowMode() && isSupportLongScreenshot();
    Message msg = Message.obtain(null, UPDATE_LONG_SCREENSHOT_SUPPORT_STATE,
            supportLongScreenshot ? 1 : 0, 0);
    sendMessageToService(msg);
}

得到这个supportLongScreenshot 值后会调用这个sendMessageToService,从名字就可以看出来是要给service发动数据了。

private void sendMessageToService(int what) {
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "sendMessageToService what = " + what);
    Message msg = Message.obtain(null, what);
    msg.replyTo = receiveMessenger;
    try {
        screenshotMessenger.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "screenshotMessenger send message fail !  what=" + what);
    }
}

流程转到SystemUI下的TakScreenshotService中

case UPDATE_LONG_SCREENSHOT_SUPPORT_STATE:
        replyMesenger = msg.replyTo;
        boolean supportLongScreenshot = msg.arg1 == 1;
        mScreenshot.setSupportLongScreenshot(supportLongScreenshot);
        break;

又到GlobalScreenshot中,传过来的boolean值决定了界面上是否允许点击 start long screenshot 按钮

public void setSupportLongScreenshot(boolean supportLongScreenshot) {
        this.supportLongScreenshot = supportLongScreenshot;
        if (supportLongScreenshot) {
            btnScrollMode.setText(mContext.getString(R.string.start_long_screen_shot));
            btnScrollMode.setEnabled(true);
            btnScrollMode.setVisibility(View.VISIBLE);
        } else {
            btnScrollMode.setText(mContext.getString(R.string.not_support_long_screen_shot));
            btnScrollMode.setEnabled(false);
            btnScrollMode.setVisibility(View.VISIBLE);
        }
        registerStopLongScreenshotReceiver();
    }

假使现在是能向下滚动的,这里就是允许 长截图的。

然后我们在回到SystemUI,发送长截图的广播找到了是在Activity里面处理的,处理完之后的效果是Activity中的LongScreenshotUtils与SystemUI建立了联系。然后我们去点击 start long screenshot
在这里插入图片描述

protected void startLongscreenshot() {
        isInLongscreenshot = true;
        if (supportLongScreenshot) screenshotService.sendLongscreenshotMessage(TakeScreenshotService.START_LONG_SCREENSHOT);
        btnScrollMode.setEnabled(true);

    }

点击长截图,SystemUI里面没有做实际工作,只是给TakeSceenshotService发送了一个START_LONG_SCREENSHOT消息。然后TakeSceenshotService又将此消息发送给Activity
在这里插入图片描述
Activity下的LongScreenshotUtils的takeLongscreenshot()方法被调用,方法前面就是一些判断,当前界面是否符合支持长截图。。然后该方法底部就是关键的东西了。
在这里插入图片描述
这里我们看到有很多controller,这些都是在LongScreenshotUtils中的定义的内部类,有个基类
abstract class ScrollController,然后其他的controller都是继承这个基类的。

abstract class ScrollController {
            //the target to scroll and capture it's long view
            protected ViewGroup view;
            //scroll distance of SCROLL_DISTANCE by time of SCROLL_DURATION at once
            protected static final int SCROLL_DISTANCE = 500;
            protected static final int SCROLL_DURATION = 50;
            protected int onceScrollDistance = SCROLL_DISTANCE;
            protected int maxScrollDistance = 0;
            protected int leftScrollDistance = 0;
            //remember scrollBar state before scrolling and resume scrollBar when scrollComplete
            protected boolean scrollBarEnabled = true;
            protected int scrolltimes = 0;

            protected Handler scrollHandler = null;

            protected int startScrollY = 0;
            protected CheckScrollDistanceRunnable checkScrollDistance = new CheckScrollDistanceRunnable();
            protected Runnable scrollTimeoutRunnable = new Runnable() {
                public void run() {
                    setScrollComplete();
                    sendScrollMessage(0);
                }
            };

            //use to compute last scroll distance
            protected int lastItemPreBottom = 0;
            protected View lastItemView = null;

            public ScrollController(ViewGroup view) {
                this.view = view;
                scrollBarEnabled = view.isVerticalScrollBarEnabled();
                //view.setVerticalScrollBarEnabled(false);
                //setScrollDistanceListener();
            }

            public void setOnceScrollDistance(int distance) {
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "setOnceScrollDistance:onceScrollDistance=" + distance);
                onceScrollDistance = distance;
            }

            protected void setScrollHandler(Handler scrollHandler) {
                this.scrollHandler = scrollHandler;
            }

            protected void startScroll() {//(前面有过截图的操作)点击开始长截图后调用到这里,在SystemUI里面已经截图了一次
                //view.setVerticalScrollBarEnabled(false);
                setScrollDistanceListener();
                maxScrollDistance = countMaxScrollDistance();
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "startScroll:maxScrollDistance = " + maxScrollDistance);
                continueScroll();
            }

            protected void continueScroll() {
                //view.removeCallbacks(scrollTimeoutRunnable);
                if (this.canScrollDown()) {
                    scrolltimes += 1;
                    startScrollY = computeCurrentScrollY();
                    lastItemView = view.getChildAt(view.getChildCount() - 1);
                    if (lastItemView != null) lastItemPreBottom = lastItemView.getBottom();
                    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ScrollController continueScroll: lastItemPreBottom = " + lastItemPreBottom+"  scrollBy==>onceScrollDistance:"+onceScrollDistance
                    	+"  view.getChildCount():"+view.getChildCount());
                    view.scrollBy(0, onceScrollDistance);
                    //view.scrollTo(0, view.getScrollY() + onceScrollDistance);
                    //view.postDelayed(scrollTimeoutRunnable,300);
                } else {
                    setScrollComplete();
                }

            }

            protected void sendScrollMessage(int scrollDistance) {
                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "sendScrollMessage: scrollDistance=" + scrollDistance, new Throwable());
                if (scrollDistance > 0) {
                    Message shotMessage = scrollHandler.obtainMessage(CAPTURE_SCREEN, scrollDistance);
                    scrollHandler.sendMessage(shotMessage);
                }
                if (scrollDistance < onceScrollDistance) {
	                if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "should have scroll to bottom");
	                    Message completeMessage = scrollHandler.obtainMessage(SCROLL_COMPLETE);
	                    scrollHandler.sendMessage(completeMessage);
                }
            }

            protected int computeCurrentScrollY() {
                return view.getScrollY();
            }

            protected int computeScrollDistance() {
                return computeCurrentScrollY() - startScrollY;
            }

            protected void setScrollComplete() {
                //view.removeCallbacks(scrollTimeoutRunnable);
                view.setVerticalScrollBarEnabled(scrollBarEnabled);
                clearScrollDistanceListener();
                resetOverlayViews();
                //sendScrollMessage(-1);
            }

            //set scroll listener which will compute scrollDistance after scroll once
            protected void setScrollDistanceListener() {
                view.setScrollDistanceRunnable(checkScrollDistance);
            }

            protected void clearScrollDistanceListener() {
                view.setScrollDistanceRunnable(null);
            }

            protected void resetOverlayViews() {
                if (shouldCheckOverlay) {
                    if (overlayViews != null && overlayViews.size() > 0) {
                        for (int i = 0; i < overlayViews.size(); i++) {
                            View view = overlayViews.get(i);
                            view.setVisibility(View.VISIBLE);
                        }
                    }
                }
            }

            //whether scrollableView canScrollDown any more
            abstract boolean canScrollDown();

            abstract int countMaxScrollDistance();

            protected int fitScrollDistance(int scrollDistance) {
                if (canScrollDown() /*|| scrollDistance > onceScrollDistance*/) {
                    return onceScrollDistance;
                } else {
                    if (lastItemView != null) {
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "fitScrollDistance continueScroll: lastItemPreBottom = " + lastItemView.getBottom());
                        return lastItemPreBottom - lastItemView.getBottom();
                    }
                }

                return scrollDistance;
            }

            protected class CheckScrollDistanceRunnable implements Runnable {

                public CheckScrollDistanceRunnable() {
                }

                @Override
                public void run() {
                    int scrollDistance = computeScrollDistance();
                    if (lastItemView != null) {
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "run: lastItemPreBottom = " + lastItemPreBottom+"  lastItemView.getBottom():" +lastItemView.getBottom());
                        scrollDistance = lastItemPreBottom - lastItemView.getBottom();
                    }
                    if ((scrollDistance != onceScrollDistance && scrollType != RECYCLEVIEW) || scrollType == SELF_WEBVIEW){
                        scrollDistance = fitScrollDistance(scrollDistance);
                    }
                    //if (scrollType != RECYCLEVIEW) scrollDistance = fitScrollDistance(scrollDistance);
                    if (scrollDistance > onceScrollDistance) scrollDistance = onceScrollDistance;
                    //if (scrollType == SELF_WEBVIEW && scrollDistance > leftScrollDistance) scrollDistance = leftScrollDistance;
                    //if (scrollType == SCROLLVIEW) scrollDistance = fitScrollDistance(scrollDistance);
                    Log.d(TAG, "scrollDistance = " + scrollDistance);
                    sendScrollMessage(scrollDistance);
                }
            }
        }

我们关注针对Settings主界面的RecyclerViewController

class RecyclerViewController extends ScrollController {
      private Method m1, m2, m3;
      private int leftMaxScrollDistance = 0;
      private int preLeftMaxScrollDistance;
      private boolean selfRecyclerView = false;

      public RecyclerViewController(ViewGroup view, Class clazz, boolean selfRecyclerView) {
          super(view);
          this.selfRecyclerView = selfRecyclerView;
          createMethods(clazz);
      }

      private void createMethods(Class clazz) {
          try {
              m1 = clazz.getDeclaredMethod("computeVerticalScrollRange", null);
              m2 = clazz.getDeclaredMethod("computeVerticalScrollExtent", null);
              m3 = clazz.getDeclaredMethod("computeVerticalScrollOffset", null);
              m1.setAccessible(true);
              m2.setAccessible(true);
              m3.setAccessible(true);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

      protected void setScrollDistanceListener() {
          if (!selfRecyclerView) super.setScrollDistanceListener();
      }

      protected void clearScrollDistanceListener() {
          if (!selfRecyclerView) super.clearScrollDistanceListener();
      }

      @Override
      protected int computeCurrentScrollY() {
          int result = 0;
          try {
              result = (Integer)m3.invoke(view);
          } catch (Exception e) {
              e.printStackTrace();
          }

          if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "computeStartScrollY:result = " + result + " selfRecyclerView?" + selfRecyclerView);
          return result;
      }

      @Override
      protected void continueScroll() {
      	Log.e(TAG,"RecyclerViewController continueScroll selfRecyclerView:"+selfRecyclerView);
          if (!selfRecyclerView) {
              super.continueScroll();
              return;
          }

          if (!canScrollDown()) {
              setScrollComplete();
              return;
          }
          scrolltimes += 1;
          startScrollY = computeCurrentScrollY();
          lastItemView = view.getChildAt(view.getChildCount() - 1);
          if (lastItemView != null) lastItemPreBottom = lastItemView.getBottom();
          if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "RecyclerViewController continueScroll: lastItemPreBottom = " + lastItemPreBottom);
          preLeftMaxScrollDistance = leftMaxScrollDistance;
          view.scrollBy(0, onceScrollDistance);
          view.postDelayed(checkScrollDistance, 200);
      }

      @Override
      protected boolean canScrollDown() {
          boolean result = true;
          try {
              int verticalScrollRange = (Integer)m1.invoke(view);
              int verticalScrollExtent = (Integer)m2.invoke(view);
              int maxDistance = verticalScrollRange - verticalScrollExtent;
              int currentScrollDistance = (Integer)m3.invoke(view);
              leftMaxScrollDistance = maxDistance - currentScrollDistance;
              Log.d(TAG, "maxDistance = " + maxDistance + ", leftMaxScrollDistance = " + leftMaxScrollDistance + " scrollY = " + view.getScrollY());
              if (currentScrollDistance >= maxDistance || preLeftMaxScrollDistance == leftMaxScrollDistance) {
                  result = false;
                  if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "RecyclerView has reached bottom");
              }
          } catch (Exception e) {
              e.printStackTrace();
          }

          Log.d(TAG, "canScrollDown:result = " + result);
          return result;
      }

      protected int countMaxScrollDistance() {//剩余可滚动的距离
          try {
              int verticalScrollRange = (Integer)m1.invoke(view);
              int verticalScrollExtent = (Integer)m2.invoke(view);
              int currentScrollDistance = (Integer)m3.invoke(view);
              maxScrollDistance = verticalScrollRange - verticalScrollExtent;
              leftMaxScrollDistance = maxScrollDistance - currentScrollDistance;
          } catch (Exception e) {
              e.printStackTrace();
          }
          return leftMaxScrollDistance;
      }

      protected int fitScrollDistance(int scrollDistance) {
      	Log.e(TAG,"RecyclerViewController fitScrollDistance");
          int preLeftMaxScrollDistance = leftMaxScrollDistance;
          //if (!canScrollDown() /*&& scrolltimes == 1 && scrollDistance < leftMaxScrollDistance*/) return preLeftMaxScrollDistance;
          return super.fitScrollDistance(scrollDistance);
      }
  }

回到takeLongscreenshot(),稍后在来分析controller,takeLongscreenshot()继续往下走会调用startScroller().

private void startScroll() {
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "startScroll");
    scrollableView.setVerticalScrollBarEnabled(false);
    Bitmap first = captureScreen(lContext, getWindow().getDecorView());//长截图会调到这里 有一次截图的操作
    if(first == null) {
        sendMessageToService(LONG_SCREENSHOT_UNKOWN_ERROR);
        return;
    }
    bitmaps.add(first);
    totalHeight = first.getHeight();
    scrollController.startScroll();//第一次截好图后就开始scroll
}

这个方法有两个关键点

1,captureScreen

这个截图与SystemUI里面通过SurfaceControl.screenshot截图不一样,view.getDrawingCache(),这个截出来是图片大小是整个手机屏幕大小(720*1520),但是状态栏与导航栏部分是有颜色,没内容

private Bitmap captureScreen(Context context, View view) {//截得是去掉导航栏的高度(包括状态栏高度,但是状态栏的内容是没有的)
	Log.e(TAG,"captureScreen view:"+view.getClass().toString());
    disableOverlayViews();
    view.setDrawingCacheEnabled(true);
    Bitmap b1;
    try {
        view.buildDrawingCache();
        b1 = view.getDrawingCache();
    } catch (Exception e) {
        Log.d(TAG, "captureScreen:" + e.getMessage());
        isScreenshotError = true;
        return null;
    }
    Rect frame = new Rect();
    view.getWindowVisibleDisplayFrame(frame);
    int statusBarHeight = frame.top;
    screenBottom = frame.bottom;
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "statusBarHeight = " + statusBarHeight + " naviBarHeight = " + screenBottom);

    int width = context.getResources().getDisplayMetrics().widthPixels;
    int height = context.getResources().getDisplayMetrics().heightPixels;
    int screenHeight = getScreenHeight();//真实高度,包含NavigationBar
    if (overlayCropPosition > 0 && cropPosition > overlayCropPosition) cropPosition = overlayCropPosition;
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "height = " + height + " screenHeight = " + screenHeight + " cropPosition = " + cropPosition
    	+"  b1.getWidth():"+b1.getWidth()+"  b1.getHeight():"+b1.getHeight());
    Bitmap b = Bitmap.createBitmap(b1, 0, 0, width, cropPosition > b1.getHeight() ? b1.getHeight() : cropPosition);//截图不包含导航栏
    secondBitmapHeight = screenHeight - cropPosition;//secondBitmapHeight 就是导航栏高度
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "captureScreen end secondBitmapHeight = " + secondBitmapHeight+"  b1.getHeight:"+ b1.getHeight()+"   b.getHeight:"+ b.getHeight());
    view.destroyDrawingCache();
    return b;
}

在这里插入图片描述
在截到b1 = view.getDrawingCache();之后有对b1做裁剪。
cropPosition 是屏幕不包括导航栏的盖度,即1520-96=1424

Bitmap b = Bitmap.createBitmap(b1, 0, 0, width, cropPosition > b1.getHeight() ? b1.getHeight() : cropPosition);//截图不包含导航栏

在这里插入图片描述
裁剪过之后就是这样的,没有导航栏。
captureScreen方法带着一个没有导航栏的bitmap返回到startScroll()中。

private void startScroll() {
   if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "startScroll");
   scrollableView.setVerticalScrollBarEnabled(false);
   Bitmap first = captureScreen(lContext, getWindow().getDecorView());//长截图会调到这里 有一次截图的操作
   if(first == null) {
       sendMessageToService(LONG_SCREENSHOT_UNKOWN_ERROR);
       return;
   }
   bitmaps.add(first);
   totalHeight = first.getHeight();
   scrollController.startScroll();//第一次截好图后就开始scroll
}

先将其add到bitmaps中,然后调用scroller的startScroll()方法。这里就到了我们前面说的ScrollerController了。

protected void startScroll() {//(前面有过截图的操作)点击开始长截图后调用到这里,在SystemUI里面已经截图了一次
     //view.setVerticalScrollBarEnabled(false);
     setScrollDistanceListener();
     maxScrollDistance = countMaxScrollDistance();
     if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "startScroll:maxScrollDistance = " + maxScrollDistance);
     continueScroll();
     }

setScrollDistanceListener,就是在每次滚动结束之后会回调的runnable

protected void setScrollDistanceListener() {
                view.setScrollDistanceRunnable(checkScrollDistance);
            }
protected class CheckScrollDistanceRunnable implements Runnable {
      public CheckScrollDistanceRunnable() {
      }

      @Override
      public void run() {
          int scrollDistance = computeScrollDistance();
          if (lastItemView != null) {
              if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "run: lastItemPreBottom = " + lastItemPreBottom+"  lastItemView.getBottom():" +lastItemView.getBottom());
              scrollDistance = lastItemPreBottom - lastItemView.getBottom();
          }
          if ((scrollDistance != onceScrollDistance && scrollType != RECYCLEVIEW) || scrollType == SELF_WEBVIEW){
              scrollDistance = fitScrollDistance(scrollDistance);
          }
          //if (scrollType != RECYCLEVIEW) scrollDistance = fitScrollDistance(scrollDistance);
          if (scrollDistance > onceScrollDistance) scrollDistance = onceScrollDistance;
          //if (scrollType == SELF_WEBVIEW && scrollDistance > leftScrollDistance) scrollDistance = leftScrollDistance;
          //if (scrollType == SCROLLVIEW) scrollDistance = fitScrollDistance(scrollDistance);
          Log.d(TAG, "scrollDistance = " + scrollDistance);
          sendScrollMessage(scrollDistance);
      }
  }

continueScroll,首先会判断当前view是否能向下滚动,canScrollDown

protected void continueScroll() {
       //view.removeCallbacks(scrollTimeoutRunnable);
       if (this.canScrollDown()) {
           scrolltimes += 1;
           startScrollY = computeCurrentScrollY();
           lastItemView = view.getChildAt(view.getChildCount() - 1);
           if (lastItemView != null) lastItemPreBottom = lastItemView.getBottom();
           if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "ScrollController continueScroll: lastItemPreBottom = " + lastItemPreBottom+"  scrollBy==>onceScrollDistance:"+onceScrollDistance
           	+"  view.getChildCount():"+view.getChildCount());
           view.scrollBy(0, onceScrollDistance);
           //view.scrollTo(0, view.getScrollY() + onceScrollDistance);
           //view.postDelayed(scrollTimeoutRunnable,300);
       } else {
           setScrollComplete();
       }

 }

RecyclerViewController的canScrollDown(),最大可滚动的距离 - 当前已经滚动的距离 = 剩余可滚动的距离,如果当前可滚动的距离 >= 最大允许滚动的距离 就不能在向下滚动了。否则就滚动view.scrollBy(0, onceScrollDistance);,onceScrollDistance的距离,该值在前面设置了,是屏幕高度的 3/4。

@Override
protected boolean canScrollDown() {
    boolean result = true;
    try {
        int verticalScrollRange = (Integer)m1.invoke(view);
        int verticalScrollExtent = (Integer)m2.invoke(view);
        int maxDistance = verticalScrollRange - verticalScrollExtent;
        int currentScrollDistance = (Integer)m3.invoke(view);
        leftMaxScrollDistance = maxDistance - currentScrollDistance;
        Log.d(TAG, "maxDistance = " + maxDistance + ", leftMaxScrollDistance = " + leftMaxScrollDistance + " scrollY = " + view.getScrollY());
        if (currentScrollDistance >= maxDistance || preLeftMaxScrollDistance == leftMaxScrollDistance) {
            result = false;
            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "RecyclerView has reached bottom");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    Log.d(TAG, "canScrollDown:result = " + result);
    return result;
}

滚动结束后,回到前面设置的runnable

 protected class CheckScrollDistanceRunnable implements Runnable {

                public CheckScrollDistanceRunnable() {
                }

                @Override
                public void run() {
                    int scrollDistance = computeScrollDistance();
                    if (lastItemView != null) {
                        if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "run: lastItemPreBottom = " + lastItemPreBottom+"  lastItemView.getBottom():" +lastItemView.getBottom());
                        scrollDistance = lastItemPreBottom - lastItemView.getBottom();
                    }
                    if ((scrollDistance != onceScrollDistance && scrollType != RECYCLEVIEW) || scrollType == SELF_WEBVIEW){
                        scrollDistance = fitScrollDistance(scrollDistance);
                    }
                    //if (scrollType != RECYCLEVIEW) scrollDistance = fitScrollDistance(scrollDistance);
                    if (scrollDistance > onceScrollDistance) scrollDistance = onceScrollDistance;
                    //if (scrollType == SELF_WEBVIEW && scrollDistance > leftScrollDistance) scrollDistance = leftScrollDistance;
                    //if (scrollType == SCROLLVIEW) scrollDistance = fitScrollDistance(scrollDistance);
                    Log.d(TAG, "scrollDistance = " + scrollDistance);
                    sendScrollMessage(scrollDistance);
                }
            }

计算出scrollDistance,传给最后的关键方法sendScrollMessage

protected void sendScrollMessage(int scrollDistance) {
    if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "sendScrollMessage: scrollDistance=" + scrollDistance, new Throwable());
    if (scrollDistance > 0) {
        Message shotMessage = scrollHandler.obtainMessage(CAPTURE_SCREEN, scrollDistance);
        scrollHandler.sendMessage(shotMessage);
    }
    if (scrollDistance < onceScrollDistance) {
     if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "should have scroll to bottom");
         Message completeMessage = scrollHandler.obtainMessage(SCROLL_COMPLETE);
         scrollHandler.sendMessage(completeMessage);
    }
}

滚动距离 > 0,发送一个CAPTURE_SCREEN的消息,滚动距离小于 设定的屏幕高度*3/4,证明滚动到底了,发送SCROLL_COMPLETE消息。我们一条条来看。

private Handler scrollHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case CAPTURE_SCREEN:
                        Bitmap temp = captureScreen(lContext, getWindow().getDecorView());
                        if(temp == null) {
                            sendMessageToService(LONG_SCREENSHOT_UNKOWN_ERROR);
                            return;
                        }
                        int scrollDistance = (Integer) msg.obj;
                        Bitmap b = Bitmap.createBitmap(temp, 0, temp.getHeight() - scrollDistance, temp.getWidth(), scrollDistance);//裁剪出滚出来的距离内容。
                        shotOnce(b);
                        break;
                    case SCROLL_COMPLETE:
                        if(!isScreenshotError) {
                            scrollComplete("scroll to bottom!");
                        }
                        break;
                }
            }
        };

CAPTURE_SCREEN:第一步截图,这个方法我们前面分析过,截出来的是不包括导航高度,包括状态栏高度但是状态栏上只有颜色,没有内容的图片。
我们想象下,如果最后是拼接的这样的图片,肯定是有重复的,比如Settings顶部的搜索框肯定就一直再重复。
在这里插入图片描述
事实上长截图出来的肯定是不重复的。在得到截图后有个关键操作

Bitmap b = Bitmap.createBitmap(temp, 0, temp.getHeight() - scrollDistance, temp.getWidth(), scrollDistance);//裁剪出滚出来的距离内容。

这里对截到的图裁剪了, temp.getHeight() - scrollDistance ,图片高度 - 滚动距离 = 滚动出来的内容顶端。
很简单呀,滚动了 100px,就有100px的新内容出来,用总高度 - 100px 就是这新滚出来的内容的top值,然后向下裁剪scrollDistance的距离。所以裁剪过之后的bitmap就是滚动出来的那100px的内容。
真的很巧妙吧!!!
接下来调用shotOnce

private void shotOnce(Bitmap b) {
            totalHeight += b.getHeight();
            //currentSavingBitmap = b;
            if (totalHeight + secondBitmapHeight >= LONG_BITMAP_HEIGHT_LIMIT || !scrollController.canScrollDown()) {
                //sendMessageToService(UPDATE_LONG_SCREENSHOT);
                if (!scrollController.canScrollDown() && totalHeight + secondBitmapHeight < LONG_BITMAP_HEIGHT_LIMIT) bitmaps.add(b);
                scrollComplete("totalHeight + secondBitmapHeight >= LONG_BITMAP_HEIGHT_LIMIT || !scrollController.canScrollDown()");
            } else {
                bitmaps.add(b);
                continueScroll();
            }

        }

是对长截图的最终高度做了一个限制,假设我们此次还没有达到限制。走到到else里面去了,就是把刚刚截出来的bitmap add到集合中,然后continueScroll()。

private void continueScroll() {
            //if (!scrollController.canScrollDown()) return;
            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "continueScroll");
            if (scrollController != null) scrollController.continueScroll();
        }

continueScroll就是在调用对应scrollController的continueScroll(),回到前面的流程,这次我们假设滚动到底了,即滚动距离 < onceScrollDistance ,这次就会发送两条消息。CAPTURE_SCREEN、SCROLL_COMPLETE。截图的消息就不说了,来看SCROLL_COMPLETE的消息。

case SCROLL_COMPLETE:
        if(!isScreenshotError) {
            scrollComplete("scroll to bottom!");
        }
        break;
private void scrollComplete(String reason) {
            if (scrollCompleted) {
                Log.d(TAG, "scrollCompleted, return");
                return;
            }
            if (DEBUG_LONG_SCREENSHOT) Log.d(TAG, "scrollComplete: reason = " + reason);
            if (overlayViews.size() > 0) {
                for (int i=0; i<overlayViews.size(); i++) {
                    View view = overlayViews.get(i);
                    view.setVisibility(View.VISIBLE);
                }
                Bitmap temp = captureScreen(lContext, getWindow().getDecorView());
                Bitmap overlayBitmap = Bitmap.createBitmap(temp, 0, getOverlayViewTop(), temp.getWidth(), temp.getHeight() - getOverlayViewTop());
                bitmaps.add(overlayBitmap);
            }

            Message msg = Message.obtain(null, UPDATE_LONG_SCREENSHOT,
                    getOverlayViewTop(), secondBitmapHeight);
            msg.obj = mergeBitmaps(bitmaps);
            sendMessageToService(msg);
            scrollCompleted = true;
        }

msg.obj = mergeBitmaps(bitmaps) 将我们前面截图的图片合成一个bitmap

private Bitmap mergeBitmaps(List<Bitmap> bitmaps) {
      if (bitmaps.size() == 0) return null;
      if (bitmaps.size() == 1) return bitmaps.get(0);
      int height = 0;
      for (int i=0; i<bitmaps.size(); i++) {
          Bitmap b = bitmaps.get(i);
          height += b.getHeight();
          Log.e(TAG,"mergeBitmaps i"+i+":"+b.getHeight());
      }
      Log.d(TAG, "mergeBitmaps:totalheight = " + height);

      Bitmap result = Bitmap.createBitmap(bitmaps.get(0).getWidth(), height, Bitmap.Config.ARGB_8888);

      int currentHight = 0;
      Canvas canvas = new Canvas(result);
      canvas.setHwBitmapsInSwModeEnabled(true);
      for (int i=0; i<bitmaps.size(); i++) {
          Bitmap b = bitmaps.get(i);
          canvas.drawBitmap(b, 0, currentHight, null);
          canvas.save(Canvas.ALL_SAVE_FLAG);
          canvas.restore();
          currentHight += b.getHeight();
      }

      return result;
  }

然后给SystemUI发送UPDATE_LONG_SCREENSHOT消息,并且将组合好的图片传过去了。这里可以想像这个图片就是长截图,但是没有导航栏,有状态栏但是没有内容,是个半成品。

回到SystemUI的TakeScreenshotService

case UPDATE_LONG_SCREENSHOT:
  Bitmap b = (Bitmap)msg.obj;
  Log.d(TAG, "Bitmap height = " +  b.getHeight());
  int overlayViewsTop = msg.arg1;
  int secondBitmapHeight = msg.arg2;

  mScreenshot.updateLongScreenshotView(b, overlayViewsTop, secondBitmapHeight);
  break;

在这里插入图片描述
mScreenshotView.setImageBitmap(longBitmap); 将longBitmap设置给mScreenshotView。
长截图滚动的动画
在这里插入图片描述
在这里插入图片描述
来关注滚动结束之后的操作。

主要是断开与TakeScreenshotService的连接,然后重置一些变量
public void stopLongscreenshot() {
        if (!isInLongscreenshot) {
            screenshotService.sendLongscreenshotMessage(TakeScreenshotService.CANCEL_LONG_SCREENSHOT);
            setIsInLongshot(0);
            return;
        }
        screenshotService.sendLongscreenshotMessage(TakeScreenshotService.STOP_LONG_SCREENSHOT);
        try {
            mContext.unregisterReceiver(mStopLongScreenshotReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (longBitmap == null) longBitmap = mScreenBitmap;
        setIsInLongshot(0);
        isInLongscreenshot = false;
        //add buy cyy start for SPCSS00634821
        if (mSecondScreenshotView != null) {
            mSecondScreenshotView.setVisibility(View.GONE);
        }
        //add buy cyy start for SPCSS00634821
    }

关键在最后 saveAndShowLongScreenShot

在这里插入图片描述

在这里插入图片描述
至此整个流程基本完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值