From 13eb527b5ae7f564e3ace6137a8d466636d87188 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Mon, 11 May 2015 16:30:13 -0700 Subject: Exploring dense all apps layout. - Disabling section headers in all apps on tablet layouts - Fixing issue with predictions not showing on rotation - Fixing issue with over-aggressive dismissing of keyboard & filtered app state - Fixing issue where the container bounds were running straight up to the nav bar Change-Id: I5a5a56afa75b50be96af4894bf785ffbb1b15fb3 --- .../android/launcher3/AlphabeticalAppsList.java | 101 +++++++++++++++++---- src/com/android/launcher3/AppsContainerView.java | 16 +++- src/com/android/launcher3/AppsGridAdapter.java | 8 +- src/com/android/launcher3/BaseContainerView.java | 5 +- src/com/android/launcher3/Launcher.java | 9 +- .../launcher3/widget/WidgetsContainerView.java | 2 +- 6 files changed, 110 insertions(+), 31 deletions(-) (limited to 'src/com/android/launcher3') diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index 62cb237e1..de4edcb7b 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -3,6 +3,7 @@ package com.android.launcher3; import android.content.ComponentName; import android.content.Context; import android.support.v7.widget.RecyclerView; +import android.util.Log; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -77,6 +78,9 @@ class AppNameComparator { */ public class AlphabeticalAppsList { + public static final String TAG = "AlphabeticalAppsList"; + private static final boolean DEBUG = false; + /** * Info about a section in the alphabetic list */ @@ -162,11 +166,58 @@ public class AlphabeticalAppsList { * A filter interface to limit the set of applications in the apps list. */ public interface Filter { - public boolean retainApp(AppInfo info, String sectionName); + boolean retainApp(AppInfo info, String sectionName); + } + + /** + * Common interface for different merging strategies. + */ + private interface MergeAlgorithm { + boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount); } - // The maximum number of rows allowed in a merged section before we stop merging - private static final int MAX_ROWS_IN_MERGED_SECTION = 3; + /** + * The logic we use to merge sections on tablets. + */ + private static class TabletMergeAlgorithm implements MergeAlgorithm { + + @Override + public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) { + // Merge EVERYTHING + return true; + } + } + + /** + * The logic we use to merge sections on phones. + */ + private static class PhoneMergeAlgorithm implements MergeAlgorithm { + + private int mMinAppsPerRow; + private int mMinRowsInMergedSection; + private int mMaxAllowableMerges; + + public PhoneMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) { + mMinAppsPerRow = minAppsPerRow; + mMinRowsInMergedSection = minRowsInMergedSection; + mMaxAllowableMerges = maxNumMerges; + } + + @Override + public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) { + // Continue merging if the number of hanging apps on the final row is less than some + // fixed number (ragged), the merged rows has yet to exceed some minimum row count, + // and while the number of merged sections is less than some fixed number of merges + int rows = sectionAppCount / numAppsPerRow; + int cols = sectionAppCount % numAppsPerRow; + return (0 < cols && cols < mMinAppsPerRow) && + rows < mMinRowsInMergedSection && + mergeCount < mMaxAllowableMerges; + } + } + + private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3; + private static final int MAX_NUM_MERGES_PHONE = 2; private List mApps = new ArrayList<>(); private List mFilteredApps = new ArrayList<>(); @@ -174,13 +225,13 @@ public class AlphabeticalAppsList { private List mSections = new ArrayList<>(); private List mFastScrollerSections = new ArrayList<>(); private List mPredictedApps = new ArrayList<>(); + private HashMap mCachedSectionNames = new HashMap<>(); private RecyclerView.Adapter mAdapter; private Filter mFilter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; + private MergeAlgorithm mMergeAlgorithm; private int mNumAppsPerRow; - // The maximum number of section merges we allow at a given time before we stop merging - private int mMaxAllowableMerges = Integer.MAX_VALUE; public AlphabeticalAppsList(Context context, int numAppsPerRow) { mIndexer = new AlphabeticIndexCompat(context); @@ -193,7 +244,16 @@ public class AlphabeticalAppsList { */ public void setNumAppsPerRow(int numAppsPerRow) { mNumAppsPerRow = numAppsPerRow; - mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f); + + // Update the merge algorithm + DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + if (grid.isPhone()) { + mMergeAlgorithm = new PhoneMergeAlgorithm((int) Math.ceil(numAppsPerRow / 2f), + MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE); + } else { + mMergeAlgorithm = new TabletMergeAlgorithm(); + } + onAppsUpdated(); } @@ -392,7 +452,15 @@ public class AlphabeticalAppsList { for (int i = 0; i < numApps; i++) { boolean isPredictedApp = i < numPredictedApps; AppInfo info = allApps.get(i); - String sectionName = isPredictedApp ? "" : mIndexer.computeSectionName(info.title); + String sectionName = ""; + if (!isPredictedApp) { + // Only cache section names from non-predicted apps + sectionName = mCachedSectionNames.get(info.title); + if (sectionName == null) { + sectionName = mIndexer.computeSectionName(info.title); + mCachedSectionNames.put(info.title, sectionName); + } + } // Check if we want to retain this app if (mFilter != null && !mFilter.retainApp(info, sectionName)) { @@ -429,20 +497,14 @@ public class AlphabeticalAppsList { // Go through each section and try and merge some of the sections if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { - int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f); int sectionAppCount = 0; for (int i = 0; i < mSections.size(); i++) { SectionInfo section = mSections.get(i); sectionAppCount = section.numApps; int mergeCount = 1; - // Merge rows if the last app in this section is in a column that is greater than - // 0, but less than the min number of apps per row. In addition, apply the - // constraint to stop merging if the number of rows in the section is greater than - // some limit, and also if there are no lessons to merge. - while (0 < (sectionAppCount % mNumAppsPerRow) && - (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow && - (sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION && + // Merge rows based on the current strategy + while (mMergeAlgorithm.continueMerging(sectionAppCount, mNumAppsPerRow, mergeCount) && (i + 1) < mSections.size()) { SectionInfo nextSection = mSections.remove(i + 1); @@ -465,10 +527,13 @@ public class AlphabeticalAppsList { } section.numApps += nextSection.numApps; sectionAppCount += nextSection.numApps; - mergeCount++; - if (mergeCount >= mMaxAllowableMerges) { - break; + + if (DEBUG) { + Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName + + " to " + section.firstAppItem.sectionName + + " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow)); } + mergeCount++; } } } diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index b8d30d081..8a5c6605e 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -213,7 +213,13 @@ public class AppsContainerView extends BaseContainerView implements DragSource, new AppsContainerSearchEditTextView.OnBackKeyListener() { @Override public void onBackKey() { - hideSearchField(true, true); + // Only hide the search field if there is no query, or if there + // are no filtered results + String query = Utilities.trim( + mSearchBarEditView.getEditableText().toString()); + if (query.isEmpty() || mApps.hasNoFilteredResults()) { + hideSearchField(true, true); + } } }); } @@ -277,15 +283,17 @@ public class AppsContainerView extends BaseContainerView implements DragSource, } else { // If there are fixed bounds, then we update the padding to reflect the fixed bounds. setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, - mInsets.bottom); + mFixedBounds.bottom); } // Update the apps recycler view, inset it by the container inset as well + DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + int startMargin = grid.isPhone() ? mContentMarginStart : 0; int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; if (isRtl) { - mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset); + mAppsRecyclerView.setPadding(inset, inset, inset + startMargin, inset); } else { - mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset); + mAppsRecyclerView.setPadding(inset + startMargin, inset, inset, inset); } // Update the header bar diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 9ecb2eeb6..563044916 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -90,6 +90,7 @@ class AppsGridAdapter extends RecyclerView.Adapter { return; } + DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); List items = mApps.getAdapterItems(); boolean hasDrawnPredictedAppDivider = false; int childCount = parent.getChildCount(); @@ -104,8 +105,6 @@ class AppsGridAdapter extends RecyclerView.Adapter { if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) { // Draw the divider under the predicted app - DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid(). - getDeviceProfile(); int top = child.getTop() + child.getHeight(); int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); @@ -113,7 +112,7 @@ class AppsGridAdapter extends RecyclerView.Adapter { c.drawLine(left + iconInset, top, right - iconInset, top, mPredictedAppsDividerPaint); hasDrawnPredictedAppDivider = true; - } else if (shouldDrawItemSection(holder, i, items)) { + } else if (grid.isPhone() && shouldDrawItemSection(holder, i, items)) { // At this point, we only draw sections for each section break; int viewTopOffset = (2 * child.getPaddingTop()); int pos = holder.getPosition(); @@ -132,7 +131,8 @@ class AppsGridAdapter extends RecyclerView.Adapter { continue; } - // Find the section code points + + // Find the section name bounds PointF sectionBounds = getAndCacheSectionBounds(sectionName); // Calculate where to draw the section diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index 2a8443221..bd1c625e3 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -59,11 +59,12 @@ public class BaseContainerView extends FrameLayout implements Insettable { mFixedBounds.set(fixedBounds); if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { mFixedBounds.top = mInsets.top; - mFixedBounds.bottom = getMeasuredHeight(); + mFixedBounds.bottom = mInsets.bottom; } // To ensure that the child RecyclerView has the full width to handle touches right to // the edge of the screen, we only apply the top and bottom padding to the bounds - mFixedBounds.inset(0, mFixedBoundsContainerInset); + mFixedBounds.top += mFixedBoundsContainerInset; + mFixedBounds.bottom += mFixedBoundsContainerInset; onFixedBoundsUpdated(); } // Post the updates since they can trigger a relayout, and this call can be triggered from diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 16e4ce644..a289fce87 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -350,6 +350,9 @@ public class Launcher extends Activity private DeviceProfile mDeviceProfile; + // This is set to the view that launched the activity that navigated the user away from + // launcher. Since there is no callback for when the activity has finished launching, enable + // the press state and keep this reference to reset the press state when we return to launcher. private BubbleTextView mWaitingForResume; protected static HashMap sCustomAppWidgets = @@ -1021,10 +1024,12 @@ public class Launcher extends Activity if (mOnResumeState == State.WORKSPACE) { showWorkspace(false); } else if (mOnResumeState == State.APPS) { + boolean launchedFromApp = (mWaitingForResume != null); // Don't update the predicted apps if the user is returning to launcher in the apps - // view as they may be depending on the UI to be static to switch to another app + // view after launching an app, as they may be depending on the UI to be static to + // switch to another app, otherwise, if it was showAppsView(false /* animated */, false /* resetListToTop */, - false /* updatePredictedApps */); + !launchedFromApp /* updatePredictedApps */); } else if (mOnResumeState == State.WIDGETS) { showWidgetsView(false, false); } diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 439227f52..f8d7d9256 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -373,7 +373,7 @@ public class WidgetsContainerView extends BaseContainerView } else { // If there are fixed bounds, then we update the padding to reflect the fixed bounds. setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, - mInsets.bottom); + mFixedBounds.bottom); } } -- cgit v1.2.3