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