OSDN Git Service

resolved conflicts for merge of 13ef17a3 to mnc-dr-dev
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / allapps / AllAppsRecyclerView.java
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 package com.android.launcher3.allapps;
17
18 import android.content.Context;
19 import android.graphics.Canvas;
20 import android.os.Bundle;
21 import android.support.v7.widget.LinearLayoutManager;
22 import android.support.v7.widget.RecyclerView;
23 import android.util.AttributeSet;
24 import android.view.View;
25
26 import com.android.launcher3.BaseRecyclerView;
27 import com.android.launcher3.BaseRecyclerViewFastScrollBar;
28 import com.android.launcher3.DeviceProfile;
29 import com.android.launcher3.Stats;
30 import com.android.launcher3.Utilities;
31 import com.android.launcher3.util.Thunk;
32
33 import java.util.List;
34
35 /**
36  * A RecyclerView with custom fast scroll support for the all apps view.
37  */
38 public class AllAppsRecyclerView extends BaseRecyclerView
39         implements Stats.LaunchSourceProvider {
40
41     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
47     private AlphabeticalAppsList mApps;
48     private int mNumAppsPerRow;
49
50     @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
51     @Thunk int mPrevFastScrollFocusedPosition;
52     @Thunk int mFastScrollFrameIndex;
53     @Thunk final int[] mFastScrollFrames = new int[10];
54
55     private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
56     private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
57
58     private ScrollPositionState mScrollPosState = new ScrollPositionState();
59
60     public AllAppsRecyclerView(Context context) {
61         this(context, null);
62     }
63
64     public AllAppsRecyclerView(Context context, AttributeSet attrs) {
65         this(context, attrs, 0);
66     }
67
68     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
69         this(context, attrs, defStyleAttr, 0);
70     }
71
72     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
73             int defStyleRes) {
74         super(context, attrs, defStyleAttr);
75     }
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      */
87     public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
88         mNumAppsPerRow = numAppsPerRow;
89
90         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
91         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
92         pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
93         pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
94         pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
95         pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
96     }
97
98     /**
99      * Scrolls this recycler view to the top.
100      */
101     public void scrollToTop() {
102         scrollToPosition(0);
103     }
104
105     /**
106      * 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
117     @Override
118     protected void onFinishInflate() {
119         super.onFinishInflate();
120
121         // Bind event handlers
122         addOnItemTouchListener(this);
123     }
124
125     @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
137     /**
138      * Maps the touch (from 0..1) to the adapter position that should be visible.
139      */
140     @Override
141     public String scrollToPositionAtProgress(float touchFraction) {
142         int rowCount = mApps.getNumAppRows();
143         if (rowCount == 0) {
144             return "";
145         }
146
147         // Stop the scroller if it is scrolling
148         stopScroll();
149
150         // 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());
170         int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
171         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");
197             }
198         }
199         return lastInfo.sectionName;
200     }
201
202     @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;
209         }
210         mPrevFastScrollFocusedPosition = -1;
211     }
212
213     /**
214      * Updates the bounds for the scrollbar.
215      */
216     @Override
217     public void onUpdateScrollbar() {
218         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
219
220         // Skip early if there are no items or we haven't been measured
221         if (items.isEmpty() || mNumAppsPerRow == 0) {
222             mScrollbar.setScrollbarThumbOffset(-1, -1);
223             return;
224         }
225
226         // Find the index and height of the first visible row (all rows have the same height)
227         int rowCount = mApps.getNumAppRows();
228         getCurScrollState(mScrollPosState, items);
229         if (mScrollPosState.rowIndex < 0) {
230             mScrollbar.setScrollbarThumbOffset(-1, -1);
231             return;
232         }
233
234         synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
235     }
236
237     /**
238      * This runnable runs a single frame of the smooth scroll animation and posts the next frame
239      * if necessary.
240      */
241     @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
242         @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.
271         int curScrollY = getPaddingTop() +
272                 (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     /**
284      * Returns the current scroll state of the apps rows.
285      */
286     private void getCurScrollState(ScrollPositionState stateOut,
287             List<AlphabeticalAppsList.AdapterItem> items) {
288         stateOut.rowIndex = -1;
289         stateOut.rowTopOffset = -1;
290         stateOut.rowHeight = -1;
291
292         // Return early if there are no items or we haven't been measured
293         if (items.isEmpty() || mNumAppsPerRow == 0) {
294             return;
295         }
296
297         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);
303                 if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
304                         item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
305                     stateOut.rowIndex = item.rowIndex;
306                     stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
307                     stateOut.rowHeight = child.getHeight();
308                     break;
309                 }
310             }
311         }
312     }
313
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);
319         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;
323         } else {
324             return 0;
325         }
326     }
327 }