Pulling out predictions into another row view.
Change-Id: Iba0d74457a1314cf0c00a88f9b07df049334e542
This commit is contained in:
parent
11509ad61a
commit
208ed75cfd
|
@ -15,5 +15,5 @@
|
|||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/WorkspaceIcon"
|
||||
style="@style/Icon"
|
||||
android:focusable="true" />
|
||||
|
|
|
@ -15,5 +15,5 @@
|
|||
-->
|
||||
|
||||
<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/WorkspaceIcon"
|
||||
style="@style/Icon"
|
||||
android:focusable="true" />
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<com.android.launcher3.BubbleTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/WorkspaceIcon.AppsCustomize"
|
||||
style="@style/Icon.AllApps"
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
|
@ -13,20 +13,37 @@
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/apps_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:elevation="15dp"
|
||||
android:visibility="gone"
|
||||
android:focusableInTouchMode="true">
|
||||
<com.android.launcher3.AppsContainerRecyclerView
|
||||
android:id="@+id/apps_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/apps_search_bar_height"
|
||||
android:layout_gravity="center_horizontal|top"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:descendantFocusability="afterDescendants" />
|
||||
<LinearLayout
|
||||
android:id="@+id/prediction_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/apps_search_bar_height"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="invisible">
|
||||
</LinearLayout>
|
||||
|
||||
<!-- We always want the search bar on top, so it goes last. -->
|
||||
<FrameLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/apps_search_bar_height"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/apps_search_bg">
|
||||
<LinearLayout
|
||||
android:id="@+id/app_search_container"
|
||||
|
@ -40,8 +57,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginStart="4dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingBottom="13dp"
|
||||
android:contentDescription="@string/all_apps_button_label"
|
||||
android:src="@drawable/ic_arrow_back_grey" />
|
||||
<com.android.launcher3.AppsContainerSearchEditTextView
|
||||
|
@ -69,19 +86,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingTop="13dp"
|
||||
android:paddingBottom="13dp"
|
||||
android:contentDescription="@string/apps_view_search_bar_hint"
|
||||
android:src="@drawable/ic_search_grey" />
|
||||
</FrameLayout>
|
||||
<com.android.launcher3.AppsContainerRecyclerView
|
||||
android:id="@+id/apps_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:descendantFocusability="afterDescendants" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,29 @@
|
|||
<?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.
|
||||
-->
|
||||
<com.android.launcher3.BubbleTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Icon.AllApps"
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/focusable_view_bg"
|
||||
launcher:deferShadowGeneration="true"
|
||||
launcher:iconDisplay="all_apps" />
|
||||
|
|
@ -15,5 +15,5 @@
|
|||
-->
|
||||
|
||||
<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/WorkspaceIcon.Folder"
|
||||
style="@style/Icon.Folder"
|
||||
android:focusable="true" />
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
android:antialias="true"
|
||||
android:src="@drawable/portal_ring_inner_holo"/>
|
||||
<com.android.launcher3.BubbleTextView
|
||||
style="@style/WorkspaceIcon"
|
||||
style="@style/Icon"
|
||||
android:id="@+id/folder_icon_name"
|
||||
android:layout_gravity="top"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<dimen name="apps_view_fast_scroll_text_size">48dp</dimen>
|
||||
<dimen name="apps_search_bar_height">52dp</dimen>
|
||||
<dimen name="apps_icon_top_bottom_padding">8dp</dimen>
|
||||
<dimen name="apps_prediction_icon_top_bottom_padding">12dp</dimen>
|
||||
|
||||
<!-- Note: This needs to match the fixed insets for the search box. -->
|
||||
<dimen name="container_fixed_bounds_inset">8dp</dimen>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<resources>
|
||||
|
||||
<style name="WorkspaceIcon">
|
||||
<style name="Icon">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
|
@ -32,11 +32,7 @@
|
|||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
</style>
|
||||
|
||||
<style name="WorkspaceIcon.Portrait"></style>
|
||||
|
||||
<style name="WorkspaceIcon.Landscape"></style>
|
||||
|
||||
<style name="WorkspaceIcon.AppsCustomize">
|
||||
<style name="Icon.AllApps">
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:textColor">@color/quantum_panel_text_color</item>
|
||||
<item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item>
|
||||
|
@ -44,7 +40,7 @@
|
|||
<item name="customShadows">false</item>
|
||||
</style>
|
||||
|
||||
<style name="WorkspaceIcon.Folder">
|
||||
<style name="Icon.Folder">
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:textColor">@color/quantum_panel_text_color</item>
|
||||
<item name="android:shadowRadius">0</item>
|
||||
|
|
|
@ -151,11 +151,13 @@ public class AlphabeticalAppsList {
|
|||
* Info about a particular adapter item (can be either section or app)
|
||||
*/
|
||||
public static class AdapterItem {
|
||||
/** Section & App properties */
|
||||
/** Common properties */
|
||||
// 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 type of this item
|
||||
public int viewType;
|
||||
|
||||
/** Section & App properties */
|
||||
// The section for this item
|
||||
public SectionInfo sectionInfo;
|
||||
|
||||
|
@ -169,30 +171,33 @@ public class AlphabeticalAppsList {
|
|||
public AppInfo appInfo = null;
|
||||
// The index of this app not including sections
|
||||
public int appIndex = -1;
|
||||
// Whether or not this is a predicted app
|
||||
public boolean isPredictedApp;
|
||||
|
||||
public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AppsGridAdapter.SECTION_BREAK_VIEW_TYPE;
|
||||
item.position = pos;
|
||||
item.isSectionHeader = true;
|
||||
item.sectionInfo = section;
|
||||
section.sectionBreakItem = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
|
||||
int sectionAppIndex, AppInfo appInfo, int appIndex,
|
||||
boolean isPredictedApp) {
|
||||
public static AdapterItem asPredictionBarSpacer(int pos) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE;
|
||||
item.position = pos;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
|
||||
int sectionAppIndex, AppInfo appInfo, int appIndex) {
|
||||
AdapterItem item = new AdapterItem();
|
||||
item.viewType = AppsGridAdapter.ICON_VIEW_TYPE;
|
||||
item.position = pos;
|
||||
item.isSectionHeader = false;
|
||||
item.sectionInfo = section;
|
||||
item.sectionName = sectionName;
|
||||
item.sectionAppIndex = sectionAppIndex;
|
||||
item.appInfo = appInfo;
|
||||
item.appIndex = appIndex;
|
||||
item.isPredictedApp = isPredictedApp;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +209,13 @@ public class AlphabeticalAppsList {
|
|||
boolean retainApp(AppInfo info, String sectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback to notify of changes to the filter.
|
||||
*/
|
||||
public interface FilterChangedCallback {
|
||||
void onFilterChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for different merging strategies.
|
||||
*/
|
||||
|
@ -260,28 +272,31 @@ public class AlphabeticalAppsList {
|
|||
private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
|
||||
private List<SectionInfo> mSections = new ArrayList<>();
|
||||
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
|
||||
private List<ComponentName> mPredictedApps = new ArrayList<>();
|
||||
private List<ComponentName> mPredictedAppComponents = new ArrayList<>();
|
||||
private List<AppInfo> mPredictedApps = new ArrayList<>();
|
||||
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
|
||||
private RecyclerView.Adapter mAdapter;
|
||||
private Filter mFilter;
|
||||
private AlphabeticIndexCompat mIndexer;
|
||||
private AppNameComparator mAppNameComparator;
|
||||
private MergeAlgorithm mMergeAlgorithm;
|
||||
private FilterChangedCallback mFilterChangedCallback;
|
||||
private int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
|
||||
public AlphabeticalAppsList(Context context, int numAppsPerRow) {
|
||||
public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow,
|
||||
int numPredictedAppsPerRow) {
|
||||
mContext = context;
|
||||
mIndexer = new AlphabeticIndexCompat(context);
|
||||
mAppNameComparator = new AppNameComparator(context);
|
||||
setNumAppsPerRow(numAppsPerRow);
|
||||
mFilterChangedCallback = cb;
|
||||
setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
|
||||
*/
|
||||
public void setNumAppsPerRow(int numAppsPerRow) {
|
||||
mNumAppsPerRow = numAppsPerRow;
|
||||
|
||||
public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
|
||||
// Update the merge algorithm
|
||||
DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
|
||||
if (grid.isPhone()) {
|
||||
|
@ -291,6 +306,9 @@ public class AlphabeticalAppsList {
|
|||
mMergeAlgorithm = new TabletMergeAlgorithm();
|
||||
}
|
||||
|
||||
mNumAppsPerRow = numAppsPerRow;
|
||||
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
|
||||
|
||||
onAppsUpdated();
|
||||
}
|
||||
|
||||
|
@ -351,6 +369,9 @@ public class AlphabeticalAppsList {
|
|||
mFilter = f;
|
||||
onAppsUpdated();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (mFilterChangedCallback != null){
|
||||
mFilterChangedCallback.onFilterChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,12 +380,19 @@ public class AlphabeticalAppsList {
|
|||
* of applications, we should merge the results only in onAppsUpdated() which is idempotent.
|
||||
*/
|
||||
public void setPredictedApps(List<ComponentName> apps) {
|
||||
mPredictedApps.clear();
|
||||
mPredictedApps.addAll(apps);
|
||||
mPredictedAppComponents.clear();
|
||||
mPredictedAppComponents.addAll(apps);
|
||||
onAppsUpdated();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current set of predicted apps.
|
||||
*/
|
||||
public List<AppInfo> getPredictedApps() {
|
||||
return mPredictedApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of apps.
|
||||
*/
|
||||
|
@ -450,6 +478,42 @@ public class AlphabeticalAppsList {
|
|||
// Sort the list of apps
|
||||
Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
|
||||
|
||||
// Prepare to update the list of sections, filtered apps, etc.
|
||||
mFilteredApps.clear();
|
||||
mSections.clear();
|
||||
mSectionedFilteredApps.clear();
|
||||
mFastScrollerSections.clear();
|
||||
SectionInfo lastSectionInfo = null;
|
||||
String lastSectionName = null;
|
||||
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
|
||||
int position = 0;
|
||||
int appIndex = 0;
|
||||
List<AppInfo> allApps = new ArrayList<>();
|
||||
|
||||
|
||||
// Process the predicted app components
|
||||
mPredictedApps.clear();
|
||||
if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
|
||||
for (ComponentName cn : mPredictedAppComponents) {
|
||||
for (AppInfo info : mApps) {
|
||||
if (cn.equals(info.componentName)) {
|
||||
mPredictedApps.add(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop at the number of predicted apps
|
||||
if (mPredictedApps.size() == mNumPredictedAppsPerRow) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPredictedApps.isEmpty()) {
|
||||
// Create a new spacer for the prediction bar
|
||||
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
|
||||
mSectionedFilteredApps.add(sectionItem);
|
||||
}
|
||||
}
|
||||
|
||||
// As a special case for some languages (currently only Simplified Chinese), we may need to
|
||||
// coalesce sections
|
||||
Locale curLocale = mContext.getResources().getConfiguration().locale;
|
||||
|
@ -475,6 +539,11 @@ public class AlphabeticalAppsList {
|
|||
}
|
||||
sectionApps.add(info);
|
||||
}
|
||||
|
||||
// Add it to the list
|
||||
for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
|
||||
allApps.addAll(entry.getValue());
|
||||
}
|
||||
} else {
|
||||
// Just compute the section headers for use below
|
||||
for (AppInfo info : mApps) {
|
||||
|
@ -485,44 +554,7 @@ public class AlphabeticalAppsList {
|
|||
mCachedSectionNames.put(info.title, sectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare to update the list of sections, filtered apps, etc.
|
||||
mFilteredApps.clear();
|
||||
mSections.clear();
|
||||
mSectionedFilteredApps.clear();
|
||||
mFastScrollerSections.clear();
|
||||
SectionInfo lastSectionInfo = null;
|
||||
String lastSectionName = null;
|
||||
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
|
||||
int position = 0;
|
||||
int appIndex = 0;
|
||||
List<AppInfo> allApps = new ArrayList<>();
|
||||
|
||||
// Add the predicted apps to the combined list
|
||||
int numPredictedApps = 0;
|
||||
if (mPredictedApps != null && !mPredictedApps.isEmpty() && !hasFilter()) {
|
||||
for (ComponentName cn : mPredictedApps) {
|
||||
for (AppInfo info : mApps) {
|
||||
if (cn.equals(info.componentName)) {
|
||||
allApps.add(info);
|
||||
numPredictedApps++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop at the number of predicted apps
|
||||
if (numPredictedApps == mNumAppsPerRow) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the other apps to the combined list
|
||||
if (localeRequiresSectionSorting) {
|
||||
for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
|
||||
allApps.addAll(entry.getValue());
|
||||
}
|
||||
} else {
|
||||
// Add it to the list
|
||||
allApps.addAll(mApps);
|
||||
}
|
||||
|
||||
|
@ -530,10 +562,9 @@ public class AlphabeticalAppsList {
|
|||
// ordered set of sections
|
||||
int numApps = allApps.size();
|
||||
for (int i = 0; i < numApps; i++) {
|
||||
boolean isPredictedApp = i < numPredictedApps;
|
||||
AppInfo info = allApps.get(i);
|
||||
// The section name was computed above so this should be find
|
||||
String sectionName = isPredictedApp ? "" : mCachedSectionNames.get(info.title);
|
||||
String sectionName = mCachedSectionNames.get(info.title);
|
||||
|
||||
// Check if we want to retain this app
|
||||
if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
|
||||
|
@ -541,8 +572,7 @@ public class AlphabeticalAppsList {
|
|||
}
|
||||
|
||||
// Create a new section if the section names do not match
|
||||
if (lastSectionInfo == null ||
|
||||
(!isPredictedApp && !sectionName.equals(lastSectionName))) {
|
||||
if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
|
||||
lastSectionName = sectionName;
|
||||
lastSectionInfo = new SectionInfo();
|
||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
|
||||
|
@ -559,7 +589,7 @@ public class AlphabeticalAppsList {
|
|||
|
||||
// Create an app item
|
||||
AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
|
||||
lastSectionInfo.numApps++, info, appIndex++, isPredictedApp);
|
||||
lastSectionInfo.numApps++, info, appIndex++);
|
||||
if (lastSectionInfo.firstAppItem == null) {
|
||||
lastSectionInfo.firstAppItem = appItem;
|
||||
lastFastScrollerSectionInfo.appItem = appItem;
|
||||
|
@ -568,6 +598,14 @@ public class AlphabeticalAppsList {
|
|||
mFilteredApps.add(info);
|
||||
}
|
||||
|
||||
// Merge multiple sections together as requested by the merge strategy for this device
|
||||
mergeSections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges multiple sections to reduce visual raggedness.
|
||||
*/
|
||||
private void mergeSections() {
|
||||
// Go through each section and try and merge some of the sections
|
||||
if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
|
||||
int sectionAppCount = 0;
|
||||
|
|
|
@ -45,8 +45,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
* scroller.
|
||||
*/
|
||||
private static class ScrollPositionState {
|
||||
// The index of the first app in the row (Note that is this not the position)
|
||||
int rowFirstAppIndex;
|
||||
// The index of the first visible row
|
||||
int rowIndex;
|
||||
// The offset of the first visible row
|
||||
|
@ -59,6 +57,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
|
||||
private AlphabeticalAppsList mApps;
|
||||
private int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
|
||||
private Drawable mScrollbar;
|
||||
private Drawable mFastScrollerBg;
|
||||
|
@ -68,6 +67,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
private Paint mFastScrollTextPaint;
|
||||
private Rect mFastScrollTextBounds = new Rect();
|
||||
private float mFastScrollAlpha;
|
||||
private int mPredictionBarHeight;
|
||||
private int mDownX;
|
||||
private int mDownY;
|
||||
private int mLastX;
|
||||
|
@ -123,8 +123,9 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
/**
|
||||
* Sets the number of apps per row in this recycler view.
|
||||
*/
|
||||
public void setNumAppsPerRow(int rowSize) {
|
||||
mNumAppsPerRow = rowSize;
|
||||
public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
|
||||
mNumAppsPerRow = numAppsPerRow;
|
||||
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -133,6 +134,13 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
background.getPadding(mBackgroundPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prediction bar height.
|
||||
*/
|
||||
public void setPredictionBarHeight(int height) {
|
||||
mPredictionBarHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fast scroller alpha.
|
||||
*/
|
||||
|
@ -330,6 +338,26 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
return "";
|
||||
}
|
||||
|
||||
// Stop the scroller if it is scrolling
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
||||
stopScroll();
|
||||
|
||||
// If there is a prediction bar, then capture the appropriate area for the prediction bar
|
||||
float predictionBarFraction = 0f;
|
||||
if (mPredictionBarHeight > 0) {
|
||||
predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize();
|
||||
if (touchFraction <= predictionBarFraction) {
|
||||
// Scroll to the top of the view, where the prediction bar is
|
||||
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||
updateScrollY(0);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from
|
||||
// predictionBarFraction..1
|
||||
touchFraction = (touchFraction - predictionBarFraction) *
|
||||
(1f / (1f - predictionBarFraction));
|
||||
AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
|
||||
for (int i = 1; i < fastScrollSections.size(); i++) {
|
||||
AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
|
||||
|
@ -340,21 +368,19 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
lastScrollSection = scrollSection;
|
||||
}
|
||||
|
||||
// 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(lastScrollSection.appItem.position, 0);
|
||||
|
||||
// We need to workaround the RecyclerView to get the right scroll position after scrolling
|
||||
// We need to workaround the RecyclerView to get the right scroll position
|
||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
if (mScrollPosState.rowIndex != -1) {
|
||||
int rowIndex = findRowForAppIndex(mScrollPosState.rowFirstAppIndex);
|
||||
int y = (rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
|
||||
updateScrollY(y);
|
||||
int scrollY = getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) +
|
||||
mPredictionBarHeight - mScrollPosState.rowTopOffset;
|
||||
updateScrollY(scrollY);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
return lastScrollSection.sectionName;
|
||||
}
|
||||
|
||||
|
@ -377,10 +403,9 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
LAYOUT_DIRECTION_RTL);
|
||||
int rowCount = getNumRows();
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
|
||||
if (mScrollPosState.rowIndex != -1) {
|
||||
int height = getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
int totalScrollHeight = rowCount * mScrollPosState.rowHeight;
|
||||
int totalScrollHeight = rowCount * mScrollPosState.rowHeight + mPredictionBarHeight;
|
||||
if (totalScrollHeight > height) {
|
||||
int scrollbarHeight = Math.max(mScrollbarMinHeight,
|
||||
(int) (height / ((float) totalScrollHeight / height)));
|
||||
|
@ -396,8 +421,8 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
// that the user has already scrolled and then map that to the scroll bar bounds
|
||||
int availableY = totalScrollHeight - height;
|
||||
int availableScrollY = height - scrollbarHeight;
|
||||
y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
|
||||
mScrollPosState.rowTopOffset;
|
||||
y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + mPredictionBarHeight
|
||||
- mScrollPosState.rowTopOffset;
|
||||
y = getPaddingTop() +
|
||||
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
|
||||
|
||||
|
@ -444,7 +469,6 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
*/
|
||||
private void getCurScrollState(ScrollPositionState stateOut,
|
||||
List<AlphabeticalAppsList.AdapterItem> items) {
|
||||
stateOut.rowFirstAppIndex = -1;
|
||||
stateOut.rowIndex = -1;
|
||||
stateOut.rowTopOffset = -1;
|
||||
stateOut.rowHeight = -1;
|
||||
|
@ -454,8 +478,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
|||
int position = getChildPosition(child);
|
||||
if (position != NO_POSITION) {
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||
if (!item.isSectionHeader) {
|
||||
stateOut.rowFirstAppIndex = item.appIndex;
|
||||
if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) {
|
||||
stateOut.rowIndex = findRowForAppIndex(item.appIndex);
|
||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||
stateOut.rowHeight = child.getHeight();
|
||||
|
|
|
@ -26,12 +26,14 @@ import android.text.Editable;
|
|||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
@ -44,11 +46,11 @@ import java.util.regex.Pattern;
|
|||
* The all apps view container.
|
||||
*/
|
||||
public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
|
||||
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
|
||||
AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
|
||||
View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
public static final boolean GRID_MERGE_SECTIONS = true;
|
||||
public static final boolean GRID_HIDE_SECTION_HEADERS = false;
|
||||
|
||||
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
|
||||
private static final boolean DYNAMIC_HEADER_ELEVATION = true;
|
||||
|
@ -64,12 +66,14 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
|
||||
@Thunk Launcher mLauncher;
|
||||
@Thunk AlphabeticalAppsList mApps;
|
||||
private LayoutInflater mLayoutInflater;
|
||||
private AppsGridAdapter mAdapter;
|
||||
private RecyclerView.LayoutManager mLayoutManager;
|
||||
private RecyclerView.ItemDecoration mItemDecoration;
|
||||
|
||||
private LinearLayout mContentView;
|
||||
private FrameLayout mContentView;
|
||||
@Thunk AppsContainerRecyclerView mAppsRecyclerView;
|
||||
private ViewGroup mPredictionBarView;
|
||||
private View mHeaderView;
|
||||
private View mSearchBarContainerView;
|
||||
private View mSearchButtonView;
|
||||
|
@ -77,11 +81,13 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
private AppsContainerSearchEditTextView mSearchBarEditView;
|
||||
|
||||
private int mNumAppsPerRow;
|
||||
private int mNumPredictedAppsPerRow;
|
||||
private Point mLastTouchDownPos = new Point(-1, -1);
|
||||
private Point mLastTouchPos = new Point();
|
||||
private int mContentMarginStart;
|
||||
// Normal container insets
|
||||
private int mContainerInset;
|
||||
private int mPredictionBarHeight;
|
||||
// RecyclerView scroll position
|
||||
@Thunk int mRecyclerViewScrollY;
|
||||
|
||||
|
@ -101,12 +107,17 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
|
||||
mContainerInset = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.apps_container_inset);
|
||||
mPredictionBarHeight = grid.allAppsCellHeightPx +
|
||||
2 * res.getDimensionPixelSize(R.dimen.apps_prediction_icon_top_bottom_padding);
|
||||
mLauncher = (Launcher) context;
|
||||
mLayoutInflater = LayoutInflater.from(context);
|
||||
mNumAppsPerRow = grid.appsViewNumCols;
|
||||
mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
|
||||
mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this);
|
||||
mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
|
||||
mApps = new AlphabeticalAppsList(context, this, mNumAppsPerRow, mNumPredictedAppsPerRow);
|
||||
mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
|
||||
mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
|
||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mAdapter.setPredictionRowHeight(mPredictionBarHeight);
|
||||
mLayoutManager = mAdapter.getLayoutManager();
|
||||
mItemDecoration = mAdapter.getItemDecoration();
|
||||
mContentMarginStart = mAdapter.getContentMarginStart();
|
||||
|
@ -186,7 +197,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
|
||||
// Work around the search box getting first focus and showing the cursor by
|
||||
// proxying the focus from the content view to the recycler view directly
|
||||
mContentView = (LinearLayout) findViewById(R.id.apps_list);
|
||||
mContentView = (FrameLayout) findViewById(R.id.apps_list);
|
||||
mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
|
@ -195,12 +206,20 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fix the header view elevation if not dynamically calculating it
|
||||
mHeaderView = findViewById(R.id.header);
|
||||
mHeaderView.setOnClickListener(this);
|
||||
if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
|
||||
mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
|
||||
getContext().getResources().getDisplayMetrics()));
|
||||
}
|
||||
|
||||
// Fix the prediction bar size
|
||||
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
||||
lp.height = mPredictionBarHeight;
|
||||
|
||||
mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
|
||||
mSearchBarContainerView = findViewById(R.id.app_search_container);
|
||||
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
|
||||
|
@ -227,7 +246,8 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
}
|
||||
mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
|
||||
mAppsRecyclerView.setApps(mApps);
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
||||
mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
|
||||
mAppsRecyclerView.setLayoutManager(mLayoutManager);
|
||||
mAppsRecyclerView.setAdapter(mAdapter);
|
||||
mAppsRecyclerView.setHasFixedSize(true);
|
||||
|
@ -246,6 +266,42 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
onUpdatePaddings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindPredictionBar() {
|
||||
if (!updatePredictionBarVisibility()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<AppInfo> predictedApps = mApps.getPredictedApps();
|
||||
int childCount = mPredictionBarView.getChildCount();
|
||||
for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
|
||||
BubbleTextView icon;
|
||||
if (i < childCount) {
|
||||
// If a child at that index exists, then get that child
|
||||
icon = (BubbleTextView) mPredictionBarView.getChildAt(i);
|
||||
} else {
|
||||
// Otherwise, inflate a new icon
|
||||
icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
R.layout.apps_prediction_bar_icon_view, mPredictionBarView, false);
|
||||
icon.setOnTouchListener(this);
|
||||
icon.setOnClickListener(mLauncher);
|
||||
icon.setOnLongClickListener(this);
|
||||
icon.setFocusable(true);
|
||||
mPredictionBarView.addView(icon);
|
||||
}
|
||||
|
||||
// Either apply the app info to the child, or hide the view
|
||||
if (i < predictedApps.size()) {
|
||||
if (icon.getVisibility() != View.VISIBLE) {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
icon.applyFromApplicationInfo(predictedApps.get(i));
|
||||
} else {
|
||||
icon.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFixedBoundsUpdated() {
|
||||
// Update the number of items in the grid
|
||||
|
@ -253,9 +309,10 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
||||
if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
|
||||
mNumAppsPerRow = grid.appsViewNumCols;
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
|
||||
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mApps.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,10 +354,16 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
|
||||
// Update the header bar
|
||||
if (hasSearchBar) {
|
||||
LinearLayout.LayoutParams lp =
|
||||
(LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
|
||||
FrameLayout.LayoutParams lp =
|
||||
(FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
|
||||
lp.leftMargin = lp.rightMargin = inset;
|
||||
mHeaderView.requestLayout();
|
||||
}
|
||||
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
||||
lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
|
||||
lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
|
||||
mPredictionBarView.requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -499,7 +562,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(i);
|
||||
if (!item.isSectionHeader) {
|
||||
if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) {
|
||||
mAppsRecyclerView.getChildAt(i).performClick();
|
||||
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
return true;
|
||||
|
@ -509,6 +572,11 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilterChanged() {
|
||||
updatePredictionBarVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getContent() {
|
||||
return null;
|
||||
|
@ -554,6 +622,12 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
mHeaderView.setElevation(newElevation);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Optimize this, stop once we are out of bounds
|
||||
if (mRecyclerViewScrollY < 0) {
|
||||
new Throwable().printStackTrace();
|
||||
}
|
||||
mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -682,6 +756,21 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
|||
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility of the prediction bar.
|
||||
* @return whether the prediction bar is visible
|
||||
*/
|
||||
private boolean updatePredictionBarVisibility() {
|
||||
boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() ||
|
||||
mSearchBarEditView.getEditableText().toString().isEmpty());
|
||||
if (showPredictionBar) {
|
||||
mPredictionBarView.setVisibility(View.VISIBLE);
|
||||
} else if (!showPredictionBar) {
|
||||
mPredictionBarView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
return showPredictionBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input method manager.
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
|||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -26,21 +27,31 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
public static final String TAG = "AppsGridAdapter";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
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;
|
||||
// A section break in the grid
|
||||
public static final int SECTION_BREAK_VIEW_TYPE = 0;
|
||||
// A normal icon
|
||||
public static final int ICON_VIEW_TYPE = 1;
|
||||
// The message shown when there are no filtered results
|
||||
public static final int EMPTY_VIEW_TYPE = 2;
|
||||
// The spacer used for the prediction bar
|
||||
public static final int PREDICTION_BAR_SPACER_TYPE = 3;
|
||||
|
||||
/**
|
||||
* Callback for when the prediction bar spacer is bound.
|
||||
*/
|
||||
public interface PredictionBarSpacerCallbacks {
|
||||
void onBindPredictionBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewHolder for each icon.
|
||||
*/
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public View mContent;
|
||||
public boolean mIsEmptyRow;
|
||||
|
||||
public ViewHolder(View v, boolean isEmptyRow) {
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
mContent = v;
|
||||
mIsEmptyRow = isEmptyRow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,8 +72,8 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
return mAppsPerRow;
|
||||
}
|
||||
|
||||
if (mApps.getAdapterItems().get(position).isSectionHeader) {
|
||||
// Section break spans full width
|
||||
if (mApps.getAdapterItems().get(position).viewType != AppsGridAdapter.ICON_VIEW_TYPE) {
|
||||
// Both the section breaks and predictive bar span the full width
|
||||
return mAppsPerRow;
|
||||
} else {
|
||||
return 1;
|
||||
|
@ -88,7 +99,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
|
||||
DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
|
||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
boolean hasDrawnPredictedAppDivider = false;
|
||||
boolean hasDrawnPredictedAppsDivider = false;
|
||||
int childCount = parent.getChildCount();
|
||||
int lastSectionTop = 0;
|
||||
int lastSectionHeight = 0;
|
||||
|
@ -99,13 +110,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
|
||||
// Draw the divider under the predicted app
|
||||
if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppsDivider) {
|
||||
// Draw the divider under the predicted apps
|
||||
parent.getBackground().getPadding(mTmpBounds);
|
||||
int top = child.getTop() + child.getHeight();
|
||||
c.drawLine(mTmpBounds.left, top, parent.getWidth() - mTmpBounds.right, top,
|
||||
mPredictedAppsDividerPaint);
|
||||
hasDrawnPredictedAppDivider = true;
|
||||
hasDrawnPredictedAppsDivider = true;
|
||||
|
||||
} else if (grid.isPhone() && shouldDrawItemSection(holder, i, items)) {
|
||||
// At this point, we only draw sections for each section break;
|
||||
|
@ -220,9 +231,10 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
/**
|
||||
* Returns whether to draw the divider for a given child.
|
||||
*/
|
||||
private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) {
|
||||
private boolean shouldDrawItemDivider(ViewHolder holder,
|
||||
List<AlphabeticalAppsList.AdapterItem> items) {
|
||||
int pos = holder.getPosition();
|
||||
return items.get(pos).isPredictedApp;
|
||||
return items.get(pos).viewType == AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,31 +245,27 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
int pos = holder.getPosition();
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(pos);
|
||||
|
||||
// Ensure it's not an empty row
|
||||
if (holder.mIsEmptyRow) {
|
||||
return false;
|
||||
}
|
||||
// Ensure this is not a section break
|
||||
if (item.isSectionHeader) {
|
||||
return false;
|
||||
}
|
||||
// Ensure this is not a predicted app
|
||||
if (item.isPredictedApp) {
|
||||
// Ensure it's an icon
|
||||
if (item.viewType != AppsGridAdapter.ICON_VIEW_TYPE) {
|
||||
return false;
|
||||
}
|
||||
// Draw the section header for the first item in each section
|
||||
return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader);
|
||||
return (childIndex == 0) ||
|
||||
(items.get(pos - 1).viewType == AppsGridAdapter.SECTION_BREAK_VIEW_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
private Handler mHandler;
|
||||
private LayoutInflater mLayoutInflater;
|
||||
@Thunk AlphabeticalAppsList mApps;
|
||||
private GridLayoutManager mGridLayoutMgr;
|
||||
private GridSpanSizer mGridSizer;
|
||||
private GridItemDecoration mItemDecoration;
|
||||
private PredictionBarSpacerCallbacks mPredictionBarCb;
|
||||
private View.OnTouchListener mTouchListener;
|
||||
private View.OnClickListener mIconClickListener;
|
||||
private View.OnLongClickListener mIconLongClickListener;
|
||||
@Thunk int mPredictionBarHeight;
|
||||
@Thunk int mAppsPerRow;
|
||||
@Thunk boolean mIsRtl;
|
||||
private String mEmptySearchText;
|
||||
|
@ -271,11 +279,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
|
||||
|
||||
public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
|
||||
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
|
||||
View.OnLongClickListener iconLongClickListener) {
|
||||
PredictionBarSpacerCallbacks pbCb, View.OnTouchListener touchListener,
|
||||
View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
|
||||
Resources res = context.getResources();
|
||||
mHandler = new Handler();
|
||||
mApps = apps;
|
||||
mAppsPerRow = appsPerRow;
|
||||
mPredictionBarCb = pbCb;
|
||||
mGridSizer = new GridSpanSizer();
|
||||
mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL,
|
||||
false);
|
||||
|
@ -309,6 +319,13 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
mGridLayoutMgr.setSpanCount(appsPerRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prediction row height.
|
||||
*/
|
||||
public void setPredictionRowHeight(int height) {
|
||||
mPredictionBarHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether we are in RTL mode.
|
||||
*/
|
||||
|
@ -350,17 +367,24 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
switch (viewType) {
|
||||
case EMPTY_VIEW_TYPE:
|
||||
return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
|
||||
false), true /* isEmptyRow */);
|
||||
false));
|
||||
case SECTION_BREAK_VIEW_TYPE:
|
||||
return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */);
|
||||
return new ViewHolder(new View(parent.getContext()));
|
||||
case PREDICTION_BAR_SPACER_TYPE:
|
||||
// Create a view of a specific height to match the floating prediction bar
|
||||
View v = new View(parent.getContext());
|
||||
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, mPredictionBarHeight);
|
||||
v.setLayoutParams(lp);
|
||||
return new ViewHolder(v);
|
||||
case ICON_VIEW_TYPE:
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
R.layout.apps_grid_row_icon_view, parent, false);
|
||||
R.layout.apps_grid_icon_view, parent, false);
|
||||
icon.setOnTouchListener(mTouchListener);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
icon.setFocusable(true);
|
||||
return new ViewHolder(icon, false /* isEmptyRow */);
|
||||
return new ViewHolder(icon);
|
||||
default:
|
||||
throw new RuntimeException("Unexpected view type");
|
||||
}
|
||||
|
@ -374,6 +398,16 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
BubbleTextView icon = (BubbleTextView) holder.mContent;
|
||||
icon.applyFromApplicationInfo(info);
|
||||
break;
|
||||
case PREDICTION_BAR_SPACER_TYPE:
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mPredictionBarCb != null) {
|
||||
mPredictionBarCb.onBindPredictionBar();
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
case EMPTY_VIEW_TYPE:
|
||||
TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
|
||||
emptyViewText.setText(mEmptySearchText);
|
||||
|
@ -394,9 +428,9 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
|||
public int getItemViewType(int position) {
|
||||
if (mApps.hasNoFilteredResults()) {
|
||||
return EMPTY_VIEW_TYPE;
|
||||
} else if (mApps.getAdapterItems().get(position).isSectionHeader) {
|
||||
return SECTION_BREAK_VIEW_TYPE;
|
||||
}
|
||||
return ICON_VIEW_TYPE;
|
||||
|
||||
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
|
||||
return item.viewType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ public class DeviceProfile {
|
|||
int allAppsNumRows;
|
||||
int allAppsNumCols;
|
||||
int appsViewNumCols;
|
||||
int appsViewNumPredictiveCols;
|
||||
int searchBarSpaceWidthPx;
|
||||
int searchBarSpaceHeightPx;
|
||||
int pageIndicatorHeightPx;
|
||||
|
@ -411,7 +412,7 @@ public class DeviceProfile {
|
|||
|
||||
// All Apps
|
||||
allAppsCellWidthPx = allAppsIconSizePx;
|
||||
allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
|
||||
allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + allAppsIconTextSizePx;
|
||||
int maxLongEdgeCellCount =
|
||||
res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
|
||||
int maxShortEdgeCellCount =
|
||||
|
@ -440,10 +441,13 @@ public class DeviceProfile {
|
|||
int appsViewLeftMarginPx =
|
||||
res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
|
||||
int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
|
||||
int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
|
||||
int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
|
||||
(allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
|
||||
if (numCols != appsViewNumCols) {
|
||||
appsViewNumCols = numCols;
|
||||
int numPredictiveAppCols = isPhone() ? numColumns : numAppsCols;
|
||||
if ((numAppsCols != appsViewNumCols) ||
|
||||
(numPredictiveAppCols != appsViewNumPredictiveCols)) {
|
||||
appsViewNumCols = numAppsCols;
|
||||
appsViewNumPredictiveCols = numPredictiveAppCols;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue