Merge "Adds long screenshot support for ListView" into sc-dev
This commit is contained in:
125
core/java/com/android/internal/view/ListViewCaptureHelper.java
Normal file
125
core/java/com/android/internal/view/ListViewCaptureHelper.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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 static com.android.internal.view.ScrollCaptureViewSupport.computeScrollAmount;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.findScrollingReferenceView;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.transformFromContainerToRequest;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.transformFromRequestToContainer;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll capture support for ListView.
|
||||||
|
*
|
||||||
|
* @see ScrollCaptureViewSupport
|
||||||
|
*/
|
||||||
|
public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView> {
|
||||||
|
private static final String TAG = "LVCaptureHelper";
|
||||||
|
private int mScrollDelta;
|
||||||
|
private boolean mScrollBarWasEnabled;
|
||||||
|
private int mOverScrollMode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareForStart(@NonNull ListView view, Rect scrollBounds) {
|
||||||
|
mScrollDelta = 0;
|
||||||
|
|
||||||
|
mOverScrollMode = view.getOverScrollMode();
|
||||||
|
view.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
|
||||||
|
mScrollBarWasEnabled = view.isVerticalScrollBarEnabled();
|
||||||
|
view.setVerticalScrollBarEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScrollResult onScrollRequested(@NonNull ListView listView, Rect scrollBounds,
|
||||||
|
Rect requestRect) {
|
||||||
|
Log.d(TAG, "-----------------------------------------------------------");
|
||||||
|
Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", "
|
||||||
|
+ "requestRect=" + requestRect + ")");
|
||||||
|
|
||||||
|
ScrollResult result = new ScrollResult();
|
||||||
|
result.requestedArea = new Rect(requestRect);
|
||||||
|
result.scrollDelta = mScrollDelta;
|
||||||
|
result.availableArea = new Rect(); // empty
|
||||||
|
|
||||||
|
if (!listView.isVisibleToUser() || listView.getChildCount() == 0) {
|
||||||
|
Log.w(TAG, "listView is empty or not visible, cannot continue");
|
||||||
|
return result; // result.availableArea == empty Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make requestRect relative to RecyclerView (from scrollBounds)
|
||||||
|
Rect requestedContainerBounds =
|
||||||
|
transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect);
|
||||||
|
|
||||||
|
Rect recyclerLocalVisible = new Rect();
|
||||||
|
listView.getLocalVisibleRect(recyclerLocalVisible);
|
||||||
|
|
||||||
|
// Expand request rect match visible bounds to center the requested rect vertically
|
||||||
|
Rect adjustedContainerBounds = new Rect(requestedContainerBounds);
|
||||||
|
int remainingHeight = recyclerLocalVisible.height() - requestedContainerBounds.height();
|
||||||
|
if (remainingHeight > 0) {
|
||||||
|
adjustedContainerBounds.inset(0, -remainingHeight / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scrollAmount = computeScrollAmount(recyclerLocalVisible, adjustedContainerBounds);
|
||||||
|
if (scrollAmount < 0) {
|
||||||
|
Log.d(TAG, "About to scroll UP (content moves down within parent)");
|
||||||
|
} else if (scrollAmount > 0) {
|
||||||
|
Log.d(TAG, "About to scroll DOWN (content moves up within parent)");
|
||||||
|
}
|
||||||
|
Log.d(TAG, "scrollAmount: " + scrollAmount);
|
||||||
|
|
||||||
|
View refView = findScrollingReferenceView(listView, scrollAmount);
|
||||||
|
int refTop = refView.getTop();
|
||||||
|
|
||||||
|
listView.scrollListBy(scrollAmount);
|
||||||
|
int scrollDistance = refTop - refView.getTop();
|
||||||
|
Log.d(TAG, "Parent view has scrolled vertically by " + scrollDistance + " px");
|
||||||
|
|
||||||
|
mScrollDelta += scrollDistance;
|
||||||
|
result.scrollDelta = mScrollDelta;
|
||||||
|
if (scrollDistance != 0) {
|
||||||
|
Log.d(TAG, "Scroll delta is now " + mScrollDelta + " px");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update, post-scroll
|
||||||
|
requestedContainerBounds = new Rect(
|
||||||
|
transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect));
|
||||||
|
|
||||||
|
listView.getLocalVisibleRect(recyclerLocalVisible);
|
||||||
|
if (requestedContainerBounds.intersect(recyclerLocalVisible)) {
|
||||||
|
result.availableArea = transformFromContainerToRequest(
|
||||||
|
mScrollDelta, scrollBounds, requestedContainerBounds);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "-----------------------------------------------------------");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareForEnd(@NonNull ListView listView) {
|
||||||
|
// Restore original position and state
|
||||||
|
listView.scrollListBy(-mScrollDelta);
|
||||||
|
listView.setOverScrollMode(mOverScrollMode);
|
||||||
|
listView.setVerticalScrollBarEnabled(mScrollBarWasEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,11 @@
|
|||||||
|
|
||||||
package com.android.internal.view;
|
package com.android.internal.view;
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
import static com.android.internal.view.ScrollCaptureViewSupport.computeScrollAmount;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.findScrollingReferenceView;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.transformFromContainerToRequest;
|
||||||
|
import static com.android.internal.view.ScrollCaptureViewSupport.transformFromRequestToContainer;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -38,17 +42,10 @@ import android.view.ViewParent;
|
|||||||
* @see ScrollCaptureViewSupport
|
* @see ScrollCaptureViewSupport
|
||||||
*/
|
*/
|
||||||
public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
|
public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
|
||||||
|
|
||||||
// Experiment
|
|
||||||
private static final boolean DISABLE_ANIMATORS = false;
|
|
||||||
// Experiment
|
|
||||||
private static final boolean STOP_RENDER_THREAD = false;
|
|
||||||
|
|
||||||
private static final String TAG = "RVCaptureHelper";
|
private static final String TAG = "RVCaptureHelper";
|
||||||
private int mScrollDelta;
|
private int mScrollDelta;
|
||||||
private boolean mScrollBarWasEnabled;
|
private boolean mScrollBarWasEnabled;
|
||||||
private int mOverScrollMode;
|
private int mOverScrollMode;
|
||||||
private float mDurationScale;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
|
public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
|
||||||
@@ -59,140 +56,81 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr
|
|||||||
|
|
||||||
mScrollBarWasEnabled = view.isVerticalScrollBarEnabled();
|
mScrollBarWasEnabled = view.isVerticalScrollBarEnabled();
|
||||||
view.setVerticalScrollBarEnabled(false);
|
view.setVerticalScrollBarEnabled(false);
|
||||||
if (DISABLE_ANIMATORS) {
|
|
||||||
mDurationScale = ValueAnimator.getDurationScale();
|
|
||||||
ValueAnimator.setDurationScale(0);
|
|
||||||
}
|
|
||||||
if (STOP_RENDER_THREAD) {
|
|
||||||
view.getThreadedRenderer().stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
|
public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
|
||||||
Rect requestRect) {
|
Rect requestRect) {
|
||||||
|
Log.d(TAG, "-----------------------------------------------------------");
|
||||||
|
Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", "
|
||||||
|
+ "requestRect=" + requestRect + ")");
|
||||||
|
|
||||||
ScrollResult result = new ScrollResult();
|
ScrollResult result = new ScrollResult();
|
||||||
result.requestedArea = new Rect(requestRect);
|
result.requestedArea = new Rect(requestRect);
|
||||||
result.scrollDelta = mScrollDelta;
|
result.scrollDelta = mScrollDelta;
|
||||||
result.availableArea = new Rect(); // empty
|
result.availableArea = new Rect(); // empty
|
||||||
|
|
||||||
Log.d(TAG, "current scrollDelta: " + mScrollDelta);
|
|
||||||
if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
|
if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
|
||||||
Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
|
Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
|
||||||
return result; // result.availableArea == empty Rect
|
return result; // result.availableArea == empty Rect
|
||||||
}
|
}
|
||||||
|
|
||||||
// move from scrollBounds-relative to parent-local coordinates
|
// Make requestRect relative to RecyclerView (from scrollBounds)
|
||||||
Rect requestedContainerBounds = new Rect(requestRect);
|
Rect requestedContainerBounds =
|
||||||
requestedContainerBounds.offset(0, -mScrollDelta);
|
transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect);
|
||||||
requestedContainerBounds.offset(scrollBounds.left, scrollBounds.top);
|
|
||||||
|
|
||||||
// requestedContainerBounds is now in recyclerview-local coordinates
|
Rect recyclerLocalVisible = new Rect();
|
||||||
Log.d(TAG, "requestedContainerBounds: " + requestedContainerBounds);
|
|
||||||
|
|
||||||
// Save a copy for later
|
|
||||||
View anchor = findChildNearestTarget(recyclerView, requestedContainerBounds);
|
|
||||||
if (anchor == null) {
|
|
||||||
Log.d(TAG, "Failed to locate anchor view");
|
|
||||||
return result; // result.availableArea == null
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Anchor view:" + anchor);
|
|
||||||
Rect requestedContentBounds = new Rect(requestedContainerBounds);
|
|
||||||
recyclerView.offsetRectIntoDescendantCoords(anchor, requestedContentBounds);
|
|
||||||
|
|
||||||
Log.d(TAG, "requestedContentBounds = " + requestedContentBounds);
|
|
||||||
int prevAnchorTop = anchor.getTop();
|
|
||||||
// Note: requestChildRectangleOnScreen may modify rectangle, must pass pass in a copy here
|
|
||||||
Rect input = new Rect(requestedContentBounds);
|
|
||||||
// Expand input rect to get the requested rect to be in the center
|
|
||||||
int remainingHeight = recyclerView.getHeight() - recyclerView.getPaddingTop()
|
|
||||||
- recyclerView.getPaddingBottom() - input.height();
|
|
||||||
if (remainingHeight > 0) {
|
|
||||||
input.inset(0, -remainingHeight / 2);
|
|
||||||
}
|
|
||||||
Log.d(TAG, "input (post center adjustment) = " + input);
|
|
||||||
|
|
||||||
if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) {
|
|
||||||
int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement
|
|
||||||
Log.d(TAG, "RecyclerView scrolled by " + scrolled + " px");
|
|
||||||
mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++
|
|
||||||
result.scrollDelta = mScrollDelta;
|
|
||||||
Log.d(TAG, "requestedContentBounds, (post-request-rect) = " + requestedContentBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedContainerBounds.set(requestedContentBounds);
|
|
||||||
recyclerView.offsetDescendantRectToMyCoords(anchor, requestedContainerBounds);
|
|
||||||
Log.d(TAG, "requestedContainerBounds, (post-scroll): " + requestedContainerBounds);
|
|
||||||
|
|
||||||
Rect recyclerLocalVisible = new Rect(scrollBounds);
|
|
||||||
recyclerView.getLocalVisibleRect(recyclerLocalVisible);
|
recyclerView.getLocalVisibleRect(recyclerLocalVisible);
|
||||||
Log.d(TAG, "recyclerLocalVisible: " + recyclerLocalVisible);
|
|
||||||
|
|
||||||
if (!requestedContainerBounds.intersect(recyclerLocalVisible)) {
|
// Expand request rect match visible bounds to center the requested rect vertically
|
||||||
// Requested area is still not visible
|
Rect adjustedContainerBounds = new Rect(requestedContainerBounds);
|
||||||
Log.d(TAG, "requested bounds not visible!");
|
int remainingHeight = recyclerLocalVisible.height() - requestedContainerBounds.height();
|
||||||
|
if (remainingHeight > 0) {
|
||||||
|
adjustedContainerBounds.inset(0, -remainingHeight / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scrollAmount = computeScrollAmount(recyclerLocalVisible, adjustedContainerBounds);
|
||||||
|
if (scrollAmount < 0) {
|
||||||
|
Log.d(TAG, "About to scroll UP (content moves down within parent)");
|
||||||
|
} else if (scrollAmount > 0) {
|
||||||
|
Log.d(TAG, "About to scroll DOWN (content moves up within parent)");
|
||||||
|
}
|
||||||
|
Log.d(TAG, "scrollAmount: " + scrollAmount);
|
||||||
|
|
||||||
|
View refView = findScrollingReferenceView(recyclerView, scrollAmount);
|
||||||
|
int refTop = refView.getTop();
|
||||||
|
|
||||||
|
// Map the request into the child view coords
|
||||||
|
Rect requestedContentBounds = new Rect(adjustedContainerBounds);
|
||||||
|
recyclerView.offsetRectIntoDescendantCoords(refView, requestedContentBounds);
|
||||||
|
Log.d(TAG, "request rect, in child view space = " + requestedContentBounds);
|
||||||
|
|
||||||
|
// Note: requestChildRectangleOnScreen may modify rectangle, must pass pass in a copy here
|
||||||
|
Rect request = new Rect(requestedContentBounds);
|
||||||
|
recyclerView.requestChildRectangleOnScreen(refView, request, true);
|
||||||
|
|
||||||
|
int scrollDistance = refTop - refView.getTop();
|
||||||
|
Log.d(TAG, "Parent view scrolled vertically by " + scrollDistance + " px");
|
||||||
|
|
||||||
|
mScrollDelta += scrollDistance;
|
||||||
|
result.scrollDelta = mScrollDelta;
|
||||||
|
if (scrollDistance != 0) {
|
||||||
|
Log.d(TAG, "Scroll delta is now " + mScrollDelta + " px");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update, post-scroll
|
||||||
|
requestedContainerBounds = new Rect(
|
||||||
|
transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect));
|
||||||
|
|
||||||
|
// in case it might have changed (nested scrolling)
|
||||||
|
recyclerView.getLocalVisibleRect(recyclerLocalVisible);
|
||||||
|
if (requestedContainerBounds.intersect(recyclerLocalVisible)) {
|
||||||
|
result.availableArea = transformFromContainerToRequest(
|
||||||
|
mScrollDelta, scrollBounds, requestedContainerBounds);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "-----------------------------------------------------------");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
Rect available = new Rect(requestedContainerBounds);
|
|
||||||
available.offset(-scrollBounds.left, -scrollBounds.top);
|
|
||||||
available.offset(0, mScrollDelta);
|
|
||||||
result.availableArea = available;
|
|
||||||
Log.d(TAG, "availableArea: " + result.availableArea);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a view that is located "closest" to targetRect. Returns the first view to fully
|
|
||||||
* vertically overlap the target targetRect. If none found, returns the view with an edge
|
|
||||||
* nearest the target targetRect.
|
|
||||||
*
|
|
||||||
* @param parent the parent vertical layout
|
|
||||||
* @param targetRect a rectangle in local coordinates of <code>parent</code>
|
|
||||||
* @return a child view within parent matching the criteria or null
|
|
||||||
*/
|
|
||||||
static View findChildNearestTarget(ViewGroup parent, Rect targetRect) {
|
|
||||||
View selected = null;
|
|
||||||
int minCenterDistance = Integer.MAX_VALUE;
|
|
||||||
int maxOverlap = 0;
|
|
||||||
|
|
||||||
// allowable center-center distance, relative to targetRect.
|
|
||||||
// if within this range, taller views are preferred
|
|
||||||
final float preferredRangeFromCenterPercent = 0.25f;
|
|
||||||
final int preferredDistance =
|
|
||||||
(int) (preferredRangeFromCenterPercent * targetRect.height());
|
|
||||||
|
|
||||||
Rect parentLocalVis = new Rect();
|
|
||||||
parent.getLocalVisibleRect(parentLocalVis);
|
|
||||||
Log.d(TAG, "findChildNearestTarget: parentVis=" + parentLocalVis
|
|
||||||
+ " targetRect=" + targetRect);
|
|
||||||
|
|
||||||
Rect frame = new Rect();
|
|
||||||
for (int i = 0; i < parent.getChildCount(); i++) {
|
|
||||||
final View child = parent.getChildAt(i);
|
|
||||||
child.getHitRect(frame);
|
|
||||||
Log.d(TAG, "child #" + i + " hitRect=" + frame);
|
|
||||||
|
|
||||||
if (child.getVisibility() != View.VISIBLE) {
|
|
||||||
Log.d(TAG, "child #" + i + " is not visible");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int centerDistance = Math.abs(targetRect.centerY() - frame.centerY());
|
|
||||||
Log.d(TAG, "child #" + i + " : center to center: " + centerDistance + "px");
|
|
||||||
|
|
||||||
if (centerDistance < minCenterDistance) {
|
|
||||||
// closer to center
|
|
||||||
minCenterDistance = centerDistance;
|
|
||||||
selected = child;
|
|
||||||
} else if (frame.intersect(targetRect) && (frame.height() > preferredDistance)) {
|
|
||||||
// within X% pixels of center, but taller
|
|
||||||
selected = child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepareForEnd(@NonNull ViewGroup view) {
|
public void onPrepareForEnd(@NonNull ViewGroup view) {
|
||||||
@@ -200,11 +138,5 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr
|
|||||||
view.scrollBy(0, -mScrollDelta);
|
view.scrollBy(0, -mScrollDelta);
|
||||||
view.setOverScrollMode(mOverScrollMode);
|
view.setOverScrollMode(mOverScrollMode);
|
||||||
view.setVerticalScrollBarEnabled(mScrollBarWasEnabled);
|
view.setVerticalScrollBarEnabled(mScrollBarWasEnabled);
|
||||||
if (DISABLE_ANIMATORS) {
|
|
||||||
ValueAnimator.setDurationScale(mDurationScale);
|
|
||||||
}
|
|
||||||
if (STOP_RENDER_THREAD) {
|
|
||||||
view.getThreadedRenderer().start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.Log;
|
|||||||
import android.view.ScrollCaptureCallback;
|
import android.view.ScrollCaptureCallback;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides built-in framework level Scroll Capture support for standard scrolling Views.
|
* Provides built-in framework level Scroll Capture support for standard scrolling Views.
|
||||||
@@ -180,6 +181,11 @@ public class ScrollCaptureInternal {
|
|||||||
+ "[" + resolveId(view.getContext(), view.getId()) + "]"
|
+ "[" + resolveId(view.getContext(), view.getId()) + "]"
|
||||||
+ " -> TYPE_RECYCLING");
|
+ " -> TYPE_RECYCLING");
|
||||||
}
|
}
|
||||||
|
if (view instanceof ListView) {
|
||||||
|
// ListView is special.
|
||||||
|
return new ScrollCaptureViewSupport<>((ListView) view,
|
||||||
|
new ListViewCaptureHelper());
|
||||||
|
}
|
||||||
return new ScrollCaptureViewSupport<>((ViewGroup) view,
|
return new ScrollCaptureViewSupport<>((ViewGroup) view,
|
||||||
new RecyclerViewCaptureHelper());
|
new RecyclerViewCaptureHelper());
|
||||||
case TYPE_FIXED:
|
case TYPE_FIXED:
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ import android.content.ContentResolver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.graphics.HardwareRenderer;
|
import android.graphics.HardwareRenderer;
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.RecordingCanvas;
|
import android.graphics.RecordingCanvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.graphics.RenderNode;
|
import android.graphics.RenderNode;
|
||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -35,6 +33,7 @@ import android.view.ScrollCaptureCallback;
|
|||||||
import android.view.ScrollCaptureSession;
|
import android.view.ScrollCaptureSession;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
|
import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
|
||||||
|
|
||||||
@@ -88,6 +87,113 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
|
|||||||
return colorMode;
|
return colorMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a rect in request bounds relative space (relative to requestBounds) to container-local
|
||||||
|
* space, accounting for the provided value of scrollY.
|
||||||
|
*
|
||||||
|
* @param scrollY the current scroll offset to apply to rect
|
||||||
|
* @param requestBounds defines the local coordinate space of rect, within the container
|
||||||
|
* @param requestRect the rectangle to transform to container-local coordinates
|
||||||
|
* @return the same rectangle mapped to container bounds
|
||||||
|
*/
|
||||||
|
public static Rect transformFromRequestToContainer(int scrollY, Rect requestBounds,
|
||||||
|
Rect requestRect) {
|
||||||
|
Rect requestedContainerBounds = new Rect(requestRect);
|
||||||
|
requestedContainerBounds.offset(0, -scrollY);
|
||||||
|
requestedContainerBounds.offset(requestBounds.left, requestBounds.top);
|
||||||
|
return requestedContainerBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a rect in container-local coordinate space to request space (relative to
|
||||||
|
* requestBounds), accounting for the provided value of scrollY.
|
||||||
|
*
|
||||||
|
* @param scrollY the current scroll offset of the container
|
||||||
|
* @param requestBounds defines the local coordinate space of rect, within the container
|
||||||
|
* @param containerRect the rectangle within the container local coordinate space
|
||||||
|
* @return the same rectangle mapped to within request bounds
|
||||||
|
*/
|
||||||
|
public static Rect transformFromContainerToRequest(int scrollY, Rect requestBounds,
|
||||||
|
Rect containerRect) {
|
||||||
|
Rect requestRect = new Rect(containerRect);
|
||||||
|
requestRect.offset(-requestBounds.left, -requestBounds.top);
|
||||||
|
requestRect.offset(0, scrollY);
|
||||||
|
return requestRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the core contract of requestRectangleOnScreen. Given a bounding rect and
|
||||||
|
* another rectangle, return the minimum scroll distance that will maximize the visible area
|
||||||
|
* of the requested rectangle.
|
||||||
|
*
|
||||||
|
* @param parentVisibleBounds the visible area
|
||||||
|
* @param requested the requested area
|
||||||
|
*/
|
||||||
|
public static int computeScrollAmount(Rect parentVisibleBounds, Rect requested) {
|
||||||
|
final int height = parentVisibleBounds.height();
|
||||||
|
final int top = parentVisibleBounds.top;
|
||||||
|
final int bottom = parentVisibleBounds.bottom;
|
||||||
|
int scrollYDelta = 0;
|
||||||
|
|
||||||
|
if (requested.bottom > bottom && requested.top > top) {
|
||||||
|
// need to scroll DOWN (move views up) to get it in view:
|
||||||
|
// move just enough so that the entire rectangle is in view
|
||||||
|
// (or at least the first screen size chunk).
|
||||||
|
|
||||||
|
if (requested.height() > height) {
|
||||||
|
// just enough to get screen size chunk on
|
||||||
|
scrollYDelta += (requested.top - top);
|
||||||
|
} else {
|
||||||
|
// entire rect at bottom
|
||||||
|
scrollYDelta += (requested.bottom - bottom);
|
||||||
|
}
|
||||||
|
} else if (requested.top < top && requested.bottom < bottom) {
|
||||||
|
// need to scroll UP (move views down) to get it in view:
|
||||||
|
// move just enough so that entire rectangle is in view
|
||||||
|
// (or at least the first screen size chunk of it).
|
||||||
|
|
||||||
|
if (requested.height() > height) {
|
||||||
|
// screen size chunk
|
||||||
|
scrollYDelta -= (bottom - requested.bottom);
|
||||||
|
} else {
|
||||||
|
// entire rect at top
|
||||||
|
scrollYDelta -= (top - requested.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scrollYDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate a view to use as a reference, given an anticipated scrolling movement.
|
||||||
|
* <p>
|
||||||
|
* This view will be used to measure the actual movement of child views after scrolling.
|
||||||
|
* When scrolling down, the last (max(y)) view is used, otherwise the first (min(y)
|
||||||
|
* view. This helps to avoid recycling the reference view as a side effect of scrolling.
|
||||||
|
*
|
||||||
|
* @param parent the scrolling container
|
||||||
|
* @param expectedScrollDistance the amount of scrolling to perform
|
||||||
|
*/
|
||||||
|
public static View findScrollingReferenceView(ViewGroup parent, int expectedScrollDistance) {
|
||||||
|
View selected = null;
|
||||||
|
Rect parentLocalVisible = new Rect();
|
||||||
|
parent.getLocalVisibleRect(parentLocalVisible);
|
||||||
|
|
||||||
|
final int childCount = parent.getChildCount();
|
||||||
|
for (int i = 0; i < childCount; i++) {
|
||||||
|
View child = parent.getChildAt(i);
|
||||||
|
if (selected == null) {
|
||||||
|
selected = child;
|
||||||
|
} else if (expectedScrollDistance < 0) {
|
||||||
|
if (child.getTop() < selected.getTop()) {
|
||||||
|
selected = child;
|
||||||
|
}
|
||||||
|
} else if (child.getBottom() > selected.getBottom()) {
|
||||||
|
selected = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) {
|
public final void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) {
|
||||||
if (signal.isCanceled()) {
|
if (signal.isCanceled()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user