All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.view.ScrollCaptureInternal Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.view;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.ScrollCaptureCallback;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ListView;

/**
 * Provides built-in framework level Scroll Capture support for standard scrolling Views.
 */
public class ScrollCaptureInternal {
    private static final String TAG = "ScrollCaptureInternal";

    // Log found scrolling views
    private static final boolean DEBUG = false;

    // Log all investigated views, as well as heuristic checks
    private static final boolean DEBUG_VERBOSE = false;

    private static final int UP = -1;
    private static final int DOWN = 1;

    /**
     * Cannot scroll according to {@link View#canScrollVertically}.
     */
    public static final int TYPE_FIXED = 0;

    /**
     * Slides a single child view using mScrollX/mScrollY.
     */
    public static final int TYPE_SCROLLING = 1;

    /**
     * Slides child views through the viewport by translating their layout positions with {@link
     * View#offsetTopAndBottom(int)}. Manages Child view lifecycle, creating as needed and
     * binding views to data from an adapter. Views are reused whenever possible.
     */
    public static final int TYPE_RECYCLING = 2;

    /**
     * Unknown scrollable view with no child views (or not a subclass of ViewGroup).
     */
    private static final int TYPE_OPAQUE = 3;

    /**
     * Performs tests on the given View and determines:
     * 1. If scrolling is possible
     * 2. What mechanisms are used for scrolling.
     * 

* This needs to be fast and not alloc memory. It's called on everything in the tree not marked * as excluded during scroll capture search. */ private static int detectScrollingType(View view) { // Confirm that it can scroll. if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) { // Nothing to scroll here, move along. if (DEBUG_VERBOSE) { Log.v(TAG, "hint: cannot be scrolled"); } return TYPE_FIXED; } if (DEBUG_VERBOSE) { Log.v(TAG, "hint: can be scrolled up or down"); } // Must be a ViewGroup if (!(view instanceof ViewGroup)) { if (DEBUG_VERBOSE) { Log.v(TAG, "hint: not a subclass of ViewGroup"); } return TYPE_OPAQUE; } if (DEBUG_VERBOSE) { Log.v(TAG, "hint: is a subclass of ViewGroup"); } // ScrollViews accept only a single child. if (((ViewGroup) view).getChildCount() > 1) { if (DEBUG_VERBOSE) { Log.v(TAG, "hint: scrollable with multiple children"); } return TYPE_RECYCLING; } // At least one child view is required. if (((ViewGroup) view).getChildCount() < 1) { if (DEBUG_VERBOSE) { Log.v(TAG, "scrollable with no children"); } return TYPE_OPAQUE; } if (DEBUG_VERBOSE) { Log.v(TAG, "hint: single child view"); } //Because recycling containers don't use scrollY, a non-zero value means Scroll view. if (view.getScrollY() != 0) { if (DEBUG_VERBOSE) { Log.v(TAG, "hint: scrollY != 0"); } return TYPE_SCROLLING; } Log.v(TAG, "hint: scrollY == 0"); // Since scrollY cannot be negative, this means a Recycling view. if (view.canScrollVertically(UP)) { if (DEBUG_VERBOSE) { Log.v(TAG, "hint: able to scroll up"); } return TYPE_RECYCLING; } if (DEBUG_VERBOSE) { Log.v(TAG, "hint: cannot be scrolled up"); } // canScrollVertically(UP) == false, getScrollY() == 0, getChildCount() == 1. // For Recycling containers, this should be a no-op (RecyclerView logs a warning) view.scrollTo(view.getScrollX(), 1); // A scrolling container would have moved by 1px. if (view.getScrollY() == 1) { view.scrollTo(view.getScrollX(), 0); if (DEBUG_VERBOSE) { Log.v(TAG, "hint: scrollTo caused scrollY to change"); } return TYPE_SCROLLING; } if (DEBUG_VERBOSE) { Log.v(TAG, "hint: scrollTo did not cause scrollY to change"); } return TYPE_RECYCLING; } /** * Creates a scroll capture callback for the given view if possible. * * @param view the view to capture * @param localVisibleRect the visible area of the given view in local coordinates, as supplied * by the view parent * @param positionInWindow the offset of localVisibleRect within the window * @return a new callback or null if the View isn't supported */ @Nullable public ScrollCaptureCallback requestCallback(View view, Rect localVisibleRect, Point positionInWindow) { // Nothing to see here yet. if (DEBUG_VERBOSE) { Log.v(TAG, "scroll capture: checking " + view.getClass().getName() + "[" + resolveId(view.getContext(), view.getId()) + "]"); } int i = detectScrollingType(view); switch (i) { case TYPE_SCROLLING: if (DEBUG) { Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName() + "[" + resolveId(view.getContext(), view.getId()) + "]" + " -> TYPE_SCROLLING"); } return new ScrollCaptureViewSupport<>((ViewGroup) view, new ScrollViewCaptureHelper()); case TYPE_RECYCLING: if (DEBUG) { Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName() + "[" + resolveId(view.getContext(), view.getId()) + "]" + " -> TYPE_RECYCLING"); } if (view instanceof ListView) { // ListView is special. return new ScrollCaptureViewSupport<>((ListView) view, new ListViewCaptureHelper()); } return new ScrollCaptureViewSupport<>((ViewGroup) view, new RecyclerViewCaptureHelper()); case TYPE_OPAQUE: if (DEBUG) { Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName() + "[" + resolveId(view.getContext(), view.getId()) + "]" + " -> TYPE_OPAQUE"); } if (view instanceof WebView) { Log.d(TAG, "scroll capture: Using WebView support"); return new ScrollCaptureViewSupport<>((WebView) view, new WebViewCaptureHelper()); } break; case TYPE_FIXED: // ignore break; } return null; } // Lifted from ViewDebug (package protected) private static String formatIntToHexString(int value) { return "0x" + Integer.toHexString(value).toUpperCase(); } static String resolveId(Context context, int id) { String fieldValue; final Resources resources = context.getResources(); if (id >= 0) { try { fieldValue = resources.getResourceTypeName(id) + '/' + resources.getResourceEntryName(id); } catch (Resources.NotFoundException e) { fieldValue = "id/" + formatIntToHexString(id); } } else { fieldValue = "NO_ID"; } return fieldValue; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy