blob: 730c8d15ac1ea3865a3014afdd052d95ad805e0a [file] [log] [blame]
Winson Chung93f98ea2015-03-10 16:28:47 -07001/*
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 */
Winson Chung5f4e0fd2015-05-22 11:12:27 -070016package com.android.launcher3.allapps;
Winson Chung93f98ea2015-03-10 16:28:47 -070017
Winson Chung93f98ea2015-03-10 16:28:47 -070018import android.content.Context;
Winson Chung243fdd72015-06-22 19:48:07 -070019import android.graphics.Canvas;
Winson Chung8f1eff72015-05-28 17:33:40 -070020import android.os.Bundle;
Winson Chung24cf7002015-03-30 14:25:04 -070021import android.support.v7.widget.LinearLayoutManager;
Winson Chung93f98ea2015-03-10 16:28:47 -070022import android.support.v7.widget.RecyclerView;
23import android.util.AttributeSet;
Winson Chungf819dc22015-03-23 14:45:54 -070024import android.view.View;
Sunny Goyalb713ad42015-06-24 11:45:32 -070025
Winson Chung5f4e0fd2015-05-22 11:12:27 -070026import com.android.launcher3.BaseRecyclerView;
Winson Chungb1777442015-06-16 13:35:04 -070027import com.android.launcher3.BaseRecyclerViewFastScrollBar;
Winson Chung5f4e0fd2015-05-22 11:12:27 -070028import com.android.launcher3.DeviceProfile;
Winson Chung8f1eff72015-05-28 17:33:40 -070029import com.android.launcher3.Stats;
Winson Chung1ae7a502015-07-06 17:14:51 -070030import com.android.launcher3.Utilities;
Sunny Goyalb713ad42015-06-24 11:45:32 -070031import com.android.launcher3.util.Thunk;
Winson Chung93f98ea2015-03-10 16:28:47 -070032
33import java.util.List;
34
35/**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070036 * A RecyclerView with custom fast scroll support for the all apps view.
Winson Chung93f98ea2015-03-10 16:28:47 -070037 */
Winson Chung8f1eff72015-05-28 17:33:40 -070038public class AllAppsRecyclerView extends BaseRecyclerView
39 implements Stats.LaunchSourceProvider {
Winson Chung93f98ea2015-03-10 16:28:47 -070040
Winson Chungb1777442015-06-16 13:35:04 -070041 private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
42 private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
43
44 private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
45 private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
46
Winson Chung93f98ea2015-03-10 16:28:47 -070047 private AlphabeticalAppsList mApps;
48 private int mNumAppsPerRow;
Winson Chungb1777442015-06-16 13:35:04 -070049
Sunny Goyalb713ad42015-06-24 11:45:32 -070050 @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
51 @Thunk int mPrevFastScrollFocusedPosition;
52 @Thunk int mFastScrollFrameIndex;
53 @Thunk final int[] mFastScrollFrames = new int[10];
54
Winson Chungb1777442015-06-16 13:35:04 -070055 private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
56 private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
Winson Chung93f98ea2015-03-10 16:28:47 -070057
Winson Chungb1777442015-06-16 13:35:04 -070058 private ScrollPositionState mScrollPosState = new ScrollPositionState();
Adam Cohen2e6da152015-05-06 11:42:25 -070059
Winson Chung5f4e0fd2015-05-22 11:12:27 -070060 public AllAppsRecyclerView(Context context) {
Winson Chung93f98ea2015-03-10 16:28:47 -070061 this(context, null);
62 }
63
Winson Chung5f4e0fd2015-05-22 11:12:27 -070064 public AllAppsRecyclerView(Context context, AttributeSet attrs) {
Winson Chung93f98ea2015-03-10 16:28:47 -070065 this(context, attrs, 0);
66 }
67
Winson Chung5f4e0fd2015-05-22 11:12:27 -070068 public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
Winson Chung93f98ea2015-03-10 16:28:47 -070069 this(context, attrs, defStyleAttr, 0);
70 }
71
Winson Chung5f4e0fd2015-05-22 11:12:27 -070072 public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
Winson Chung93f98ea2015-03-10 16:28:47 -070073 int defStyleRes) {
74 super(context, attrs, defStyleAttr);
Winson Chung93f98ea2015-03-10 16:28:47 -070075 }
76
77 /**
78 * Sets the list of apps in this view, used to determine the fastscroll position.
79 */
80 public void setApps(AlphabeticalAppsList apps) {
81 mApps = apps;
82 }
83
84 /**
85 * Sets the number of apps per row in this recycler view.
86 */
Winson Chung1cddad62015-06-25 16:46:47 -070087 public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
Winson Chung208ed752015-05-12 19:05:30 -070088 mNumAppsPerRow = numAppsPerRow;
Winson Chungda25da32015-05-15 16:30:08 -070089
Winson Chungda25da32015-05-15 16:30:08 -070090 RecyclerView.RecycledViewPool pool = getRecycledViewPool();
91 int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
Winson Chung5f4e0fd2015-05-22 11:12:27 -070092 pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
93 pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
Winson Chung1ae7a502015-07-06 17:14:51 -070094 pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
Winson Chung5f4e0fd2015-05-22 11:12:27 -070095 pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
Winson Chung93f98ea2015-03-10 16:28:47 -070096 }
97
98 /**
Winson Chunged0c1cc2015-05-12 13:01:54 -070099 * Scrolls this recycler view to the top.
100 */
101 public void scrollToTop() {
102 scrollToPosition(0);
Winson Chung32f14072015-05-15 12:03:37 -0700103 }
104
105 /**
Winson Chung243fdd72015-06-22 19:48:07 -0700106 * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
107 * background bounds.
108 */
109 @Override
110 protected void dispatchDraw(Canvas canvas) {
111 canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
112 getWidth() - mBackgroundPadding.right,
113 getHeight() - mBackgroundPadding.bottom);
114 super.dispatchDraw(canvas);
115 }
116
Winson Chung93f98ea2015-03-10 16:28:47 -0700117 @Override
118 protected void onFinishInflate() {
Hyunyoung Songebe17342015-05-06 17:49:33 -0700119 super.onFinishInflate();
Winson Chungef7f8742015-06-04 17:18:17 -0700120
121 // Bind event handlers
Winson Chung93f98ea2015-03-10 16:28:47 -0700122 addOnItemTouchListener(this);
123 }
124
Winson Chung8f1eff72015-05-28 17:33:40 -0700125 @Override
126 public void fillInLaunchSourceData(Bundle sourceData) {
127 sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
128 if (mApps.hasFilter()) {
129 sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
130 Stats.SUB_CONTAINER_ALL_APPS_SEARCH);
131 } else {
132 sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
133 Stats.SUB_CONTAINER_ALL_APPS_A_Z);
134 }
135 }
136
Winson Chung93f98ea2015-03-10 16:28:47 -0700137 /**
Winson Chung99d96ba2015-05-08 12:04:45 -0700138 * Maps the touch (from 0..1) to the adapter position that should be visible.
Winson Chung93f98ea2015-03-10 16:28:47 -0700139 */
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700140 @Override
141 public String scrollToPositionAtProgress(float touchFraction) {
Winson Chungb1777442015-06-16 13:35:04 -0700142 int rowCount = mApps.getNumAppRows();
143 if (rowCount == 0) {
Winson Chung9121fbf2015-04-06 15:12:49 -0700144 return "";
145 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700146
Winson Chung208ed752015-05-12 19:05:30 -0700147 // Stop the scroller if it is scrolling
Winson Chung208ed752015-05-12 19:05:30 -0700148 stopScroll();
149
Winson Chungb1777442015-06-16 13:35:04 -0700150 // Find the fastscroll section that maps to this touch fraction
151 List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
152 mApps.getFastScrollerSections();
153 AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
154 if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
155 for (int i = 1; i < fastScrollSections.size(); i++) {
156 AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
157 if (info.touchFraction > touchFraction) {
158 break;
159 }
160 lastInfo = info;
161 }
162 } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
163 lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1)));
164 } else {
165 throw new RuntimeException("Unexpected scroll bar mode");
166 }
167
168 // Map the touch position back to the scroll of the recycler view
169 getCurScrollState(mScrollPosState, mApps.getAdapterItems());
Winson Chung1ae7a502015-07-06 17:14:51 -0700170 int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
Winson Chungb1777442015-06-16 13:35:04 -0700171 LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
172 if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
173 layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
174 }
175
176 if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
177 mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
178
179 // Reset the last focused view
180 if (mLastFastScrollFocusedView != null) {
181 mLastFastScrollFocusedView.setFastScrollFocused(false, true);
182 mLastFastScrollFocusedView = null;
183 }
184
185 if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
186 smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
187 } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
188 final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
189 if (vh != null &&
190 vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
191 mLastFastScrollFocusedView =
192 (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
193 mLastFastScrollFocusedView.setFastScrollFocused(true, true);
194 }
195 } else {
196 throw new RuntimeException("Unexpected fast scroll mode");
Winson Chung208ed752015-05-12 19:05:30 -0700197 }
198 }
Winson Chungb1777442015-06-16 13:35:04 -0700199 return lastInfo.sectionName;
Winson Chung93f98ea2015-03-10 16:28:47 -0700200 }
Winson Chungf819dc22015-03-23 14:45:54 -0700201
Winson Chungb1777442015-06-16 13:35:04 -0700202 @Override
203 public void onFastScrollCompleted() {
204 super.onFastScrollCompleted();
205 // Reset and clean up the last focused view
206 if (mLastFastScrollFocusedView != null) {
207 mLastFastScrollFocusedView.setFastScrollFocused(false, true);
208 mLastFastScrollFocusedView = null;
Winson Chungf819dc22015-03-23 14:45:54 -0700209 }
Winson Chungb1777442015-06-16 13:35:04 -0700210 mPrevFastScrollFocusedPosition = -1;
Winson Chungf819dc22015-03-23 14:45:54 -0700211 }
212
213 /**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700214 * Updates the bounds for the scrollbar.
215 */
216 @Override
Winson Chungb1777442015-06-16 13:35:04 -0700217 public void onUpdateScrollbar() {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700218 List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
219
Winson Chungef7f8742015-06-04 17:18:17 -0700220 // Skip early if there are no items or we haven't been measured
221 if (items.isEmpty() || mNumAppsPerRow == 0) {
Winson Chungb1777442015-06-16 13:35:04 -0700222 mScrollbar.setScrollbarThumbOffset(-1, -1);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700223 return;
224 }
225
226 // Find the index and height of the first visible row (all rows have the same height)
Winson Chungb1777442015-06-16 13:35:04 -0700227 int rowCount = mApps.getNumAppRows();
228 getCurScrollState(mScrollPosState, items);
229 if (mScrollPosState.rowIndex < 0) {
230 mScrollbar.setScrollbarThumbOffset(-1, -1);
231 return;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700232 }
Winson Chungb1777442015-06-16 13:35:04 -0700233
Winson Chung1ae7a502015-07-06 17:14:51 -0700234 synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700235 }
236
Winson Chunged0c1cc2015-05-12 13:01:54 -0700237 /**
Winson Chungb1777442015-06-16 13:35:04 -0700238 * This runnable runs a single frame of the smooth scroll animation and posts the next frame
239 * if necessary.
240 */
Sunny Goyalb713ad42015-06-24 11:45:32 -0700241 @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
Winson Chungb1777442015-06-16 13:35:04 -0700242 @Override
243 public void run() {
244 if (mFastScrollFrameIndex < mFastScrollFrames.length) {
245 scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
246 mFastScrollFrameIndex++;
247 postOnAnimation(mSmoothSnapNextFrameRunnable);
248 } else {
249 // Animation completed, set the fast scroll state on the target view
250 final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
251 if (vh != null &&
252 vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
253 mLastFastScrollFocusedView != vh.itemView) {
254 mLastFastScrollFocusedView =
255 (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
256 mLastFastScrollFocusedView.setFastScrollFocused(true, true);
257 }
258 }
259 }
260 };
261
262 /**
263 * Smoothly snaps to a given position. We do this manually by calculating the keyframes
264 * ourselves and animating the scroll on the recycler view.
265 */
266 private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
267 removeCallbacks(mSmoothSnapNextFrameRunnable);
268
269 // Calculate the full animation from the current scroll position to the final scroll
270 // position, and then run the animation for the duration.
Winson Chung1ae7a502015-07-06 17:14:51 -0700271 int curScrollY = getPaddingTop() +
Winson Chungb1777442015-06-16 13:35:04 -0700272 (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
273 int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
274 int numFrames = mFastScrollFrames.length;
275 for (int i = 0; i < numFrames; i++) {
276 // TODO(winsonc): We can interpolate this as well.
277 mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
278 }
279 mFastScrollFrameIndex = 0;
280 postOnAnimation(mSmoothSnapNextFrameRunnable);
281 }
282
283 /**
Winson Chung1ae7a502015-07-06 17:14:51 -0700284 * Returns the current scroll state of the apps rows.
Winson Chunged0c1cc2015-05-12 13:01:54 -0700285 */
286 private void getCurScrollState(ScrollPositionState stateOut,
287 List<AlphabeticalAppsList.AdapterItem> items) {
Winson Chunged0c1cc2015-05-12 13:01:54 -0700288 stateOut.rowIndex = -1;
289 stateOut.rowTopOffset = -1;
290 stateOut.rowHeight = -1;
Winson Chung532e6ea2015-05-14 12:24:25 -0700291
Winson Chungef7f8742015-06-04 17:18:17 -0700292 // Return early if there are no items or we haven't been measured
293 if (items.isEmpty() || mNumAppsPerRow == 0) {
Winson Chung532e6ea2015-05-14 12:24:25 -0700294 return;
295 }
296
Winson Chunged0c1cc2015-05-12 13:01:54 -0700297 int childCount = getChildCount();
298 for (int i = 0; i < childCount; i++) {
299 View child = getChildAt(i);
300 int position = getChildPosition(child);
301 if (position != NO_POSITION) {
302 AlphabeticalAppsList.AdapterItem item = items.get(position);
Winson Chung1ae7a502015-07-06 17:14:51 -0700303 if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
304 item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
Winson Chungb1777442015-06-16 13:35:04 -0700305 stateOut.rowIndex = item.rowIndex;
Winson Chunged0c1cc2015-05-12 13:01:54 -0700306 stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
307 stateOut.rowHeight = child.getHeight();
308 break;
309 }
310 }
311 }
312 }
Winson Chungb1777442015-06-16 13:35:04 -0700313
314 /**
315 * Returns the scrollY for the given position in the adapter.
316 */
317 private int getScrollAtPosition(int position, int rowHeight) {
318 AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
Winson Chung1ae7a502015-07-06 17:14:51 -0700319 if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
320 item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
321 int offset = item.rowIndex > 0 ? getPaddingTop() : 0;
322 return offset + item.rowIndex * rowHeight;
Winson Chungb1777442015-06-16 13:35:04 -0700323 } else {
324 return 0;
325 }
326 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700327}