OSDN Git Service

Exploring dense all apps layout.
authorWinson Chung <winsonc@google.com>
Mon, 11 May 2015 23:30:13 +0000 (16:30 -0700)
committerWinson Chung <winsonc@google.com>
Tue, 12 May 2015 00:53:47 +0000 (17:53 -0700)
- 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

res/layout/apps_empty_view.xml
res/values-sw600dp/dimens.xml
res/values-sw720dp/dimens.xml
src/com/android/launcher3/AlphabeticalAppsList.java
src/com/android/launcher3/AppsContainerView.java
src/com/android/launcher3/AppsGridAdapter.java
src/com/android/launcher3/BaseContainerView.java
src/com/android/launcher3/Launcher.java
src/com/android/launcher3/widget/WidgetsContainerView.java

index 8408077..e4c4e2e 100644 (file)
@@ -18,7 +18,7 @@
     android:id="@+id/empty_text"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_gravity="center"
+    android:gravity="center"
     android:paddingTop="24dp"
     android:paddingBottom="24dp"
     android:paddingRight="@dimen/apps_grid_view_start_margin"
index b184642..2bdd4f0 100644 (file)
     <dimen name="app_icon_size">64dp</dimen>
 
 <!-- Apps view -->
-    <dimen name="apps_container_inset">24dp</dimen>
-    <dimen name="apps_grid_view_start_margin">64dp</dimen>
+    <dimen name="apps_container_inset">18dp</dimen>
+    <dimen name="apps_grid_view_start_margin">0dp</dimen>
     <dimen name="apps_view_section_text_size">26sp</dimen>
-    <dimen name="apps_view_row_height">76dp</dimen>
+    <dimen name="apps_view_row_height">72dp</dimen>
     <dimen name="apps_icon_top_bottom_padding">12dp</dimen>
 
 <!-- AppsCustomize -->
index cec6b7d..39b0c80 100644 (file)
@@ -16,7 +16,7 @@
 
 <resources>
     <dimen name="app_icon_size">72dp</dimen>
-    <dimen name="apps_search_bar_height">56dp</dimen>
+    <dimen name="apps_search_bar_height">54dp</dimen>
     <dimen name="apps_icon_top_bottom_padding">16dp</dimen>
 
 <!-- QSB -->
index 62cb237..de4edcb 100644 (file)
@@ -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<AppInfo> mApps = new ArrayList<>();
     private List<AppInfo> mFilteredApps = new ArrayList<>();
@@ -174,13 +225,13 @@ public class AlphabeticalAppsList {
     private List<SectionInfo> mSections = new ArrayList<>();
     private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
     private List<ComponentName> 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 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++;
                 }
             }
         }
index b8d30d0..8a5c660 100644 (file)
@@ -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
index 9ecb2ee..5630449 100644 (file)
@@ -90,6 +90,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
                 return;
             }
 
+            DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
             boolean hasDrawnPredictedAppDivider = false;
             int childCount = parent.getChildCount();
@@ -104,8 +105,6 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
 
                 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<AppsGridAdapter.ViewHolder> {
                     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<AppsGridAdapter.ViewHolder> {
                             continue;
                         }
 
-                        // Find the section code points
+
+                        // Find the section name bounds
                         PointF sectionBounds = getAndCacheSectionBounds(sectionName);
 
                         // Calculate where to draw the section
index 2a84432..bd1c625 100644 (file)
@@ -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
index 16e4ce6..a289fce 100644 (file)
@@ -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<String, CustomAppWidget> 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);
         }
index 439227f..f8d7d92 100644 (file)
@@ -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);
         }
     }