diff options
author | Winson Chung <winsonc@google.com> | 2015-03-13 11:14:16 -0700 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2015-03-13 11:48:45 -0700 |
commit | 888b3a10bf1e99192f12c844ee275c6f360d6b34 (patch) | |
tree | 0dd028c7b444b7deb9a9c34a91f1efe3a81d1466 | |
parent | 93f98eaf1800024cb2f28379bdd997f3debae63a (diff) | |
download | android_packages_apps_Trebuchet-888b3a10bf1e99192f12c844ee275c6f360d6b34.tar.gz android_packages_apps_Trebuchet-888b3a10bf1e99192f12c844ee275c6f360d6b34.tar.bz2 android_packages_apps_Trebuchet-888b3a10bf1e99192f12c844ee275c6f360d6b34.zip |
Minor changes to apps view.
- Ensuring that apps with numbers and in other locals have a section header.
- Adding an empty state when there are no apps with the current filter
- Removing unnecessary call to check AppInfos
Change-Id: I9dc541c680475b98745fa257ad7e4af06e3966c9
-rw-r--r-- | res/layout-sw600dp/apps_view.xml | 2 | ||||
-rw-r--r-- | res/layout/apps_empty_view.xml | 28 | ||||
-rw-r--r-- | res/layout/apps_reveal_view.xml (renamed from res/layout/apps_list_reveal_view.xml) | 0 | ||||
-rw-r--r-- | res/layout/apps_view.xml | 2 | ||||
-rw-r--r-- | res/values/strings.xml | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/AlphabeticalAppsList.java | 11 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsContainerView.java | 24 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsGridAdapter.java | 53 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsListAdapter.java | 66 | ||||
-rw-r--r-- | src/com/android/launcher3/BubbleTextView.java | 3 | ||||
-rw-r--r-- | src/com/android/launcher3/compat/AlphabeticIndexCompat.java | 26 |
11 files changed, 173 insertions, 46 deletions
diff --git a/res/layout-sw600dp/apps_view.xml b/res/layout-sw600dp/apps_view.xml index 3bb6ec505..0628ca6d3 100644 --- a/res/layout-sw600dp/apps_view.xml +++ b/res/layout-sw600dp/apps_view.xml @@ -22,7 +22,7 @@ android:background="#22000000" android:descendantFocusability="afterDescendants"> <include - layout="@layout/apps_list_reveal_view" + layout="@layout/apps_reveal_view" android:layout_width="@dimen/apps_container_width" android:layout_height="540dp" android:layout_gravity="center" /> diff --git a/res/layout/apps_empty_view.xml b/res/layout/apps_empty_view.xml new file mode 100644 index 000000000..8408077a2 --- /dev/null +++ b/res/layout/apps_empty_view.xml @@ -0,0 +1,28 @@ +<?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. +--> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/empty_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:paddingTop="24dp" + android:paddingBottom="24dp" + android:paddingRight="@dimen/apps_grid_view_start_margin" + android:textSize="16sp" + android:textColor="#4c4c4c" + android:focusable="false" /> + diff --git a/res/layout/apps_list_reveal_view.xml b/res/layout/apps_reveal_view.xml index 19e462bee..19e462bee 100644 --- a/res/layout/apps_list_reveal_view.xml +++ b/res/layout/apps_reveal_view.xml diff --git a/res/layout/apps_view.xml b/res/layout/apps_view.xml index c1bae63f6..00f3cca88 100644 --- a/res/layout/apps_view.xml +++ b/res/layout/apps_view.xml @@ -22,7 +22,7 @@ android:background="@drawable/apps_customize_bg" android:descendantFocusability="afterDescendants"> <include - layout="@layout/apps_list_reveal_view" /> + layout="@layout/apps_reveal_view" /> <include layout="@layout/apps_list_view" /> </com.android.launcher3.AppsContainerView>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 0d113dbf7..408109d15 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -74,6 +74,10 @@ <!-- Apps view --> <!-- Search bar text in the apps view. [CHAR_LIMIT=50] --> <string name="apps_view_search_bar_hint">Search Apps</string> + <!-- Loading apps text. [CHAR_LIMIT=50] --> + <string name="loading_apps_message">Loading Apps...</string> + <!-- No-search-results text. [CHAR_LIMIT=50] --> + <string name="apps_view_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string> <!-- Folders --> <skip /> diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index 2847afc89..c1d2738da 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -78,9 +78,7 @@ public class AlphabeticalAppsList { * Returns the section name for the application. */ public String getSectionNameForApp(AppInfo info) { - String title = info.title.toString(); - String sectionName = mIndexer.getBucketLabel(mIndexer.getBucketIndex(title)); - return sectionName; + return mIndexer.computeSectionName(info.title.toString().trim()); } /** @@ -91,6 +89,13 @@ public class AlphabeticalAppsList { } /** + * Returns whether there are no filtered results. + */ + public boolean hasNoFilteredResults() { + return (mFilter != null) && mFilteredApps.isEmpty(); + } + + /** * Sets the current filter for this list of apps. */ public void setFilter(Filter f) { diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index cc31e20fa..64b27baf5 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -16,10 +16,12 @@ package com.android.launcher3; import android.content.Context; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.KeyEvent; @@ -30,7 +32,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; -import com.android.launcher3.compat.AlphabeticIndexCompat; import java.util.List; @@ -76,6 +77,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O super(context, attrs, defStyleAttr, defStyleRes); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + Resources res = context.getResources(); mLauncher = (Launcher) context; mApps = new AlphabeticalAppsList(context); @@ -83,6 +85,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O mNumAppsPerRow = grid.appsViewNumCols; AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this); + adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); mLayoutManager = adapter.getLayoutManager(context); mItemDecoration = adapter.getItemDecoration(); mAdapter = adapter; @@ -90,6 +93,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O } else if (USE_LAYOUT == LIST_LAYOUT) { mNumAppsPerRow = 1; AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this); + adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); mLayoutManager = adapter.getLayoutManager(context); mAdapter = adapter; } @@ -163,10 +167,12 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O mAppsListView.setHasFixedSize(true); if (isRtl) { mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(), - mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingBottom()); + mAppsListView.getPaddingRight() + mContentMarginStart, + mAppsListView.getPaddingBottom()); } else { - mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, mAppsListView.getPaddingTop(), - mAppsListView.getPaddingRight(), mAppsListView.getPaddingBottom()); + mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, + mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight(), + mAppsListView.getPaddingBottom()); } if (mItemDecoration != null) { mAppsListView.addItemDecoration(mItemDecoration); @@ -299,7 +305,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O if (s.toString().isEmpty()) { mApps.setFilter(null); } else { - final AlphabeticIndexCompat indexer = mApps.getIndexer(); + String formatStr = getResources().getString(R.string.apps_view_no_search_results); + if (USE_LAYOUT == GRID_LAYOUT) { + ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr, + s.toString())); + } else { + ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr, + s.toString())); + } + final String filterText = s.toString().toLowerCase().replaceAll("\\s+", ""); mApps.setFilter(new AlphabeticalAppsList.Filter() { @Override diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 6727e4f09..028cd8f70 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -10,6 +10,7 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.android.launcher3.compat.AlphabeticIndexCompat; @@ -22,6 +23,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { 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; /** * ViewHolder for each icon. @@ -29,11 +31,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public static class ViewHolder extends RecyclerView.ViewHolder { public View mContent; public boolean mIsSectionRow; + public boolean mIsEmptyRow; - public ViewHolder(View v, boolean isSectionRow) { + public ViewHolder(View v, boolean isSectionRow, boolean isEmptyRow) { super(v); mContent = v; mIsSectionRow = isSectionRow; + mIsEmptyRow = isEmptyRow; } } @@ -43,8 +47,14 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup { @Override public int getSpanSize(int position) { + if (mApps.hasNoFilteredResults()) { + // Empty view spans full width + return mAppsPerRow; + } + AppInfo info = mApps.getApps().get(position); if (info == AlphabeticalAppsList.SECTION_BREAK_INFO) { + // Section break spans full width return mAppsPerRow; } else { return 1; @@ -59,14 +69,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - AlphabeticIndexCompat indexer = mApps.getIndexer(); for (int i = 0; i < parent.getChildCount(); i++) { View child = parent.getChildAt(i); ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child); if (holder != null) { GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) child.getLayoutParams(); - if (!holder.mIsSectionRow && !lp.isItemRemoved()) { + if (!holder.mIsSectionRow && !holder.mIsEmptyRow && !lp.isItemRemoved()) { if (mApps.getApps().get(holder.getPosition() - 1) == AlphabeticalAppsList.SECTION_BREAK_INFO) { // Draw at the parent @@ -106,6 +115,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { private View.OnLongClickListener mIconLongClickListener; private int mAppsPerRow; private boolean mIsRtl; + private String mEmptySearchText; // Section drawing private int mStartMargin; @@ -141,6 +151,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { } /** + * Sets the text to show when there are no apps. + */ + public void setEmptySearchText(String query) { + mEmptySearchText = query; + } + + /** * Returns the grid layout manager. */ public GridLayoutManager getLayoutManager(Context context) { @@ -167,8 +184,12 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { + case EMPTY_VIEW_TYPE: + return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent, + false), false /* isSectionRow */, true /* isEmptyRow */); case SECTION_BREAK_VIEW_TYPE: - return new ViewHolder(new View(parent.getContext()), true); + return new ViewHolder(new View(parent.getContext()), true /* isSectionRow */, + false /* isEmptyRow */); case ICON_VIEW_TYPE: BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( R.layout.apps_grid_row_icon_view, parent, false); @@ -176,7 +197,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { icon.setOnClickListener(mIconClickListener); icon.setOnLongClickListener(mIconLongClickListener); icon.setFocusable(true); - return new ViewHolder(icon, false); + return new ViewHolder(icon, false /* isSectionRow */, false /* isEmptyRow */); default: throw new RuntimeException("Unexpected view type"); } @@ -184,21 +205,33 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { @Override public void onBindViewHolder(ViewHolder holder, int position) { - AppInfo info = mApps.getApps().get(position); - if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) { - BubbleTextView icon = (BubbleTextView) holder.mContent; - icon.applyFromApplicationInfo(info); + switch (holder.getItemViewType()) { + case ICON_VIEW_TYPE: + AppInfo info = mApps.getApps().get(position); + BubbleTextView icon = (BubbleTextView) holder.mContent; + icon.applyFromApplicationInfo(info); + break; + case EMPTY_VIEW_TYPE: + TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); + emptyViewText.setText(mEmptySearchText); + break; } } @Override public int getItemCount() { + if (mApps.hasNoFilteredResults()) { + // For the empty view + return 1; + } return mApps.getApps().size(); } @Override public int getItemViewType(int position) { - if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) { + if (mApps.hasNoFilteredResults()) { + return EMPTY_VIEW_TYPE; + } else if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) { return SECTION_BREAK_VIEW_TYPE; } return ICON_VIEW_TYPE; diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java index 8ac381e79..e1f4d3578 100644 --- a/src/com/android/launcher3/AppsListAdapter.java +++ b/src/com/android/launcher3/AppsListAdapter.java @@ -30,12 +30,14 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> { 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; private LayoutInflater mLayoutInflater; private AlphabeticalAppsList mApps; private View.OnTouchListener mTouchListener; private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; + private String mEmptySearchText; public AppsListAdapter(Context context, AlphabeticalAppsList apps, View.OnTouchListener touchListener, View.OnClickListener iconClickListener, @@ -51,9 +53,19 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> { return new LinearLayoutManager(context); } + /** + * Sets the text to show when there are no apps. + */ + public void setEmptySearchText(String query) { + mEmptySearchText = query; + } + @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { + case EMPTY_VIEW_TYPE: + return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent, + false)); case SECTION_BREAK_VIEW_TYPE: return new ViewHolder(new View(parent.getContext())); case ICON_VIEW_TYPE: @@ -79,39 +91,51 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> { @Override public void onBindViewHolder(ViewHolder holder, int position) { - AppInfo info = mApps.getApps().get(position); - if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) { - ViewGroup content = (ViewGroup) holder.mContent; - String sectionDescription = mApps.getSectionNameForApp(info); + switch (holder.getItemViewType()) { + case ICON_VIEW_TYPE: + AppInfo info = mApps.getApps().get(position); + ViewGroup content = (ViewGroup) holder.mContent; + String sectionDescription = mApps.getSectionNameForApp(info); - // Bind the section header - boolean showSectionHeader = true; - if (position > 0) { - AppInfo prevInfo = mApps.getApps().get(position - 1); - showSectionHeader = (prevInfo == AlphabeticalAppsList.SECTION_BREAK_INFO); - } - TextView tv = (TextView) content.findViewById(R.id.section); - if (showSectionHeader) { - tv.setText(sectionDescription); - tv.setVisibility(View.VISIBLE); - } else { - tv.setVisibility(View.INVISIBLE); - } + // Bind the section header + boolean showSectionHeader = true; + if (position > 0) { + AppInfo prevInfo = mApps.getApps().get(position - 1); + showSectionHeader = (prevInfo == AlphabeticalAppsList.SECTION_BREAK_INFO); + } + TextView tv = (TextView) content.findViewById(R.id.section); + if (showSectionHeader) { + tv.setText(sectionDescription); + tv.setVisibility(View.VISIBLE); + } else { + tv.setVisibility(View.INVISIBLE); + } - // Bind the icon - BubbleTextView icon = (BubbleTextView) content.getChildAt(1); - icon.applyFromApplicationInfo(info); + // Bind the icon + BubbleTextView icon = (BubbleTextView) content.getChildAt(1); + icon.applyFromApplicationInfo(info); + break; + case EMPTY_VIEW_TYPE: + TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); + emptyViewText.setText(mEmptySearchText); + break; } } @Override public int getItemCount() { + if (mApps.hasNoFilteredResults()) { + // For the empty view + return 1; + } return mApps.getApps().size(); } @Override public int getItemViewType(int position) { - if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) { + if (mApps.hasNoFilteredResults()) { + return EMPTY_VIEW_TYPE; + } else if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) { return SECTION_BREAK_VIEW_TYPE; } return ICON_VIEW_TYPE; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index fabae5702..8ef234bb0 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -161,7 +161,8 @@ public class BubbleTextView extends TextView { if (info.contentDescription != null) { setContentDescription(info.contentDescription); } - setTag(info); + // We don't need to check the info since it's not a ShortcutInfo + super.setTag(info); } @Override diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index 602a84566..47e1b7a98 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -27,7 +27,7 @@ class BaseAlphabeticIndex { /** * Returns the index of the bucket in which the given string should appear. */ - public int getBucketIndex(String s) { + protected int getBucketIndex(String s) { if (s.isEmpty()) { return UNKNOWN_BUCKET_INDEX; } @@ -41,7 +41,7 @@ class BaseAlphabeticIndex { /** * Returns the label for the bucket at the given index (as returned by getBucketIndex). */ - public String getBucketLabel(int index) { + protected String getBucketLabel(int index) { return BUCKETS.substring(index, index + 1); } } @@ -100,11 +100,29 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { } /** + * Computes the section name for an given string {@param s}. + */ + public String computeSectionName(String s) { + String sectionName = getBucketLabel(getBucketIndex(s)); + if (sectionName.trim().isEmpty() && s.length() > 0) { + boolean startsWithDigit = Character.isDigit(s.charAt(0)); + if (startsWithDigit) { + // Digit section + return "#"; + } else { + // Unknown section + return "\u2022"; + } + } + return sectionName; + } + + /** * Returns the index of the bucket in which {@param s} should appear. * Function is synchronized because underlying routine walks an iterator * whose state is maintained inside the index object. */ - public int getBucketIndex(String s) { + protected int getBucketIndex(String s) { if (mHasValidAlphabeticIndex) { try { return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s); @@ -118,7 +136,7 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { /** * Returns the label for the bucket at the given index (as returned by getBucketIndex). */ - public String getBucketLabel(int index) { + protected String getBucketLabel(int index) { if (mHasValidAlphabeticIndex) { try { return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index); |