1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.view; 18 19 import static com.android.internal.view.ScrollCaptureViewSupport.computeScrollAmount; 20 import static com.android.internal.view.ScrollCaptureViewSupport.findScrollingReferenceView; 21 import static com.android.internal.view.ScrollCaptureViewSupport.transformFromContainerToRequest; 22 import static com.android.internal.view.ScrollCaptureViewSupport.transformFromRequestToContainer; 23 24 import android.annotation.NonNull; 25 import android.graphics.Rect; 26 import android.os.CancellationSignal; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.ListView; 30 31 import java.util.function.Consumer; 32 33 /** 34 * Scroll capture support for ListView. 35 * 36 * @see ScrollCaptureViewSupport 37 */ 38 public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView> { 39 private static final String TAG = "LVCaptureHelper"; 40 private int mScrollDelta; 41 private boolean mScrollBarWasEnabled; 42 private int mOverScrollMode; 43 44 @Override onAcceptSession(@onNull ListView view)45 public boolean onAcceptSession(@NonNull ListView view) { 46 return view.isVisibleToUser() 47 && (view.canScrollVertically(UP) || view.canScrollVertically(DOWN)); 48 } 49 50 @Override onPrepareForStart(@onNull ListView view, Rect scrollBounds)51 public void onPrepareForStart(@NonNull ListView view, Rect scrollBounds) { 52 mScrollDelta = 0; 53 54 mOverScrollMode = view.getOverScrollMode(); 55 view.setOverScrollMode(View.OVER_SCROLL_NEVER); 56 57 mScrollBarWasEnabled = view.isVerticalScrollBarEnabled(); 58 view.setVerticalScrollBarEnabled(false); 59 } 60 61 @Override onScrollRequested(@onNull ListView listView, Rect scrollBounds, Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer)62 public void onScrollRequested(@NonNull ListView listView, Rect scrollBounds, 63 Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) { 64 Log.d(TAG, "-----------------------------------------------------------"); 65 Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", " 66 + "requestRect=" + requestRect + ")"); 67 68 ScrollResult result = new ScrollResult(); 69 result.requestedArea = new Rect(requestRect); 70 result.scrollDelta = mScrollDelta; 71 result.availableArea = new Rect(); // empty 72 73 if (!listView.isVisibleToUser() || listView.getChildCount() == 0) { 74 Log.w(TAG, "listView is empty or not visible, cannot continue"); 75 resultConsumer.accept(result); // result.availableArea == empty Rect 76 return; 77 } 78 79 // Make requestRect relative to RecyclerView (from scrollBounds) 80 Rect requestedContainerBounds = 81 transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect); 82 83 Rect recyclerLocalVisible = new Rect(); 84 listView.getLocalVisibleRect(recyclerLocalVisible); 85 86 // Expand request rect match visible bounds to center the requested rect vertically 87 Rect adjustedContainerBounds = new Rect(requestedContainerBounds); 88 int remainingHeight = recyclerLocalVisible.height() - requestedContainerBounds.height(); 89 if (remainingHeight > 0) { 90 adjustedContainerBounds.inset(0, -remainingHeight / 2); 91 } 92 93 int scrollAmount = computeScrollAmount(recyclerLocalVisible, adjustedContainerBounds); 94 if (scrollAmount < 0) { 95 Log.d(TAG, "About to scroll UP (content moves down within parent)"); 96 } else if (scrollAmount > 0) { 97 Log.d(TAG, "About to scroll DOWN (content moves up within parent)"); 98 } 99 Log.d(TAG, "scrollAmount: " + scrollAmount); 100 101 View refView = findScrollingReferenceView(listView, scrollAmount); 102 int refTop = refView.getTop(); 103 104 listView.scrollListBy(scrollAmount); 105 int scrollDistance = refTop - refView.getTop(); 106 Log.d(TAG, "Parent view has scrolled vertically by " + scrollDistance + " px"); 107 108 mScrollDelta += scrollDistance; 109 result.scrollDelta = mScrollDelta; 110 if (scrollDistance != 0) { 111 Log.d(TAG, "Scroll delta is now " + mScrollDelta + " px"); 112 } 113 114 // Update, post-scroll 115 requestedContainerBounds = new Rect( 116 transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect)); 117 118 listView.getLocalVisibleRect(recyclerLocalVisible); 119 if (requestedContainerBounds.intersect(recyclerLocalVisible)) { 120 result.availableArea = transformFromContainerToRequest( 121 mScrollDelta, scrollBounds, requestedContainerBounds); 122 } 123 Log.d(TAG, "-----------------------------------------------------------"); 124 resultConsumer.accept(result); 125 } 126 127 @Override onPrepareForEnd(@onNull ListView listView)128 public void onPrepareForEnd(@NonNull ListView listView) { 129 // Restore original position and state 130 listView.scrollListBy(-mScrollDelta); 131 listView.setOverScrollMode(mOverScrollMode); 132 listView.setVerticalScrollBarEnabled(mScrollBarWasEnabled); 133 } 134 } 135