diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4c9d57aecd..da56d9049c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,7 +55,7 @@
4dp
- 56dp
+ 0dp
8dp
24sp
48dp
@@ -65,7 +65,7 @@
16dp
4dp
- -16dp
+ -24dp
72dp
48dp
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 6dd029f177..140c28c0c4 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -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,12 +317,37 @@ 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.
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index b300cae629..083e3c19ed 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -58,11 +58,70 @@ 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.
*/
@@ -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);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index dc0d27cd21..68407bdd5d 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -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 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 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