Small refactoring to apps list.

- Fixes issue with fading in app icons when items are added/removed
- Reduces number of expensive calls when drawing sections and the scrollbar
- Removes fake section AppInfos in the adapters
This commit is contained in:
Winson Chung
2015-04-06 15:12:49 -07:00
parent c517f4ce65
commit 9121fbffaf
6 changed files with 140 additions and 97 deletions
@@ -81,24 +81,65 @@ public class AlphabeticalAppsList {
/**
* Info about a section in the alphabetic list
*/
public class SectionInfo {
public static class SectionInfo {
// The name of this section
public String sectionName;
// The number of applications in this section
public int numAppsInSection;
// The first app AdapterItem for this section
public AdapterItem firstAppItem;
public SectionInfo(String name) {
sectionName = name;
}
}
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
// The index of this adapter item in the list
public int position;
// Whether or not the item at this adapter position is a section or not
public boolean isSectionHeader;
// The name of this section, or the section that this app is contained in
public String sectionName;
// The associated AppInfo, or null if this adapter item is a section
public AppInfo appInfo;
// The index of this app (not including sections), or -1 if this adapter item is a section
public int appIndex;
public static AdapterItem asSection(int pos, String name) {
AdapterItem item = new AdapterItem();
item.position = pos;
item.isSectionHeader = true;
item.sectionName = name;
item.appInfo = null;
item.appIndex = -1;
return item;
}
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, int appIndex) {
AdapterItem item = new AdapterItem();
item.position = pos;
item.isSectionHeader = false;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
}
/**
* A filter interface to limit the set of applications in the apps list.
*/
public interface Filter {
public boolean retainApp(AppInfo info);
public boolean retainApp(AppInfo info, String sectionName);
}
// Hack to force RecyclerView to break sections
public static final AppInfo SECTION_BREAK_INFO = null;
private List<AppInfo> mApps = new ArrayList<>();
private List<AppInfo> mFilteredApps = new ArrayList<>();
private List<AppInfo> mSectionedFilteredApps = new ArrayList<>();
private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
private List<SectionInfo> mSections = new ArrayList<>();
private RecyclerView.Adapter mAdapter;
private Filter mFilter;
@@ -127,22 +168,15 @@ public class AlphabeticalAppsList {
/**
* Returns the current filtered list of applications broken down into their sections.
*/
public List<AppInfo> getApps() {
public List<AdapterItem> getAdapterItems() {
return mSectionedFilteredApps;
}
/**
* Returns the current filtered list of applications.
* Returns the number of applications in this list.
*/
public List<AppInfo> getAppsWithoutSectionBreaks() {
return mFilteredApps;
}
/**
* Returns the section name for the application.
*/
public String getSectionNameForApp(AppInfo info) {
return mIndexer.computeSectionName(info.title.toString().trim());
public int getSize() {
return mFilteredApps.size();
}
/**
@@ -276,28 +310,39 @@ public class AlphabeticalAppsList {
* Updates internals when the set of apps are updated.
*/
private void onAppsUpdated() {
// Recreate the filtered apps
// Recreate the filtered and sectioned apps (for convenience for the grid layout)
mFilteredApps.clear();
for (AppInfo info : mApps) {
if (mFilter == null || mFilter.retainApp(info)) {
mFilteredApps.add(info);
}
}
// Section the apps (for convenience for the grid layout)
mSections.clear();
mSectionedFilteredApps.clear();
SectionInfo lastSectionInfo = null;
for (AppInfo info : mFilteredApps) {
String sectionName = getSectionNameForApp(info);
if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
lastSectionInfo = new SectionInfo();
lastSectionInfo.sectionName = sectionName;
mSectionedFilteredApps.add(SECTION_BREAK_INFO);
mSections.add(lastSectionInfo);
int position = 0;
int appIndex = 0;
for (AppInfo info : mApps) {
String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
// Check if we want to retain this app
if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
continue;
}
// Create a new section if necessary
if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
lastSectionInfo = new SectionInfo(sectionName);
mSections.add(lastSectionInfo);
// Create a new section item
AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
mSectionedFilteredApps.add(sectionItem);
}
// Create an app item
AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
lastSectionInfo.numAppsInSection++;
mSectionedFilteredApps.add(info);
if (lastSectionInfo.firstAppItem == null) {
lastSectionInfo.firstAppItem = appItem;
}
mSectionedFilteredApps.add(appItem);
mFilteredApps.add(info);
}
}
}
@@ -228,12 +228,12 @@ public class AppsContainerRecyclerView extends RecyclerView
* Draws the fast scroller popup.
*/
private void drawFastScrollerPopup(Canvas canvas) {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
if (mFastScrollAlpha > 0f) {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
// Calculate the position for the fast scroller popup
Rect bgBounds = mFastScrollerBg.getBounds();
if (isRtl) {
@@ -288,52 +288,50 @@ public class AppsContainerRecyclerView extends RecyclerView
*/
private String scrollToPositionAtProgress(float progress) {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
// Get the total number of rows
int rowCount = getNumRows();
if (sections.isEmpty()) {
return "";
}
// Find the position of the first application in the section that contains the row at the
// current progress
int rowAtProgress = (int) (progress * rowCount);
int appIndex = 0;
rowCount = 0;
for (AlphabeticalAppsList.SectionInfo info : sections) {
int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
if (rowCount + numRowsInSection > rowAtProgress) {
int rowAtProgress = (int) (progress * getNumRows());
int rowCount = 0;
AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
for (AlphabeticalAppsList.SectionInfo section : sections) {
int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow);
if (rowCount + numRowsInSection >= rowAtProgress) {
lastSectionInfo = section;
break;
}
rowCount += numRowsInSection;
appIndex += info.numAppsInSection;
}
appIndex = Math.max(0, Math.min(mApps.getAppsWithoutSectionBreaks().size() - 1, appIndex));
AppInfo appInfo = mApps.getAppsWithoutSectionBreaks().get(appIndex);
int sectionedAppIndex = mApps.getApps().indexOf(appInfo);
int position = mApps.getAdapterItems().indexOf(lastSectionInfo.firstAppItem);
// Scroll the position into view, anchored at the top of the screen if possible. We call the
// scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
stopScroll();
layoutManager.scrollToPositionWithOffset(sectionedAppIndex, 0);
layoutManager.scrollToPositionWithOffset(position, 0);
// Return the section name of the row
return mApps.getSectionNameForApp(appInfo);
return mApps.getAdapterItems().get(position).sectionName;
}
/**
* Returns the bounds for the scrollbar.
*/
private void updateVerticalScrollbarBounds() {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
// Skip early if there are no items
if (mApps.getApps().isEmpty()) {
if (mApps.getAdapterItems().isEmpty()) {
mVerticalScrollbarBounds.setEmpty();
return;
}
// Find the index and height of the first visible row (all rows have the same height)
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
int rowIndex = -1;
int rowTopOffset = -1;
int rowHeight = -1;
@@ -341,12 +339,11 @@ public class AppsContainerRecyclerView extends RecyclerView
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int position = getChildPosition(child);
int position = getChildAdapterPosition(child);
if (position != NO_POSITION) {
AppInfo info = mApps.getApps().get(position);
if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) {
int appIndex = mApps.getAppsWithoutSectionBreaks().indexOf(info);
rowIndex = findRowForAppIndex(appIndex);
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
if (!item.isSectionHeader) {
rowIndex = findRowForAppIndex(item.appIndex);
rowTopOffset = getLayoutManager().getDecoratedTop(child);
rowHeight = child.getHeight();
break;
@@ -21,7 +21,6 @@ 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;
@@ -319,9 +318,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
mApps.setFilter(new AlphabeticalAppsList.Filter() {
@Override
public boolean retainApp(AppInfo info) {
public boolean retainApp(AppInfo info, String sectionName) {
String title = info.title.toString();
String sectionName = mApps.getSectionNameForApp(info);
return sectionName.toLowerCase().contains(filterText) ||
title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
}
@@ -332,15 +330,22 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
List<AppInfo> appsWithoutSections = mApps.getAppsWithoutSectionBreaks();
List<AppInfo> apps = mApps.getApps();
if (appsWithoutSections.size() == 1) {
mAppsListView.getChildAt(apps.indexOf(appsWithoutSections.get(0))).performClick();
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
// Skip the quick-launch if there isn't exactly one item
if (mApps.getSize() != 1) {
return false;
}
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
if (!item.isSectionHeader) {
mAppsListView.getChildAt(i).performClick();
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
return true;
}
}
return true;
}
return false;
}
+11 -10
View File
@@ -12,9 +12,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.util.Thunk;
import java.util.List;
/**
* The grid view adapter of all the apps.
@@ -54,8 +55,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
return mAppsPerRow;
}
AppInfo info = mApps.getApps().get(position);
if (info == AlphabeticalAppsList.SECTION_BREAK_INFO) {
if (mApps.getAdapterItems().get(position).isSectionHeader) {
// Section break spans full width
return mAppsPerRow;
} else {
@@ -71,6 +71,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
@@ -78,11 +79,11 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
child.getLayoutParams();
if (!holder.mIsSectionRow && !holder.mIsEmptyRow && !lp.isItemRemoved()) {
if (mApps.getApps().get(holder.getPosition() - 1) ==
AlphabeticalAppsList.SECTION_BREAK_INFO) {
if (items.get(holder.getAdapterPosition() - 1).isSectionHeader) {
// Draw at the parent
AppInfo info = mApps.getApps().get(holder.getPosition());
String section = mApps.getSectionNameForApp(info);
AlphabeticalAppsList.AdapterItem item =
items.get(holder.getAdapterPosition());
String section = item.sectionName;
mSectionTextPaint.getTextBounds(section, 0, section.length(),
mTmpBounds);
if (mIsRtl) {
@@ -212,7 +213,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case ICON_VIEW_TYPE:
AppInfo info = mApps.getApps().get(position);
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.mContent;
icon.applyFromApplicationInfo(info);
break;
@@ -229,14 +230,14 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
// For the empty view
return 1;
}
return mApps.getApps().size();
return mApps.getAdapterItems().size();
}
@Override
public int getItemViewType(int position) {
if (mApps.hasNoFilteredResults()) {
return EMPTY_VIEW_TYPE;
} else if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
} else if (mApps.getAdapterItems().get(position).isSectionHeader) {
return SECTION_BREAK_VIEW_TYPE;
}
return ICON_VIEW_TYPE;
@@ -9,7 +9,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.compat.AlphabeticIndexCompat;
/**
* The linear list view adapter for all the apps.
@@ -93,15 +92,16 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case ICON_VIEW_TYPE:
AppInfo info = mApps.getApps().get(position);
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
ViewGroup content = (ViewGroup) holder.mContent;
String sectionDescription = mApps.getSectionNameForApp(info);
String sectionDescription = item.sectionName;
// Bind the section header
boolean showSectionHeader = true;
if (position > 0) {
AppInfo prevInfo = mApps.getApps().get(position - 1);
showSectionHeader = (prevInfo == AlphabeticalAppsList.SECTION_BREAK_INFO);
AlphabeticalAppsList.AdapterItem prevItem =
mApps.getAdapterItems().get(position - 1);
showSectionHeader = prevItem.isSectionHeader;
}
TextView tv = (TextView) content.findViewById(R.id.section);
if (showSectionHeader) {
@@ -113,7 +113,7 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
// Bind the icon
BubbleTextView icon = (BubbleTextView) content.getChildAt(1);
icon.applyFromApplicationInfo(info);
icon.applyFromApplicationInfo(item.appInfo);
break;
case EMPTY_VIEW_TYPE:
TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
@@ -128,14 +128,14 @@ class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
// For the empty view
return 1;
}
return mApps.getApps().size();
return mApps.getAdapterItems().size();
}
@Override
public int getItemViewType(int position) {
if (mApps.hasNoFilteredResults()) {
return EMPTY_VIEW_TYPE;
} else if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
} else if (mApps.getAdapterItems().get(position).isSectionHeader) {
return SECTION_BREAK_VIEW_TYPE;
}
return ICON_VIEW_TYPE;
@@ -388,11 +388,6 @@ public class BubbleTextView extends TextView {
}
}
@Override
protected boolean onSetAlpha(int alpha) {
return true;
}
@Override
public void cancelLongPress() {
super.cancelLongPress();