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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user