OSDN Git Service

Refactored section names to only draw when there is space.
authorWinson Chung <winsonc@google.com>
Fri, 12 Jun 2015 21:18:55 +0000 (14:18 -0700)
committerWinson Chung <winsonc@google.com>
Tue, 16 Jun 2015 00:55:02 +0000 (00:55 +0000)
- This CL removes all space for section names in both phones
  and tablets.  And when there are no section names, the layout
  will automatically fully merge the sections.

Bug: 20222023
Change-Id: Ic7c751d86f095e5cbd690bfd4f94bb5b00ff8ae4

res/values/dimens.xml
src/com/android/launcher3/BaseRecyclerView.java
src/com/android/launcher3/allapps/AllAppsContainerView.java
src/com/android/launcher3/allapps/AllAppsGridAdapter.java
src/com/android/launcher3/allapps/AllAppsRecyclerView.java
src/com/android/launcher3/allapps/AlphabeticalAppsList.java

index 4c9d57a..da56d90 100644 (file)
@@ -55,7 +55,7 @@
     <!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
     <dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
 
-    <dimen name="all_apps_grid_view_start_margin">56dp</dimen>
+    <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
     <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
     <dimen name="all_apps_grid_section_text_size">24sp</dimen>
     <dimen name="all_apps_search_bar_height">48dp</dimen>
@@ -65,7 +65,7 @@
     <dimen name="all_apps_prediction_bar_top_bottom_padding">16dp</dimen>
 
     <dimen name="all_apps_fast_scroll_bar_width">4dp</dimen>
-    <dimen name="all_apps_fast_scroll_scrubber_touch_inset">-16dp</dimen>
+    <dimen name="all_apps_fast_scroll_scrubber_touch_inset">-24dp</dimen>
     <dimen name="all_apps_fast_scroll_popup_size">72dp</dimen>
     <dimen name="all_apps_fast_scroll_text_size">48dp</dimen>
 
index 6dd029f..140c28c 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
@@ -315,13 +317,38 @@ public class BaseRecyclerView extends RecyclerView
     /**
      * Animates the visibility of the fast scroller popup.
      */
-    private void animateFastScrollerVisibility(boolean visible) {
+    private void animateFastScrollerVisibility(final boolean visible) {
         ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
         anim.setDuration(visible ? 200 : 150);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (visible) {
+                    onFastScrollingStart();
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!visible) {
+                    onFastScrollingEnd();
+                }
+            }
+        });
         anim.start();
     }
 
     /**
+     * To be overridden by subclasses.
+     */
+    protected void onFastScrollingStart() {}
+
+    /**
+     * To be overridden by subclasses.
+     */
+    protected void onFastScrollingEnd() {}
+
+    /**
      * Invalidates the fast scroller popup.
      */
     protected void invalidateFastScroller(Rect bounds) {
index b300cae..083e3c1 100644 (file)
@@ -58,12 +58,71 @@ import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.util.Thunk;
 
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
 import java.util.ArrayList;
 import java.util.List;
 
 
 
 /**
+ * A merge algorithm that merges every section indiscriminately.
+ */
+final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
+
+    @Override
+    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
+           AlphabeticalAppsList.SectionInfo withSection,
+           int sectionAppCount, int numAppsPerRow, int mergeCount) {
+        // Merge EVERYTHING
+        return true;
+    }
+}
+
+/**
+ * The logic we use to merge multiple sections.  We only merge sections when their final row
+ * contains less than a certain number of icons, and stop at a specified max number of merges.
+ * In addition, we will try and not merge sections that identify apps from different scripts.
+ */
+final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
+
+    private int mMinAppsPerRow;
+    private int mMinRowsInMergedSection;
+    private int mMaxAllowableMerges;
+    private CharsetEncoder mAsciiEncoder;
+
+    public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
+        mMinAppsPerRow = minAppsPerRow;
+        mMinRowsInMergedSection = minRowsInMergedSection;
+        mMaxAllowableMerges = maxNumMerges;
+        mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
+    }
+
+    @Override
+    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
+           AlphabeticalAppsList.SectionInfo withSection,
+           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;
+
+        // Ensure that we do not merge across scripts, currently we only allow for english and
+        // native scripts so we can test if both can just be ascii encoded
+        boolean isCrossScript = false;
+        if (section.firstAppItem != null && withSection.firstAppItem != null) {
+            isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
+                    mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
+        }
+        return (0 < cols && cols < mMinAppsPerRow) &&
+                rows < mMinRowsInMergedSection &&
+                mergeCount < mMaxAllowableMerges &&
+                !isCrossScript;
+    }
+}
+
+/**
  * The all apps view container.
  */
 public class AllAppsContainerView extends BaseContainerView implements DragSource,
@@ -72,7 +131,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
         View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener,
         AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider {
 
-    public static final boolean GRID_MERGE_SECTIONS = true;
+    private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
+    private static final int MAX_NUM_MERGES_PHONE = 2;
 
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
@@ -90,6 +150,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
     private ViewGroup mSearchBarContainerView;
     private View mSearchBarView;
 
+    private int mSectionNamesMargin;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
     // This coordinate is relative to this container view
@@ -127,7 +188,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
                 Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
                 2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
                 2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
-
+        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mApps = new AlphabeticalAppsList(context);
         mApps.setAdapterChangedCallback(this);
         mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this);
@@ -342,9 +403,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
             mNumAppsPerRow = grid.allAppsNumCols;
             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
+
+            // If there is a start margin to draw section names, determine how we are going to merge
+            // app sections
+            boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
+            AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
+                    new FullMergeAlgorithm() :
+                    new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
+                            MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
+
             mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
         }
 
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -376,14 +446,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
 
         // Pad the recycler view by the background padding plus the start margin (for the section
         // names)
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int startMargin = grid.isPhone ? getResources().getDimensionPixelSize(
-                R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
+        int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getScrollbarWidth());
         if (isRtl) {
             mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
-                    padding.right + startMargin, 0);
+                    padding.right + startInset, 0);
         } else {
-            mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
+            mAppsRecyclerView.setPadding(padding.left + startInset, 0,
                     padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
         }
 
index dc0d27c..68407bd 100644 (file)
@@ -21,7 +21,6 @@ import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -32,7 +31,6 @@ import android.view.ViewGroup;
 import android.widget.TextView;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -114,11 +112,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
 
         private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
         private Rect mTmpBounds = new Rect();
-        private Launcher mLauncher;
-
-        public GridItemDecoration(Context context) {
-            mLauncher = (Launcher) context;
-        }
 
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
@@ -129,13 +122,13 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
             if (DEBUG_SECTION_MARGIN) {
                 Paint p = new Paint();
                 p.setColor(0x33ff0000);
-                c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mStartMargin,
+                c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mSectionNamesMargin,
                         parent.getMeasuredHeight(), p);
             }
 
-            DeviceProfile grid = mLauncher.getDeviceProfile();
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
             boolean hasDrawnPredictedAppsDivider = false;
+            boolean showSectionNames = mSectionNamesMargin > 0;
             int childCount = parent.getChildCount();
             int lastSectionTop = 0;
             int lastSectionHeight = 0;
@@ -154,7 +147,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
                             mPredictedAppsDividerPaint);
                     hasDrawnPredictedAppsDivider = true;
 
-                } else if (grid.isPhone && shouldDrawItemSection(holder, i, items)) {
+                } else if (showSectionNames && shouldDrawItemSection(holder, i, items)) {
                     // At this point, we only draw sections for each section break;
                     int viewTopOffset = (2 * child.getPaddingTop());
                     int pos = holder.getPosition();
@@ -179,9 +172,10 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
 
                         // Calculate where to draw the section
                         int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
-                        int x = mIsRtl ? parent.getWidth() - mBackgroundPadding.left - mStartMargin :
-                                mBackgroundPadding.left;
-                        x += (int) ((mStartMargin - sectionBounds.x) / 2f);
+                        int x = mIsRtl ?
+                                parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin :
+                                        mBackgroundPadding.left;
+                        x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f);
                         int y = child.getTop() + sectionBaseline;
 
                         // Determine whether this is the last row with apps in that section, if
@@ -309,7 +303,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
     private String mEmptySearchText;
 
     // Section drawing
-    @Thunk int mStartMargin;
+    @Thunk int mSectionNamesMargin;
     @Thunk int mSectionHeaderOffset;
     @Thunk Paint mSectionTextPaint;
     @Thunk Paint mPredictedAppsDividerPaint;
@@ -324,12 +318,12 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
         mGridSizer = new GridSpanSizer();
         mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
-        mItemDecoration = new GridItemDecoration(context);
+        mItemDecoration = new GridItemDecoration();
         mLayoutInflater = LayoutInflater.from(context);
         mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        mStartMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
+        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
 
         mSectionTextPaint = new Paint();
index 25918ce..ff327da 100644 (file)
@@ -39,6 +39,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
     private int mPredictionBarHeight;
+    private int mLastFastscrollPosition = -1;
 
     private Launcher mLauncher;
 
@@ -131,6 +132,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView
         }
     }
 
+    @Override
+    protected void onFastScrollingEnd() {
+        mLastFastscrollPosition = -1;
+    }
+
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
@@ -174,12 +180,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView
 
         // 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);
+        if (mLastFastscrollPosition != lastScrollSection.appItem.position) {
+            mLastFastscrollPosition = lastScrollSection.appItem.position;
+            layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
+        }
 
         return lastScrollSection.sectionName;
     }
 
-
     /**
      * Returns the row index for a app index in the list.
      */
index a0cf5b6..4f29e0c 100644 (file)
@@ -19,18 +19,13 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
-
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.model.AbstractUserComparator;
 import com.android.launcher3.model.AppNameComparator;
-import com.android.launcher3.util.Thunk;
 
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -144,70 +139,11 @@ public class AlphabeticalAppsList {
     /**
      * Common interface for different merging strategies.
      */
-    private interface MergeAlgorithm {
+    public interface MergeAlgorithm {
         boolean continueMerging(SectionInfo section, SectionInfo withSection,
                 int sectionAppCount, int numAppsPerRow, int mergeCount);
     }
 
-    /**
-     * The logic we use to merge sections on tablets.  Currently, we don't show section names on
-     * tablet layouts, so just merge all the sections indiscriminately.
-     */
-    @Thunk static class TabletMergeAlgorithm implements MergeAlgorithm {
-
-        @Override
-        public boolean continueMerging(SectionInfo section, SectionInfo withSection,
-                int sectionAppCount, int numAppsPerRow, int mergeCount) {
-            // Merge EVERYTHING
-            return true;
-        }
-    }
-
-    /**
-     * The logic we use to merge sections on phones.  We only merge sections when their final row
-     * contains less than a certain number of icons, and stop at a specified max number of merges.
-     * In addition, we will try and not merge sections that identify apps from different scripts.
-     */
-    private static class PhoneMergeAlgorithm implements MergeAlgorithm {
-
-        private int mMinAppsPerRow;
-        private int mMinRowsInMergedSection;
-        private int mMaxAllowableMerges;
-        private CharsetEncoder mAsciiEncoder;
-
-        public PhoneMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
-            mMinAppsPerRow = minAppsPerRow;
-            mMinRowsInMergedSection = minRowsInMergedSection;
-            mMaxAllowableMerges = maxNumMerges;
-            mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
-        }
-
-        @Override
-        public boolean continueMerging(SectionInfo section, SectionInfo withSection,
-                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;
-
-            // Ensure that we do not merge across scripts, currently we only allow for english and
-            // native scripts so we can test if both can just be ascii encoded
-            boolean isCrossScript = false;
-            if (section.firstAppItem != null && withSection.firstAppItem != null) {
-                isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
-                        mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
-            }
-            return (0 < cols && cols < mMinAppsPerRow) &&
-                    rows < mMinRowsInMergedSection &&
-                    mergeCount < mMaxAllowableMerges &&
-                    !isCrossScript;
-        }
-    }
-
-    private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
-    private static final int MAX_NUM_MERGES_PHONE = 2;
-
     private Launcher mLauncher;
 
     // The set of apps from the system not including predictions
@@ -249,20 +185,13 @@ public class AlphabeticalAppsList {
     }
 
     /**
-     * Sets the number of apps per row.  Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
+     * Sets the number of apps per row.
      */
-    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
-        // Update the merge algorithm
-        DeviceProfile grid = mLauncher.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();
-        }
-
+    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow,
+            MergeAlgorithm mergeAlgorithm) {
         mNumAppsPerRow = numAppsPerRow;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+        mMergeAlgorithm = mergeAlgorithm;
 
         updateAdapterItems();
     }
@@ -599,13 +528,13 @@ public class AlphabeticalAppsList {
      * Merges multiple sections to reduce visual raggedness.
      */
     private void mergeSections() {
-        // Ignore merging until we have a valid row size
-        if (mNumAppsPerRow == 0) {
+        // Ignore merging until we have an algorithm and a valid row size
+        if (mMergeAlgorithm == null || mNumAppsPerRow == 0) {
             return;
         }
 
         // Go through each section and try and merge some of the sections
-        if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
+        if (!hasFilter()) {
             int sectionAppCount = 0;
             for (int i = 0; i < mSections.size() - 1; i++) {
                 SectionInfo section = mSections.get(i);