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.util.Log; 27 import android.view.View; 28 import android.widget.ListView; 29 30 /** 31 * Scroll capture support for ListView. 32 * 33 * @see ScrollCaptureViewSupport 34 */ 35 public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView> { 36 private static final String TAG = "LVCaptureHelper"; 37 private int mScrollDelta; 38 private boolean mScrollBarWasEnabled; 39 private int mOverScrollMode; 40 41 @Override onPrepareForStart(@onNull ListView view, Rect scrollBounds)42 public void onPrepareForStart(@NonNull ListView view, Rect scrollBounds) { 43 mScrollDelta = 0; 44 45 mOverScrollMode = view.getOverScrollMode(); 46 view.setOverScrollMode(View.OVER_SCROLL_NEVER); 47 48 mScrollBarWasEnabled = view.isVerticalScrollBarEnabled(); 49 view.setVerticalScrollBarEnabled(false); 50 } 51 52 @Override onScrollRequested(@onNull ListView listView, Rect scrollBounds, Rect requestRect)53 public ScrollResult onScrollRequested(@NonNull ListView listView, Rect scrollBounds, 54 Rect requestRect) { 55 Log.d(TAG, "-----------------------------------------------------------"); 56 Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", " 57 + "requestRect=" + requestRect + ")"); 58 59 ScrollResult result = new ScrollResult(); 60 result.requestedArea = new Rect(requestRect); 61 result.scrollDelta = mScrollDelta; 62 result.availableArea = new Rect(); // empty 63 64 if (!listView.isVisibleToUser() || listView.getChildCount() == 0) { 65 Log.w(TAG, "listView is empty or not visible, cannot continue"); 66 return result; // result.availableArea == empty Rect 67 } 68 69 // Make requestRect relative to RecyclerView (from scrollBounds) 70 Rect requestedContainerBounds = 71 transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect); 72 73 Rect recyclerLocalVisible = new Rect(); 74 listView.getLocalVisibleRect(recyclerLocalVisible); 75 76 // Expand request rect match visible bounds to center the requested rect vertically 77 Rect adjustedContainerBounds = new Rect(requestedContainerBounds); 78 int remainingHeight = recyclerLocalVisible.height() - requestedContainerBounds.height(); 79 if (remainingHeight > 0) { 80 adjustedContainerBounds.inset(0, -remainingHeight / 2); 81 } 82 83 int scrollAmount = computeScrollAmount(recyclerLocalVisible, adjustedContainerBounds); 84 if (scrollAmount < 0) { 85 Log.d(TAG, "About to scroll UP (content moves down within parent)"); 86 } else if (scrollAmount > 0) { 87 Log.d(TAG, "About to scroll DOWN (content moves up within parent)"); 88 } 89 Log.d(TAG, "scrollAmount: " + scrollAmount); 90 91 View refView = findScrollingReferenceView(listView, scrollAmount); 92 int refTop = refView.getTop(); 93 94 listView.scrollListBy(scrollAmount); 95 int scrollDistance = refTop - refView.getTop(); 96 Log.d(TAG, "Parent view has scrolled vertically by " + scrollDistance + " px"); 97 98 mScrollDelta += scrollDistance; 99 result.scrollDelta = mScrollDelta; 100 if (scrollDistance != 0) { 101 Log.d(TAG, "Scroll delta is now " + mScrollDelta + " px"); 102 } 103 104 // Update, post-scroll 105 requestedContainerBounds = new Rect( 106 transformFromRequestToContainer(mScrollDelta, scrollBounds, requestRect)); 107 108 listView.getLocalVisibleRect(recyclerLocalVisible); 109 if (requestedContainerBounds.intersect(recyclerLocalVisible)) { 110 result.availableArea = transformFromContainerToRequest( 111 mScrollDelta, scrollBounds, requestedContainerBounds); 112 } 113 Log.d(TAG, "-----------------------------------------------------------"); 114 return result; 115 } 116 117 118 @Override onPrepareForEnd(@onNull ListView listView)119 public void onPrepareForEnd(@NonNull ListView listView) { 120 // Restore original position and state 121 listView.scrollListBy(-mScrollDelta); 122 listView.setOverScrollMode(mOverScrollMode); 123 listView.setVerticalScrollBarEnabled(mScrollBarWasEnabled); 124 } 125 } 126