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