diff options
author | Winson Chung <winsonc@google.com> | 2015-05-12 19:05:30 -0700 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2015-05-13 09:10:31 -0700 |
commit | 208ed75cfdb02e571273d73d056d8ed7f6f756eb (patch) | |
tree | 7996096c9fd3ad65a58f1ff6b254436195421dd2 | |
parent | 11509ad61afb7424ce83057b0d2a4b09f853651f (diff) | |
download | android_packages_apps_Trebuchet-208ed75cfdb02e571273d73d056d8ed7f6f756eb.tar.gz android_packages_apps_Trebuchet-208ed75cfdb02e571273d73d056d8ed7f6f756eb.tar.bz2 android_packages_apps_Trebuchet-208ed75cfdb02e571273d73d056d8ed7f6f756eb.zip |
Pulling out predictions into another row view.
Change-Id: Iba0d74457a1314cf0c00a88f9b07df049334e542
-rw-r--r-- | res/layout/all_apps_button.xml | 2 | ||||
-rw-r--r-- | res/layout/application.xml | 2 | ||||
-rw-r--r-- | res/layout/apps_grid_icon_view.xml (renamed from res/layout/apps_grid_row_icon_view.xml) | 2 | ||||
-rw-r--r-- | res/layout/apps_list_view.xml | 43 | ||||
-rw-r--r-- | res/layout/apps_prediction_bar_icon_view.xml | 29 | ||||
-rw-r--r-- | res/layout/folder_application.xml | 2 | ||||
-rw-r--r-- | res/layout/folder_icon.xml | 2 | ||||
-rw-r--r-- | res/values/dimens.xml | 1 | ||||
-rw-r--r-- | res/values/styles.xml | 10 | ||||
-rw-r--r-- | src/com/android/launcher3/AlphabeticalAppsList.java | 160 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsContainerRecyclerView.java | 65 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsContainerView.java | 117 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsGridAdapter.java | 102 | ||||
-rw-r--r-- | src/com/android/launcher3/DeviceProfile.java | 12 |
14 files changed, 385 insertions, 164 deletions
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml index 9d6d82bb2..68cc10932 100644 --- a/res/layout/all_apps_button.xml +++ b/res/layout/all_apps_button.xml @@ -15,5 +15,5 @@ --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/WorkspaceIcon" + style="@style/Icon" android:focusable="true" /> diff --git a/res/layout/application.xml b/res/layout/application.xml index c21dea070..831cee5b0 100644 --- a/res/layout/application.xml +++ b/res/layout/application.xml @@ -15,5 +15,5 @@ --> <com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/WorkspaceIcon" + style="@style/Icon" android:focusable="true" /> diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_grid_icon_view.xml index acb3da334..67d7d50e7 100644 --- a/res/layout/apps_grid_row_icon_view.xml +++ b/res/layout/apps_grid_icon_view.xml @@ -16,7 +16,7 @@ <com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" - style="@style/WorkspaceIcon.AppsCustomize" + style="@style/Icon.AllApps" android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml index ddcb639b8..ef20323c1 100644 --- a/res/layout/apps_list_view.xml +++ b/res/layout/apps_list_view.xml @@ -13,20 +13,37 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/apps_list" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" android:elevation="15dp" android:visibility="gone" android:focusableInTouchMode="true"> + <com.android.launcher3.AppsContainerRecyclerView + android:id="@+id/apps_list_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/apps_search_bar_height" + android:layout_gravity="center_horizontal|top" + android:clipToPadding="false" + android:focusable="true" + android:descendantFocusability="afterDescendants" /> + <LinearLayout + android:id="@+id/prediction_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/apps_search_bar_height" + android:orientation="horizontal" + android:visibility="invisible"> + </LinearLayout> + + <!-- We always want the search bar on top, so it goes last. --> <FrameLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="@dimen/apps_search_bar_height" - android:orientation="horizontal" android:background="@drawable/apps_search_bg"> <LinearLayout android:id="@+id/app_search_container" @@ -40,8 +57,8 @@ android:layout_height="wrap_content" android:layout_gravity="start|center_vertical" android:layout_marginStart="4dp" - android:paddingTop="12dp" - android:paddingBottom="12dp" + android:paddingTop="13dp" + android:paddingBottom="13dp" android:contentDescription="@string/all_apps_button_label" android:src="@drawable/ic_arrow_back_grey" /> <com.android.launcher3.AppsContainerSearchEditTextView @@ -69,19 +86,9 @@ android:layout_height="wrap_content" android:layout_gravity="end|center_vertical" android:layout_marginEnd="6dp" - android:paddingTop="12dp" - android:paddingBottom="12dp" + android:paddingTop="13dp" + android:paddingBottom="13dp" android:contentDescription="@string/apps_view_search_bar_hint" android:src="@drawable/ic_search_grey" /> </FrameLayout> - <com.android.launcher3.AppsContainerRecyclerView - android:id="@+id/apps_list_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:paddingTop="12dp" - android:paddingBottom="12dp" - android:clipToPadding="false" - android:focusable="true" - android:descendantFocusability="afterDescendants" /> -</LinearLayout>
\ No newline at end of file +</FrameLayout>
\ No newline at end of file diff --git a/res/layout/apps_prediction_bar_icon_view.xml b/res/layout/apps_prediction_bar_icon_view.xml new file mode 100644 index 000000000..4a6f1574b --- /dev/null +++ b/res/layout/apps_prediction_bar_icon_view.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher3.BubbleTextView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res-auto" + style="@style/Icon.AllApps" + android:id="@+id/icon" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="center" + android:layout_weight="1" + android:focusable="true" + android:background="@drawable/focusable_view_bg" + launcher:deferShadowGeneration="true" + launcher:iconDisplay="all_apps" /> + diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml index b48b61331..4d003313e 100644 --- a/res/layout/folder_application.xml +++ b/res/layout/folder_application.xml @@ -15,5 +15,5 @@ --> <com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/WorkspaceIcon.Folder" + style="@style/Icon.Folder" android:focusable="true" /> diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml index fd45d7685..d9a7671af 100644 --- a/res/layout/folder_icon.xml +++ b/res/layout/folder_icon.xml @@ -28,7 +28,7 @@ android:antialias="true" android:src="@drawable/portal_ring_inner_holo"/> <com.android.launcher3.BubbleTextView - style="@style/WorkspaceIcon" + style="@style/Icon" android:id="@+id/folder_icon_name" android:layout_gravity="top" android:layout_width="match_parent" diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 90d709887..1a9254575 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -60,6 +60,7 @@ <dimen name="apps_view_fast_scroll_text_size">48dp</dimen> <dimen name="apps_search_bar_height">52dp</dimen> <dimen name="apps_icon_top_bottom_padding">8dp</dimen> + <dimen name="apps_prediction_icon_top_bottom_padding">12dp</dimen> <!-- Note: This needs to match the fixed insets for the search box. --> <dimen name="container_fixed_bounds_inset">8dp</dimen> diff --git a/res/values/styles.xml b/res/values/styles.xml index 16d4cbeed..78cc0837f 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -19,7 +19,7 @@ <resources> - <style name="WorkspaceIcon"> + <style name="Icon"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">match_parent</item> <item name="android:layout_gravity">center</item> @@ -32,11 +32,7 @@ <item name="android:fontFamily">sans-serif-condensed</item> </style> - <style name="WorkspaceIcon.Portrait"></style> - - <style name="WorkspaceIcon.Landscape"></style> - - <style name="WorkspaceIcon.AppsCustomize"> + <style name="Icon.AllApps"> <item name="android:background">@null</item> <item name="android:textColor">@color/quantum_panel_text_color</item> <item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item> @@ -44,7 +40,7 @@ <item name="customShadows">false</item> </style> - <style name="WorkspaceIcon.Folder"> + <style name="Icon.Folder"> <item name="android:background">@null</item> <item name="android:textColor">@color/quantum_panel_text_color</item> <item name="android:shadowRadius">0</item> diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index dc75637e5..eff7b0625 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -151,11 +151,13 @@ public class AlphabeticalAppsList { * Info about a particular adapter item (can be either section or app) */ public static class AdapterItem { - /** Section & App properties */ + /** Common properties */ // The index of this adapter item in the list public int position; - // Whether or not the item at this adapter position is a section or not - public boolean isSectionHeader; + // The type of this item + public int viewType; + + /** Section & App properties */ // The section for this item public SectionInfo sectionInfo; @@ -169,30 +171,33 @@ public class AlphabeticalAppsList { public AppInfo appInfo = null; // The index of this app not including sections public int appIndex = -1; - // Whether or not this is a predicted app - public boolean isPredictedApp; public static AdapterItem asSectionBreak(int pos, SectionInfo section) { AdapterItem item = new AdapterItem(); + item.viewType = AppsGridAdapter.SECTION_BREAK_VIEW_TYPE; item.position = pos; - item.isSectionHeader = true; item.sectionInfo = section; section.sectionBreakItem = item; return item; } + public static AdapterItem asPredictionBarSpacer(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE; + item.position = pos; + return item; + } + public static AdapterItem asApp(int pos, SectionInfo section, String sectionName, - int sectionAppIndex, AppInfo appInfo, int appIndex, - boolean isPredictedApp) { + int sectionAppIndex, AppInfo appInfo, int appIndex) { AdapterItem item = new AdapterItem(); + item.viewType = AppsGridAdapter.ICON_VIEW_TYPE; item.position = pos; - item.isSectionHeader = false; item.sectionInfo = section; item.sectionName = sectionName; item.sectionAppIndex = sectionAppIndex; item.appInfo = appInfo; item.appIndex = appIndex; - item.isPredictedApp = isPredictedApp; return item; } } @@ -205,6 +210,13 @@ public class AlphabeticalAppsList { } /** + * A callback to notify of changes to the filter. + */ + public interface FilterChangedCallback { + void onFilterChanged(); + } + + /** * Common interface for different merging strategies. */ private interface MergeAlgorithm { @@ -260,28 +272,31 @@ public class AlphabeticalAppsList { private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>(); private List<SectionInfo> mSections = new ArrayList<>(); private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>(); - private List<ComponentName> mPredictedApps = new ArrayList<>(); + private List<ComponentName> mPredictedAppComponents = new ArrayList<>(); + private List<AppInfo> mPredictedApps = new ArrayList<>(); private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); private RecyclerView.Adapter mAdapter; private Filter mFilter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; private MergeAlgorithm mMergeAlgorithm; + private FilterChangedCallback mFilterChangedCallback; private int mNumAppsPerRow; + private int mNumPredictedAppsPerRow; - public AlphabeticalAppsList(Context context, int numAppsPerRow) { + public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow, + int numPredictedAppsPerRow) { mContext = context; mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppNameComparator(context); - setNumAppsPerRow(numAppsPerRow); + mFilterChangedCallback = cb; + setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow); } /** * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED. */ - public void setNumAppsPerRow(int numAppsPerRow) { - mNumAppsPerRow = numAppsPerRow; - + public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) { // Update the merge algorithm DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); if (grid.isPhone()) { @@ -291,6 +306,9 @@ public class AlphabeticalAppsList { mMergeAlgorithm = new TabletMergeAlgorithm(); } + mNumAppsPerRow = numAppsPerRow; + mNumPredictedAppsPerRow = numPredictedAppsPerRow; + onAppsUpdated(); } @@ -351,6 +369,9 @@ public class AlphabeticalAppsList { mFilter = f; onAppsUpdated(); mAdapter.notifyDataSetChanged(); + if (mFilterChangedCallback != null){ + mFilterChangedCallback.onFilterChanged(); + } } } @@ -359,13 +380,20 @@ public class AlphabeticalAppsList { * of applications, we should merge the results only in onAppsUpdated() which is idempotent. */ public void setPredictedApps(List<ComponentName> apps) { - mPredictedApps.clear(); - mPredictedApps.addAll(apps); + mPredictedAppComponents.clear(); + mPredictedAppComponents.addAll(apps); onAppsUpdated(); mAdapter.notifyDataSetChanged(); } /** + * Returns the current set of predicted apps. + */ + public List<AppInfo> getPredictedApps() { + return mPredictedApps; + } + + /** * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { @@ -450,6 +478,42 @@ public class AlphabeticalAppsList { // Sort the list of apps Collections.sort(mApps, mAppNameComparator.getAppInfoComparator()); + // Prepare to update the list of sections, filtered apps, etc. + mFilteredApps.clear(); + mSections.clear(); + mSectionedFilteredApps.clear(); + mFastScrollerSections.clear(); + SectionInfo lastSectionInfo = null; + String lastSectionName = null; + FastScrollSectionInfo lastFastScrollerSectionInfo = null; + int position = 0; + int appIndex = 0; + List<AppInfo> allApps = new ArrayList<>(); + + + // Process the predicted app components + mPredictedApps.clear(); + if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { + for (ComponentName cn : mPredictedAppComponents) { + for (AppInfo info : mApps) { + if (cn.equals(info.componentName)) { + mPredictedApps.add(info); + break; + } + } + // Stop at the number of predicted apps + if (mPredictedApps.size() == mNumPredictedAppsPerRow) { + break; + } + } + + if (!mPredictedApps.isEmpty()) { + // Create a new spacer for the prediction bar + AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++); + mSectionedFilteredApps.add(sectionItem); + } + } + // As a special case for some languages (currently only Simplified Chinese), we may need to // coalesce sections Locale curLocale = mContext.getResources().getConfiguration().locale; @@ -475,6 +539,11 @@ public class AlphabeticalAppsList { } sectionApps.add(info); } + + // Add it to the list + for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { + allApps.addAll(entry.getValue()); + } } else { // Just compute the section headers for use below for (AppInfo info : mApps) { @@ -485,44 +554,7 @@ public class AlphabeticalAppsList { mCachedSectionNames.put(info.title, sectionName); } } - } - - // Prepare to update the list of sections, filtered apps, etc. - mFilteredApps.clear(); - mSections.clear(); - mSectionedFilteredApps.clear(); - mFastScrollerSections.clear(); - SectionInfo lastSectionInfo = null; - String lastSectionName = null; - FastScrollSectionInfo lastFastScrollerSectionInfo = null; - int position = 0; - int appIndex = 0; - List<AppInfo> allApps = new ArrayList<>(); - - // Add the predicted apps to the combined list - int numPredictedApps = 0; - if (mPredictedApps != null && !mPredictedApps.isEmpty() && !hasFilter()) { - for (ComponentName cn : mPredictedApps) { - for (AppInfo info : mApps) { - if (cn.equals(info.componentName)) { - allApps.add(info); - numPredictedApps++; - break; - } - } - // Stop at the number of predicted apps - if (numPredictedApps == mNumAppsPerRow) { - break; - } - } - } - - // Add all the other apps to the combined list - if (localeRequiresSectionSorting) { - for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { - allApps.addAll(entry.getValue()); - } - } else { + // Add it to the list allApps.addAll(mApps); } @@ -530,10 +562,9 @@ public class AlphabeticalAppsList { // ordered set of sections int numApps = allApps.size(); for (int i = 0; i < numApps; i++) { - boolean isPredictedApp = i < numPredictedApps; AppInfo info = allApps.get(i); // The section name was computed above so this should be find - String sectionName = isPredictedApp ? "" : mCachedSectionNames.get(info.title); + String sectionName = mCachedSectionNames.get(info.title); // Check if we want to retain this app if (mFilter != null && !mFilter.retainApp(info, sectionName)) { @@ -541,8 +572,7 @@ public class AlphabeticalAppsList { } // Create a new section if the section names do not match - if (lastSectionInfo == null || - (!isPredictedApp && !sectionName.equals(lastSectionName))) { + if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) { lastSectionName = sectionName; lastSectionInfo = new SectionInfo(); lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName, @@ -559,7 +589,7 @@ public class AlphabeticalAppsList { // Create an app item AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName, - lastSectionInfo.numApps++, info, appIndex++, isPredictedApp); + lastSectionInfo.numApps++, info, appIndex++); if (lastSectionInfo.firstAppItem == null) { lastSectionInfo.firstAppItem = appItem; lastFastScrollerSectionInfo.appItem = appItem; @@ -568,6 +598,14 @@ public class AlphabeticalAppsList { mFilteredApps.add(info); } + // Merge multiple sections together as requested by the merge strategy for this device + mergeSections(); + } + + /** + * Merges multiple sections to reduce visual raggedness. + */ + private void mergeSections() { // Go through each section and try and merge some of the sections if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { int sectionAppCount = 0; diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index e918bc2ee..3952923af 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -45,8 +45,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { * scroller. */ private static class ScrollPositionState { - // The index of the first app in the row (Note that is this not the position) - int rowFirstAppIndex; // The index of the first visible row int rowIndex; // The offset of the first visible row @@ -59,6 +57,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { private AlphabeticalAppsList mApps; private int mNumAppsPerRow; + private int mNumPredictedAppsPerRow; private Drawable mScrollbar; private Drawable mFastScrollerBg; @@ -68,6 +67,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { private Paint mFastScrollTextPaint; private Rect mFastScrollTextBounds = new Rect(); private float mFastScrollAlpha; + private int mPredictionBarHeight; private int mDownX; private int mDownY; private int mLastX; @@ -123,8 +123,9 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { /** * Sets the number of apps per row in this recycler view. */ - public void setNumAppsPerRow(int rowSize) { - mNumAppsPerRow = rowSize; + public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) { + mNumAppsPerRow = numAppsPerRow; + mNumPredictedAppsPerRow = numPredictedAppsPerRow; } @Override @@ -134,6 +135,13 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { } /** + * Sets the prediction bar height. + */ + public void setPredictionBarHeight(int height) { + mPredictionBarHeight = height; + } + + /** * Sets the fast scroller alpha. */ public void setFastScrollerAlpha(float alpha) { @@ -330,6 +338,26 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { return ""; } + // Stop the scroller if it is scrolling + LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); + stopScroll(); + + // If there is a prediction bar, then capture the appropriate area for the prediction bar + float predictionBarFraction = 0f; + if (mPredictionBarHeight > 0) { + predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize(); + if (touchFraction <= predictionBarFraction) { + // Scroll to the top of the view, where the prediction bar is + layoutManager.scrollToPositionWithOffset(0, 0); + updateScrollY(0); + return ""; + } + } + + // Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from + // predictionBarFraction..1 + touchFraction = (touchFraction - predictionBarFraction) * + (1f / (1f - predictionBarFraction)); AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0); for (int i = 1; i < fastScrollSections.size(); i++) { AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i); @@ -340,21 +368,19 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { lastScrollSection = scrollSection; } - // Scroll the position into view, anchored at the top of the screen if possible. We call the - // scroll method on the LayoutManager directly since it is not exposed by RecyclerView. - LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); - stopScroll(); - layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0); - - // We need to workaround the RecyclerView to get the right scroll position after scrolling + // We need to workaround the RecyclerView to get the right scroll position List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); getCurScrollState(mScrollPosState, items); if (mScrollPosState.rowIndex != -1) { - int rowIndex = findRowForAppIndex(mScrollPosState.rowFirstAppIndex); - int y = (rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset; - updateScrollY(y); + int scrollY = getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + + mPredictionBarHeight - mScrollPosState.rowTopOffset; + updateScrollY(scrollY); } + // Scroll to the view at the position, anchored at the top of the screen. We call the scroll + // method on the LayoutManager directly since it is not exposed by RecyclerView. + layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0); + return lastScrollSection.sectionName; } @@ -377,10 +403,9 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { LAYOUT_DIRECTION_RTL); int rowCount = getNumRows(); getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { int height = getHeight() - getPaddingTop() - getPaddingBottom(); - int totalScrollHeight = rowCount * mScrollPosState.rowHeight; + int totalScrollHeight = rowCount * mScrollPosState.rowHeight + mPredictionBarHeight; if (totalScrollHeight > height) { int scrollbarHeight = Math.max(mScrollbarMinHeight, (int) (height / ((float) totalScrollHeight / height))); @@ -396,8 +421,8 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { // that the user has already scrolled and then map that to the scroll bar bounds int availableY = totalScrollHeight - height; int availableScrollY = height - scrollbarHeight; - y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - - mScrollPosState.rowTopOffset; + y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + mPredictionBarHeight + - mScrollPosState.rowTopOffset; y = getPaddingTop() + (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY); @@ -444,7 +469,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { */ private void getCurScrollState(ScrollPositionState stateOut, List<AlphabeticalAppsList.AdapterItem> items) { - stateOut.rowFirstAppIndex = -1; stateOut.rowIndex = -1; stateOut.rowTopOffset = -1; stateOut.rowHeight = -1; @@ -454,8 +478,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView { int position = getChildPosition(child); if (position != NO_POSITION) { AlphabeticalAppsList.AdapterItem item = items.get(position); - if (!item.isSectionHeader) { - stateOut.rowFirstAppIndex = item.appIndex; + if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) { stateOut.rowIndex = findRowForAppIndex(item.appIndex); stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child); stateOut.rowHeight = child.getHeight(); diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index 5dac9f1e8..21dd7cbc4 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -26,12 +26,14 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import com.android.launcher3.util.Thunk; @@ -44,11 +46,11 @@ import java.util.regex.Pattern; * The all apps view container. */ public class AppsContainerView extends BaseContainerView implements DragSource, Insettable, - TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener, - View.OnClickListener, View.OnLongClickListener { + TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, + AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks, + View.OnTouchListener, View.OnClickListener, View.OnLongClickListener { public static final boolean GRID_MERGE_SECTIONS = true; - public static final boolean GRID_HIDE_SECTION_HEADERS = false; private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; private static final boolean DYNAMIC_HEADER_ELEVATION = true; @@ -64,12 +66,14 @@ public class AppsContainerView extends BaseContainerView implements DragSource, @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; + private LayoutInflater mLayoutInflater; private AppsGridAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.ItemDecoration mItemDecoration; - private LinearLayout mContentView; + private FrameLayout mContentView; @Thunk AppsContainerRecyclerView mAppsRecyclerView; + private ViewGroup mPredictionBarView; private View mHeaderView; private View mSearchBarContainerView; private View mSearchButtonView; @@ -77,11 +81,13 @@ public class AppsContainerView extends BaseContainerView implements DragSource, private AppsContainerSearchEditTextView mSearchBarEditView; private int mNumAppsPerRow; + private int mNumPredictedAppsPerRow; private Point mLastTouchDownPos = new Point(-1, -1); private Point mLastTouchPos = new Point(); private int mContentMarginStart; // Normal container insets private int mContainerInset; + private int mPredictionBarHeight; // RecyclerView scroll position @Thunk int mRecyclerViewScrollY; @@ -101,12 +107,17 @@ public class AppsContainerView extends BaseContainerView implements DragSource, mContainerInset = context.getResources().getDimensionPixelSize( R.dimen.apps_container_inset); + mPredictionBarHeight = grid.allAppsCellHeightPx + + 2 * res.getDimensionPixelSize(R.dimen.apps_prediction_icon_top_bottom_padding); mLauncher = (Launcher) context; + mLayoutInflater = LayoutInflater.from(context); mNumAppsPerRow = grid.appsViewNumCols; - mApps = new AlphabeticalAppsList(context, mNumAppsPerRow); - mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this); + mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols; + mApps = new AlphabeticalAppsList(context, this, mNumAppsPerRow, mNumPredictedAppsPerRow); + mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this); mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); mAdapter.setNumAppsPerRow(mNumAppsPerRow); + mAdapter.setPredictionRowHeight(mPredictionBarHeight); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); mContentMarginStart = mAdapter.getContentMarginStart(); @@ -186,7 +197,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource, // Work around the search box getting first focus and showing the cursor by // proxying the focus from the content view to the recycler view directly - mContentView = (LinearLayout) findViewById(R.id.apps_list); + mContentView = (FrameLayout) findViewById(R.id.apps_list); mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { @@ -195,12 +206,20 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } } }); + + // Fix the header view elevation if not dynamically calculating it mHeaderView = findViewById(R.id.header); mHeaderView.setOnClickListener(this); if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) { mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP, getContext().getResources().getDisplayMetrics())); } + + // Fix the prediction bar size + mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); + lp.height = mPredictionBarHeight; + mSearchButtonView = mHeaderView.findViewById(R.id.search_button); mSearchBarContainerView = findViewById(R.id.app_search_container); mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button); @@ -227,7 +246,8 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); - mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); + mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); + mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight); mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); @@ -247,15 +267,52 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } @Override + public void onBindPredictionBar() { + if (!updatePredictionBarVisibility()) { + return; + } + + List<AppInfo> predictedApps = mApps.getPredictedApps(); + int childCount = mPredictionBarView.getChildCount(); + for (int i = 0; i < mNumPredictedAppsPerRow; i++) { + BubbleTextView icon; + if (i < childCount) { + // If a child at that index exists, then get that child + icon = (BubbleTextView) mPredictionBarView.getChildAt(i); + } else { + // Otherwise, inflate a new icon + icon = (BubbleTextView) mLayoutInflater.inflate( + R.layout.apps_prediction_bar_icon_view, mPredictionBarView, false); + icon.setOnTouchListener(this); + icon.setOnClickListener(mLauncher); + icon.setOnLongClickListener(this); + icon.setFocusable(true); + mPredictionBarView.addView(icon); + } + + // Either apply the app info to the child, or hide the view + if (i < predictedApps.size()) { + if (icon.getVisibility() != View.VISIBLE) { + icon.setVisibility(View.VISIBLE); + } + icon.applyFromApplicationInfo(predictedApps.get(i)); + } else { + icon.setVisibility(View.INVISIBLE); + } + } + } + + @Override protected void onFixedBoundsUpdated() { // Update the number of items in the grid LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) { mNumAppsPerRow = grid.appsViewNumCols; - mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); + mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols; + mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); - mApps.setNumAppsPerRow(mNumAppsPerRow); + mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); } } @@ -297,10 +354,16 @@ public class AppsContainerView extends BaseContainerView implements DragSource, // Update the header bar if (hasSearchBar) { - LinearLayout.LayoutParams lp = - (LinearLayout.LayoutParams) mHeaderView.getLayoutParams(); + FrameLayout.LayoutParams lp = + (FrameLayout.LayoutParams) mHeaderView.getLayoutParams(); lp.leftMargin = lp.rightMargin = inset; + mHeaderView.requestLayout(); } + + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); + lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth(); + lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth(); + mPredictionBarView.requestLayout(); } /** @@ -499,7 +562,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource, List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); for (int i = 0; i < items.size(); i++) { AlphabeticalAppsList.AdapterItem item = items.get(i); - if (!item.isSectionHeader) { + if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) { mAppsRecyclerView.getChildAt(i).performClick(); getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); return true; @@ -510,6 +573,11 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } @Override + public void onFilterChanged() { + updatePredictionBarVisibility(); + } + + @Override public View getContent() { return null; } @@ -554,6 +622,12 @@ public class AppsContainerView extends BaseContainerView implements DragSource, mHeaderView.setElevation(newElevation); } } + + // XXX: Optimize this, stop once we are out of bounds + if (mRecyclerViewScrollY < 0) { + new Throwable().printStackTrace(); + } + mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop()); } /** @@ -683,6 +757,21 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } /** + * Updates the visibility of the prediction bar. + * @return whether the prediction bar is visible + */ + private boolean updatePredictionBarVisibility() { + boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() || + mSearchBarEditView.getEditableText().toString().isEmpty()); + if (showPredictionBar) { + mPredictionBarView.setVisibility(View.VISIBLE); + } else if (!showPredictionBar) { + mPredictionBarView.setVisibility(View.INVISIBLE); + } + return showPredictionBar; + } + + /** * Returns an input method manager. */ private InputMethodManager getInputMethodManager() { diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 4014e3804..dfbfa01da 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -6,6 +6,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -26,21 +27,31 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public static final String TAG = "AppsGridAdapter"; private static final boolean DEBUG = false; - private static final int SECTION_BREAK_VIEW_TYPE = 0; - private static final int ICON_VIEW_TYPE = 1; - private static final int EMPTY_VIEW_TYPE = 2; + // A section break in the grid + public static final int SECTION_BREAK_VIEW_TYPE = 0; + // A normal icon + public static final int ICON_VIEW_TYPE = 1; + // The message shown when there are no filtered results + public static final int EMPTY_VIEW_TYPE = 2; + // The spacer used for the prediction bar + public static final int PREDICTION_BAR_SPACER_TYPE = 3; + + /** + * Callback for when the prediction bar spacer is bound. + */ + public interface PredictionBarSpacerCallbacks { + void onBindPredictionBar(); + } /** * ViewHolder for each icon. */ public static class ViewHolder extends RecyclerView.ViewHolder { public View mContent; - public boolean mIsEmptyRow; - public ViewHolder(View v, boolean isEmptyRow) { + public ViewHolder(View v) { super(v); mContent = v; - mIsEmptyRow = isEmptyRow; } } @@ -61,8 +72,8 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { return mAppsPerRow; } - if (mApps.getAdapterItems().get(position).isSectionHeader) { - // Section break spans full width + if (mApps.getAdapterItems().get(position).viewType != AppsGridAdapter.ICON_VIEW_TYPE) { + // Both the section breaks and predictive bar span the full width return mAppsPerRow; } else { return 1; @@ -88,7 +99,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - boolean hasDrawnPredictedAppDivider = false; + boolean hasDrawnPredictedAppsDivider = false; int childCount = parent.getChildCount(); int lastSectionTop = 0; int lastSectionHeight = 0; @@ -99,13 +110,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { continue; } - if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) { - // Draw the divider under the predicted app + if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppsDivider) { + // Draw the divider under the predicted apps parent.getBackground().getPadding(mTmpBounds); int top = child.getTop() + child.getHeight(); c.drawLine(mTmpBounds.left, top, parent.getWidth() - mTmpBounds.right, top, mPredictedAppsDividerPaint); - hasDrawnPredictedAppDivider = true; + hasDrawnPredictedAppsDivider = true; } else if (grid.isPhone() && shouldDrawItemSection(holder, i, items)) { // At this point, we only draw sections for each section break; @@ -220,9 +231,10 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { /** * Returns whether to draw the divider for a given child. */ - private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) { + private boolean shouldDrawItemDivider(ViewHolder holder, + List<AlphabeticalAppsList.AdapterItem> items) { int pos = holder.getPosition(); - return items.get(pos).isPredictedApp; + return items.get(pos).viewType == AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE; } /** @@ -233,31 +245,27 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { int pos = holder.getPosition(); AlphabeticalAppsList.AdapterItem item = items.get(pos); - // Ensure it's not an empty row - if (holder.mIsEmptyRow) { - return false; - } - // Ensure this is not a section break - if (item.isSectionHeader) { - return false; - } - // Ensure this is not a predicted app - if (item.isPredictedApp) { + // Ensure it's an icon + if (item.viewType != AppsGridAdapter.ICON_VIEW_TYPE) { return false; } // Draw the section header for the first item in each section - return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader); + return (childIndex == 0) || + (items.get(pos - 1).viewType == AppsGridAdapter.SECTION_BREAK_VIEW_TYPE); } } + private Handler mHandler; private LayoutInflater mLayoutInflater; @Thunk AlphabeticalAppsList mApps; private GridLayoutManager mGridLayoutMgr; private GridSpanSizer mGridSizer; private GridItemDecoration mItemDecoration; + private PredictionBarSpacerCallbacks mPredictionBarCb; private View.OnTouchListener mTouchListener; private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; + @Thunk int mPredictionBarHeight; @Thunk int mAppsPerRow; @Thunk boolean mIsRtl; private String mEmptySearchText; @@ -271,11 +279,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow, - View.OnTouchListener touchListener, View.OnClickListener iconClickListener, - View.OnLongClickListener iconLongClickListener) { + PredictionBarSpacerCallbacks pbCb, View.OnTouchListener touchListener, + View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) { Resources res = context.getResources(); + mHandler = new Handler(); mApps = apps; mAppsPerRow = appsPerRow; + mPredictionBarCb = pbCb; mGridSizer = new GridSpanSizer(); mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL, false); @@ -310,6 +320,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { } /** + * Sets the prediction row height. + */ + public void setPredictionRowHeight(int height) { + mPredictionBarHeight = height; + } + + /** * Sets whether we are in RTL mode. */ public void setRtl(boolean rtl) { @@ -350,17 +367,24 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { switch (viewType) { case EMPTY_VIEW_TYPE: return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent, - false), true /* isEmptyRow */); + false)); case SECTION_BREAK_VIEW_TYPE: - return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */); + return new ViewHolder(new View(parent.getContext())); + case PREDICTION_BAR_SPACER_TYPE: + // Create a view of a specific height to match the floating prediction bar + View v = new View(parent.getContext()); + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, mPredictionBarHeight); + v.setLayoutParams(lp); + return new ViewHolder(v); case ICON_VIEW_TYPE: BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( - R.layout.apps_grid_row_icon_view, parent, false); + R.layout.apps_grid_icon_view, parent, false); icon.setOnTouchListener(mTouchListener); icon.setOnClickListener(mIconClickListener); icon.setOnLongClickListener(mIconLongClickListener); icon.setFocusable(true); - return new ViewHolder(icon, false /* isEmptyRow */); + return new ViewHolder(icon); default: throw new RuntimeException("Unexpected view type"); } @@ -374,6 +398,16 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { BubbleTextView icon = (BubbleTextView) holder.mContent; icon.applyFromApplicationInfo(info); break; + case PREDICTION_BAR_SPACER_TYPE: + mHandler.post(new Runnable() { + @Override + public void run() { + if (mPredictionBarCb != null) { + mPredictionBarCb.onBindPredictionBar(); + } + } + }); + break; case EMPTY_VIEW_TYPE: TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); emptyViewText.setText(mEmptySearchText); @@ -394,9 +428,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public int getItemViewType(int position) { if (mApps.hasNoFilteredResults()) { return EMPTY_VIEW_TYPE; - } else if (mApps.getAdapterItems().get(position).isSectionHeader) { - return SECTION_BREAK_VIEW_TYPE; } - return ICON_VIEW_TYPE; + + AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); + return item.viewType; } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 3bbf0e7d8..dc6de0736 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -127,6 +127,7 @@ public class DeviceProfile { int allAppsNumRows; int allAppsNumCols; int appsViewNumCols; + int appsViewNumPredictiveCols; int searchBarSpaceWidthPx; int searchBarSpaceHeightPx; int pageIndicatorHeightPx; @@ -411,7 +412,7 @@ public class DeviceProfile { // All Apps allAppsCellWidthPx = allAppsIconSizePx; - allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx; + allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + allAppsIconTextSizePx; int maxLongEdgeCellCount = res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count); int maxShortEdgeCellCount = @@ -440,10 +441,13 @@ public class DeviceProfile { int appsViewLeftMarginPx = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx; - int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) / + int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) / (allAppsCellWidthPx + 2 * allAppsCellPaddingPx); - if (numCols != appsViewNumCols) { - appsViewNumCols = numCols; + int numPredictiveAppCols = isPhone() ? numColumns : numAppsCols; + if ((numAppsCols != appsViewNumCols) || + (numPredictiveAppCols != appsViewNumPredictiveCols)) { + appsViewNumCols = numAppsCols; + appsViewNumPredictiveCols = numPredictiveAppCols; return true; } return false; |