1 /*
2  * Copyright (C) 2015 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.launcher3.widget.picker;
18 
19 import android.content.Context;
20 import android.graphics.Point;
21 import android.util.AttributeSet;
22 import android.view.MotionEvent;
23 
24 import androidx.recyclerview.widget.LinearLayoutManager;
25 import androidx.recyclerview.widget.RecyclerView;
26 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
27 
28 import com.android.launcher3.FastScrollRecyclerView;
29 import com.android.launcher3.R;
30 import com.android.launcher3.util.ScrollableLayoutManager;
31 
32 /**
33  * The widgets recycler view.
34  */
35 public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnItemTouchListener {
36 
37     private WidgetsListAdapter mAdapter;
38 
39     private final int mScrollbarTop;
40 
41     private final Point mFastScrollerOffset = new Point();
42     private boolean mTouchDownOnScroller;
43     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
44 
WidgetsRecyclerView(Context context)45     public WidgetsRecyclerView(Context context) {
46         this(context, null);
47     }
48 
WidgetsRecyclerView(Context context, AttributeSet attrs)49     public WidgetsRecyclerView(Context context, AttributeSet attrs) {
50         this(context, attrs, 0);
51     }
52 
WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr)53     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
54         // API 21 and below only support 3 parameter ctor.
55         super(context, attrs, defStyleAttr);
56         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
57         addOnItemTouchListener(this);
58     }
59 
60     @Override
onFinishInflate()61     protected void onFinishInflate() {
62         super.onFinishInflate();
63         setLayoutManager(new ScrollableLayoutManager(getContext()));
64     }
65 
66     @Override
setAdapter(Adapter adapter)67     public void setAdapter(Adapter adapter) {
68         super.setAdapter(adapter);
69         mAdapter = (WidgetsListAdapter) adapter;
70     }
71 
72     /**
73      * Maps the touch (from 0..1) to the adapter position that should be visible.
74      */
75     @Override
scrollToPositionAtProgress(float touchFraction)76     public CharSequence scrollToPositionAtProgress(float touchFraction) {
77         // Skip early if widgets are not bound.
78         if (isModelNotReady()) {
79             return "";
80         }
81 
82         // Stop the scroller if it is scrolling
83         stopScroll();
84 
85         int rowCount = mAdapter.getItemCount();
86         float pos = rowCount * touchFraction;
87         int availableScrollHeight = getAvailableScrollHeight();
88         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
89         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
90 
91         int posInt = (int) ((touchFraction == 1) ? pos - 1 : pos);
92         return mAdapter.getSectionName(posInt);
93     }
94 
95     /**
96      * Updates the bounds for the scrollbar.
97      */
98     @Override
onUpdateScrollbar(int dy)99     public void onUpdateScrollbar(int dy) {
100         // Skip early if widgets are not bound.
101         if (isModelNotReady()) {
102             mScrollbar.setThumbOffsetY(-1);
103             return;
104         }
105 
106         // Skip early if, there no child laid out in the container.
107         int scrollY = computeVerticalScrollOffset();
108         if (scrollY < 0) {
109             mScrollbar.setThumbOffsetY(-1);
110             return;
111         }
112 
113         synchronizeScrollBarThumbOffsetToViewScroll(scrollY, getAvailableScrollHeight());
114     }
115 
isModelNotReady()116     private boolean isModelNotReady() {
117         return mAdapter.getItemCount() == 0;
118     }
119 
120     @Override
getScrollBarTop()121     public int getScrollBarTop() {
122         return mHeaderViewDimensionsProvider == null
123                 ? mScrollbarTop
124                 : mHeaderViewDimensionsProvider.getHeaderViewHeight() + mScrollbarTop;
125     }
126 
127     @Override
onInterceptTouchEvent(RecyclerView rv, MotionEvent e)128     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
129         if (e.getAction() == MotionEvent.ACTION_DOWN) {
130             mTouchDownOnScroller = isHitOnScrollBar(e);
131         }
132         if (mTouchDownOnScroller) {
133             final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
134             return result;
135         }
136         return false;
137     }
138 
139     @Override
onTouchEvent(RecyclerView rv, MotionEvent e)140     public void onTouchEvent(RecyclerView rv, MotionEvent e) {
141         if (mTouchDownOnScroller) {
142             mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
143         }
144     }
145 
146     /**
147      * Detects whether a {@code MotionEvent} is on the scroll bar
148      * @param e The {@code MotionEvent} on the screen
149      * @return {@code true} if the motion is on the scroll bar
150      */
isHitOnScrollBar(MotionEvent e)151     boolean isHitOnScrollBar(MotionEvent e) {
152         return mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
153     }
154 
155     @Override
onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)156     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
157     }
158 
setHeaderViewDimensionsProvider( HeaderViewDimensionsProvider headerViewDimensionsProvider)159     public void setHeaderViewDimensionsProvider(
160             HeaderViewDimensionsProvider headerViewDimensionsProvider) {
161         mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
162     }
163 
164     /**
165      * Provides dimensions of the header view that is shown at the top of a
166      * {@link WidgetsRecyclerView}.
167      */
168     public interface HeaderViewDimensionsProvider {
169         /**
170          * Returns the height, in pixels, of the header view that is shown at the top of a
171          * {@link WidgetsRecyclerView}.
172          */
getHeaderViewHeight()173         int getHeaderViewHeight();
174     }
175 }
176