diff options
| -rw-r--r-- | core/java/com/android/internal/view/RecyclerViewCaptureHelper.java | 145 |
1 files changed, 98 insertions, 47 deletions
diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index b29cf1c2cb1a..848a5ba77317 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -61,77 +61,128 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr @Override public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, 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 + Log.d(TAG, "current scrollDelta: " + mScrollDelta); if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) { Log.w(TAG, "recyclerView 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(); - recyclerView.getLocalVisibleRect(recyclerLocalVisible); + // move from scrollBounds-relative to parent-local coordinates + Rect requestedContainerBounds = new Rect(requestRect); + requestedContainerBounds.offset(0, -mScrollDelta); + requestedContainerBounds.offset(scrollBounds.left, scrollBounds.top); - // 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); - } + // requestedContainerBounds is now in recyclerview-local coordinates + Log.d(TAG, "requestedContainerBounds: " + requestedContainerBounds); - 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)"); + // 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, "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); + 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 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"); + 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); } - // Update, post-scroll - requestedContainerBounds = new Rect( - transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect)); + requestedContainerBounds.set(requestedContentBounds); + recyclerView.offsetDescendantRectToMyCoords(anchor, requestedContainerBounds); + Log.d(TAG, "requestedContainerBounds, (post-scroll): " + requestedContainerBounds); - // in case it might have changed (nested scrolling) + Rect recyclerLocalVisible = new Rect(scrollBounds); recyclerView.getLocalVisibleRect(recyclerLocalVisible); - if (requestedContainerBounds.intersect(recyclerLocalVisible)) { - result.availableArea = transformFromContainerToRequest( - mScrollDelta, scrollBounds, requestedContainerBounds); + Log.d(TAG, "recyclerLocalVisible: " + recyclerLocalVisible); + + if (!requestedContainerBounds.intersect(recyclerLocalVisible)) { + // Requested area is still not visible + Log.d(TAG, "requested bounds not visible!"); + return result; } - Log.d(TAG, "-----------------------------------------------------------"); + 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 public void onPrepareForEnd(@NonNull ViewGroup view) { // Restore original position and state |