Merge "Adding app grid layout with fastscroller." into ub-launcher3-burnaby
This commit is contained in:
@@ -37,6 +37,9 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := nano
|
||||
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
|
||||
|
||||
LOCAL_SDK_VERSION := 21
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
android-support-v4 \
|
||||
android-support-v7-recyclerview
|
||||
|
||||
LOCAL_PACKAGE_NAME := Launcher3
|
||||
#LOCAL_CERTIFICATE := shared
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/apps_view_scrollbar_thumb_color" />
|
||||
<size
|
||||
android:width="48dp"
|
||||
android:height="48dp" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/apps_view_scrollbar_thumb_color" />
|
||||
<size android:width="4dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#ffffff" />
|
||||
<corners
|
||||
android:topLeftRadius="3dp"
|
||||
android:topRightRadius="3dp" />
|
||||
</shape>
|
||||
@@ -23,12 +23,12 @@
|
||||
android:descendantFocusability="afterDescendants">
|
||||
<include
|
||||
layout="@layout/apps_list_reveal_view"
|
||||
android:layout_width="420dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/apps_container_width"
|
||||
android:layout_height="540dp"
|
||||
android:layout_gravity="center" />
|
||||
<include
|
||||
layout="@layout/apps_list_view"
|
||||
android:layout_width="420dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/apps_container_width"
|
||||
android:layout_height="540dp"
|
||||
android:layout_gravity="center" />
|
||||
</com.android.launcher3.AppsContainerView>
|
||||
@@ -13,10 +13,17 @@
|
||||
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/WorkspaceIcon.AppsCustomize"
|
||||
android:id="@+id/application_icon"
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/focusable_view_bg" />
|
||||
android:background="@drawable/focusable_view_bg"
|
||||
launcher:deferShadowGeneration="true" />
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/apps_view_row_height"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/focusable_view_bg"
|
||||
android:descendantFocusability="afterDescendants">
|
||||
<TextView
|
||||
android:id="@+id/section"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:gravity="right"
|
||||
android:textColor="#1ca195"
|
||||
android:textSize="16sp"
|
||||
android:textAllCaps="true"
|
||||
android:focusable="false" />
|
||||
</LinearLayout>
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/all_apps_transition_overlay"
|
||||
android:id="@+id/apps_view_transition_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
style="@style/WorkspaceIcon.AppsCustomize"
|
||||
android:id="@+id/application_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/focusable_view_bg"
|
||||
launcher:iconPaddingOverride="24dp"
|
||||
launcher:textSizeOverride="16dp"
|
||||
launcher:layoutHorizontal="true" />
|
||||
launcher:layoutHorizontal="true"
|
||||
launcher:deferShadowGeneration="true" />
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="16dp"
|
||||
android:gravity="left|center_vertical"
|
||||
android:textColor="#009688"
|
||||
android:textSize="24sp"
|
||||
android:textAllCaps="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textColor="@color/apps_view_section_text_color"
|
||||
android:textSize="@dimen/apps_view_section_text_size"
|
||||
android:focusable="false" />
|
||||
</LinearLayout>
|
||||
@@ -13,18 +13,41 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ListView
|
||||
<LinearLayout
|
||||
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:layout_gravity="center"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
android:orientation="vertical"
|
||||
android:elevation="15dp"
|
||||
android:background="@drawable/apps_list_bg"
|
||||
android:visibility="gone"
|
||||
android:focusable="true"
|
||||
android:descendantFocusability="afterDescendants" />
|
||||
android:visibility="gone">
|
||||
<EditText
|
||||
android:id="@+id/app_search_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:hint="@string/apps_view_search_bar_hint"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:scrollHorizontally="true"
|
||||
android:gravity="fill_horizontal"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#4c4c4c"
|
||||
android:textColorHint="#9c9c9c"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:background="@drawable/apps_list_search_bg"
|
||||
android:elevation="4dp" />
|
||||
<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:scrollbars="vertical"
|
||||
android:scrollbarThumbVertical="@drawable/apps_list_scrollbar_thumb"
|
||||
android:focusable="true"
|
||||
android:descendantFocusability="afterDescendants" />
|
||||
</LinearLayout>
|
||||
@@ -19,7 +19,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:background="#22000000"
|
||||
android:background="@drawable/apps_customize_bg"
|
||||
android:descendantFocusability="afterDescendants">
|
||||
<include
|
||||
layout="@layout/apps_list_reveal_view" />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<dimen name="app_icon_size">64dp</dimen>
|
||||
|
||||
<!-- Apps view -->
|
||||
<dimen name="apps_container_width">480dp</dimen>
|
||||
<dimen name="apps_view_row_height">76dp</dimen>
|
||||
|
||||
<!-- AppsCustomize -->
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<attr name="iconSizeOverride" format="dimension" />
|
||||
<attr name="iconPaddingOverride" format="dimension" />
|
||||
<attr name="textSizeOverride" format="dimension" />
|
||||
<attr name="deferShadowGeneration" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Page Indicator specific attributes. -->
|
||||
|
||||
@@ -36,4 +36,8 @@
|
||||
<color name="outline_color">#FFFFFFFF</color>
|
||||
<color name="widget_text_panel">#FF374248</color>
|
||||
|
||||
<!-- Apps view -->
|
||||
<color name="apps_view_scrollbar_thumb_color">#009688</color>
|
||||
<color name="apps_view_section_text_color">#009688</color>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -47,7 +47,13 @@
|
||||
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
|
||||
|
||||
<!-- Apps view -->
|
||||
<dimen name="apps_container_width">0dp</dimen>
|
||||
<dimen name="apps_grid_view_start_margin">52dp</dimen>
|
||||
<dimen name="apps_view_row_height">64dp</dimen>
|
||||
<dimen name="apps_view_section_text_size">24sp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_gutter_size">48dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_text_size">48dp</dimen>
|
||||
|
||||
<!-- AllApps/Customize/AppsCustomize -->
|
||||
<!-- The height of the tab bar - if this changes, we should update the
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
drop if there are multiple choices. [CHAR_LIMIT=35] -->
|
||||
<string name="external_drop_widget_pick_title">Choose widget to create</string>
|
||||
|
||||
<!-- Apps view -->
|
||||
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
|
||||
<string name="apps_view_search_bar_hint">Search Apps</string>
|
||||
|
||||
<!-- Folders -->
|
||||
<skip />
|
||||
<!-- Label of Folder name field in Rename folder dialog box -->
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import com.android.launcher3.compat.AlphabeticIndexCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* The alphabetically sorted list of applications.
|
||||
*/
|
||||
public class AlphabeticalAppsList {
|
||||
|
||||
/**
|
||||
* Info about a section in the alphabetic list
|
||||
*/
|
||||
public class SectionInfo {
|
||||
public String sectionName;
|
||||
public int numAppsInSection;
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter interface to limit the set of applications in the apps list.
|
||||
*/
|
||||
public interface Filter {
|
||||
public boolean retainApp(AppInfo info);
|
||||
}
|
||||
|
||||
// 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<SectionInfo> mSections = new ArrayList<>();
|
||||
private RecyclerView.Adapter mAdapter;
|
||||
private Filter mFilter;
|
||||
private AlphabeticIndexCompat mIndexer;
|
||||
|
||||
public AlphabeticalAppsList(Context context) {
|
||||
mIndexer = new AlphabeticIndexCompat(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the adapter to notify when this dataset changes.
|
||||
*/
|
||||
public void setAdapter(RecyclerView.Adapter adapter) {
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sections of all the current filtered applications.
|
||||
*/
|
||||
public List<SectionInfo> getSections() {
|
||||
return mSections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current filtered list of applications broken down into their sections.
|
||||
*/
|
||||
public List<AppInfo> getApps() {
|
||||
return mSectionedFilteredApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current filtered list of applications.
|
||||
*/
|
||||
public List<AppInfo> getAppsWithoutSectionBreaks() {
|
||||
return mFilteredApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexer for this locale.
|
||||
*/
|
||||
public AlphabeticIndexCompat getIndexer() {
|
||||
return mIndexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current filter for this list of apps.
|
||||
*/
|
||||
public void setFilter(Filter f) {
|
||||
mFilter = f;
|
||||
onAppsUpdated();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of apps.
|
||||
*/
|
||||
public void setApps(List<AppInfo> apps) {
|
||||
Collections.sort(apps, LauncherModel.getAppNameComparator());
|
||||
mApps.clear();
|
||||
mApps.addAll(apps);
|
||||
onAppsUpdated();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new apps to the list.
|
||||
*/
|
||||
public void addApps(List<AppInfo> apps) {
|
||||
// We add it in place, in alphabetical order
|
||||
for (AppInfo info : apps) {
|
||||
addApp(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates existing apps in the list
|
||||
*/
|
||||
public void updateApps(List<AppInfo> apps) {
|
||||
for (AppInfo info : apps) {
|
||||
int index = mApps.indexOf(info);
|
||||
if (index != -1) {
|
||||
mApps.set(index, info);
|
||||
onAppsUpdated();
|
||||
mAdapter.notifyItemChanged(index);
|
||||
} else {
|
||||
addApp(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes some apps from the list.
|
||||
*/
|
||||
public void removeApps(List<AppInfo> apps) {
|
||||
for (AppInfo info : apps) {
|
||||
int removeIndex = findAppByComponent(mApps, info);
|
||||
if (removeIndex != -1) {
|
||||
int sectionedIndex = mSectionedFilteredApps.indexOf(info);
|
||||
int numAppsInSection = numAppsInSection(info);
|
||||
mApps.remove(removeIndex);
|
||||
onAppsUpdated();
|
||||
if (numAppsInSection == 1) {
|
||||
// Remove the section and the icon
|
||||
mAdapter.notifyItemRemoved(sectionedIndex - 1);
|
||||
mAdapter.notifyItemRemoved(sectionedIndex - 1);
|
||||
} else {
|
||||
mAdapter.notifyItemRemoved(sectionedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of an app given a target AppInfo.
|
||||
*/
|
||||
private int findAppByComponent(List<AppInfo> apps, AppInfo targetInfo) {
|
||||
ComponentName targetComponent = targetInfo.intent.getComponent();
|
||||
int length = apps.size();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
AppInfo info = apps.get(i);
|
||||
if (info.user.equals(info.user)
|
||||
&& info.intent.getComponent().equals(targetComponent)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation to actually add an app to the alphabetic list
|
||||
*/
|
||||
private void addApp(AppInfo info) {
|
||||
Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
|
||||
int index = Collections.binarySearch(mApps, info, appNameComparator);
|
||||
if (index < 0) {
|
||||
mApps.add(-(index + 1), info);
|
||||
onAppsUpdated();
|
||||
|
||||
int sectionedIndex = mSectionedFilteredApps.indexOf(info);
|
||||
int numAppsInSection = numAppsInSection(info);
|
||||
if (numAppsInSection == 1) {
|
||||
// New section added along with icon
|
||||
mAdapter.notifyItemInserted(sectionedIndex - 1);
|
||||
mAdapter.notifyItemInserted(sectionedIndex - 1);
|
||||
} else {
|
||||
mAdapter.notifyItemInserted(sectionedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of apps in the section that the given info is in.
|
||||
*/
|
||||
private int numAppsInSection(AppInfo info) {
|
||||
int appIndex = mFilteredApps.indexOf(info);
|
||||
int appCount = 0;
|
||||
for (SectionInfo section : mSections) {
|
||||
if (appCount + section.numAppsInSection > appIndex) {
|
||||
return section.numAppsInSection;
|
||||
}
|
||||
appCount += section.numAppsInSection;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates internals when the set of apps are updated.
|
||||
*/
|
||||
private void onAppsUpdated() {
|
||||
// Recreate the filtered apps
|
||||
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);
|
||||
}
|
||||
lastSectionInfo.numAppsInSection++;
|
||||
mSectionedFilteredApps.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A RecyclerView with custom fastscroll support. This is the main container for the all apps
|
||||
* icons.
|
||||
*/
|
||||
public class AppsContainerRecyclerView extends RecyclerView
|
||||
implements RecyclerView.OnItemTouchListener {
|
||||
|
||||
private AlphabeticalAppsList mApps;
|
||||
private int mNumAppsPerRow;
|
||||
|
||||
private Drawable mFastScrollerBg;
|
||||
private boolean mDraggingFastScroller;
|
||||
private String mFastScrollSectionName;
|
||||
private Paint mFastScrollTextPaint;
|
||||
private Rect mFastScrollTextBounds = new Rect();
|
||||
private float mFastScrollAlpha;
|
||||
private int mDownX;
|
||||
private int mDownY;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
private int mGutterSize;
|
||||
|
||||
public AppsContainerRecyclerView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AppsContainerRecyclerView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
Resources res = context.getResources();
|
||||
int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size);
|
||||
mFastScrollerBg = res.getDrawable(R.drawable.apps_list_fastscroll_bg);
|
||||
mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
|
||||
mFastScrollTextPaint = new Paint();
|
||||
mFastScrollTextPaint.setColor(Color.WHITE);
|
||||
mFastScrollTextPaint.setAntiAlias(true);
|
||||
mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
|
||||
R.dimen.apps_view_fast_scroll_text_size));
|
||||
mGutterSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_gutter_size);
|
||||
setFastScrollerAlpha(getFastScrollerAlpha());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of apps in this view, used to determine the fastscroll position.
|
||||
*/
|
||||
public void setApps(AlphabeticalAppsList apps) {
|
||||
mApps = apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of apps per row in this recycler view.
|
||||
*/
|
||||
public void setNumAppsPerRow(int rowSize) {
|
||||
mNumAppsPerRow = rowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fast scroller alpha.
|
||||
*/
|
||||
public void setFastScrollerAlpha(float alpha) {
|
||||
mFastScrollAlpha = alpha;
|
||||
invalidateFastScroller();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fast scroller alpha.
|
||||
*/
|
||||
public float getFastScrollerAlpha() {
|
||||
return mFastScrollAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
addOnItemTouchListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
if (mFastScrollAlpha > 0f) {
|
||||
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
||||
LAYOUT_DIRECTION_RTL);
|
||||
Rect bgBounds = mFastScrollerBg.getBounds();
|
||||
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
||||
int x;
|
||||
if (isRtl) {
|
||||
x = getPaddingLeft() + getScrollBarSize();
|
||||
} else {
|
||||
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
|
||||
}
|
||||
int y = mLastY - bgBounds.height() / 2;
|
||||
y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
|
||||
bgBounds.height()));
|
||||
canvas.translate(x, y);
|
||||
mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
|
||||
mFastScrollerBg.draw(canvas);
|
||||
mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
|
||||
mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
|
||||
mFastScrollSectionName.length(), mFastScrollTextBounds);
|
||||
canvas.drawText(mFastScrollSectionName,
|
||||
(bgBounds.width() - mFastScrollTextBounds.width()) / 2,
|
||||
bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
|
||||
mFastScrollTextPaint);
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We intercept the touch handling only to support fast scrolling when initiated from the
|
||||
* gutter. Otherwise, we fall back to the default RecyclerView touch handling.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
|
||||
return handleTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
|
||||
handleTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touch event and determines whether to show the fast scroller (or updates it if
|
||||
* it is already showing).
|
||||
*/
|
||||
private boolean handleTouchEvent(MotionEvent ev) {
|
||||
ViewConfiguration config = ViewConfiguration.get(getContext());
|
||||
|
||||
int action = ev.getAction();
|
||||
int x = (int) ev.getX();
|
||||
int y = (int) ev.getY();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Keep track of the down positions
|
||||
mDownX = mLastX = x;
|
||||
mDownY = mLastY = y;
|
||||
stopScroll();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
// Check if we are scrolling
|
||||
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
||||
LAYOUT_DIRECTION_RTL);
|
||||
boolean isInGutter;
|
||||
if (isRtl) {
|
||||
isInGutter = mDownX < mGutterSize;
|
||||
} else {
|
||||
isInGutter = mDownX >= (getWidth() - mGutterSize);
|
||||
}
|
||||
if (!mDraggingFastScroller && isInGutter &&
|
||||
Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
mDraggingFastScroller = true;
|
||||
animateFastScrollerVisibility(true);
|
||||
}
|
||||
if (mDraggingFastScroller) {
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
|
||||
// Scroll to the right position, and update the section name
|
||||
int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
|
||||
int bottom = getHeight() - getPaddingBottom() -
|
||||
(mFastScrollerBg.getBounds().height() / 2);
|
||||
float boundedY = (float) Math.max(top, Math.min(bottom, y));
|
||||
mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
|
||||
(bottom - top));
|
||||
invalidateFastScroller();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mDraggingFastScroller = false;
|
||||
animateFastScrollerVisibility(false);
|
||||
break;
|
||||
}
|
||||
return mDraggingFastScroller;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the visibility of the fast scroller popup.
|
||||
*/
|
||||
private void animateFastScrollerVisibility(boolean visible) {
|
||||
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
|
||||
anim.setDuration(visible ? 200 : 150);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the fast scroller popup.
|
||||
*/
|
||||
private void invalidateFastScroller() {
|
||||
invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
|
||||
mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the progress (from 0..1) to the position that should be visible
|
||||
*/
|
||||
private String scrollToPositionAtProgress(float progress) {
|
||||
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
|
||||
// Get the total number of rows
|
||||
int rowCount = 0;
|
||||
for (AlphabeticalAppsList.SectionInfo info : sections) {
|
||||
int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
|
||||
rowCount += numRowsInSection;
|
||||
}
|
||||
|
||||
// Find the index of the first app in that row and scroll to that position
|
||||
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) {
|
||||
appIndex += (rowAtProgress - rowCount) * mNumAppsPerRow;
|
||||
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);
|
||||
scrollToPosition(sectionedAppIndex);
|
||||
|
||||
// Returns the section name of the row
|
||||
return mApps.getSectionNameForApp(appInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,409 +1,64 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
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.ListView;
|
||||
import android.widget.SectionIndexer;
|
||||
import android.widget.TextView;
|
||||
import com.android.launcher3.compat.AlphabeticIndexCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a row in the apps list view.
|
||||
*/
|
||||
class AppsRow {
|
||||
int sectionId;
|
||||
String sectionDescription;
|
||||
List<AppInfo> apps;
|
||||
|
||||
public AppsRow(int sId, String sc, List<AppInfo> ai) {
|
||||
sectionId = sId;
|
||||
sectionDescription = sc;
|
||||
apps = ai;
|
||||
}
|
||||
|
||||
public AppsRow(int sId, List<AppInfo> ai) {
|
||||
sectionId = sId;
|
||||
apps = ai;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface to an algorithm that generates app rows.
|
||||
*/
|
||||
interface AppRowAlgorithm {
|
||||
public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow);
|
||||
public int getIconViewLayoutId();
|
||||
public int getRowViewLayoutId();
|
||||
public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the rows in the apps list view.
|
||||
*/
|
||||
class SectionedAppsAlgorithm implements AppRowAlgorithm {
|
||||
|
||||
@Override
|
||||
public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
|
||||
List<AppsRow> rows = new ArrayList<>();
|
||||
LinkedHashMap<String, List<AppInfo>> sections = computeSectionedApps(sortedApps);
|
||||
int sectionId = 0;
|
||||
for (Map.Entry<String, List<AppInfo>> sectionEntry : sections.entrySet()) {
|
||||
String section = sectionEntry.getKey();
|
||||
List<AppInfo> apps = sectionEntry.getValue();
|
||||
int numRows = (int) Math.ceil((float) apps.size() / appsPerRow);
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
List<AppInfo> appsInRow = new ArrayList<>();
|
||||
int offset = i * appsPerRow;
|
||||
for (int j = 0; j < appsPerRow; j++) {
|
||||
if (offset + j < apps.size()) {
|
||||
appsInRow.add(apps.get(offset + j));
|
||||
}
|
||||
}
|
||||
if (i == 0) {
|
||||
rows.add(new AppsRow(sectionId, section, appsInRow));
|
||||
} else {
|
||||
rows.add(new AppsRow(sectionId, appsInRow));
|
||||
}
|
||||
}
|
||||
sectionId++;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconViewLayoutId() {
|
||||
return R.layout.apps_grid_row_icon_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowViewLayoutId() {
|
||||
return R.layout.apps_grid_row_view;
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, List<AppInfo>> computeSectionedApps(List<AppInfo> sortedApps) {
|
||||
LinkedHashMap<String, List<AppInfo>> sections = new LinkedHashMap<>();
|
||||
for (AppInfo info : sortedApps) {
|
||||
String section = getSection(info);
|
||||
List<AppInfo> sectionApps = sections.get(section);
|
||||
if (sectionApps == null) {
|
||||
sectionApps = new ArrayList<>();
|
||||
sections.put(section, sectionApps);
|
||||
}
|
||||
sectionApps.add(info);
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
|
||||
icon.applyFromApplicationInfo(info);
|
||||
}
|
||||
|
||||
private String getSection(AppInfo app) {
|
||||
return app.title.toString().substring(0, 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the rows in the apps grid view.
|
||||
*/
|
||||
class ListedAppsAlgorithm implements AppRowAlgorithm {
|
||||
|
||||
@Override
|
||||
public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
|
||||
List<AppsRow> rows = new ArrayList<>();
|
||||
int sectionId = -1;
|
||||
String prevSection = "";
|
||||
for (AppInfo info : sortedApps) {
|
||||
List<AppInfo> appsInRow = new ArrayList<>();
|
||||
appsInRow.add(info);
|
||||
String section = getSection(info);
|
||||
if (!prevSection.equals(section)) {
|
||||
prevSection = section;
|
||||
sectionId++;
|
||||
rows.add(new AppsRow(sectionId, section, appsInRow));
|
||||
} else {
|
||||
rows.add(new AppsRow(sectionId, appsInRow));
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconViewLayoutId() {
|
||||
return R.layout.apps_list_row_icon_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowViewLayoutId() {
|
||||
return R.layout.apps_list_row_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
|
||||
icon.applyFromApplicationInfo(info);
|
||||
}
|
||||
|
||||
private String getSection(AppInfo app) {
|
||||
return app.title.toString().substring(0, 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The adapter of all the apps
|
||||
*/
|
||||
class AppsListAdapter extends BaseAdapter implements SectionIndexer {
|
||||
|
||||
private LayoutInflater mLayoutInflater;
|
||||
private List<AppsRow> mAppRows = new ArrayList<>();
|
||||
private View.OnTouchListener mTouchListener;
|
||||
private View.OnClickListener mIconClickListener;
|
||||
private View.OnLongClickListener mIconLongClickListener;
|
||||
private AppRowAlgorithm mRowAlgorithm;
|
||||
private int mAppsPerRow;
|
||||
|
||||
public AppsListAdapter(Context context, View.OnTouchListener touchListener,
|
||||
View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
|
||||
mLayoutInflater = LayoutInflater.from(context);
|
||||
mTouchListener = touchListener;
|
||||
mIconClickListener = iconClickListener;
|
||||
mIconLongClickListener = iconLongClickListener;
|
||||
}
|
||||
|
||||
void setApps(List<AppsRow> apps, int appsPerRow, AppRowAlgorithm algo) {
|
||||
mAppsPerRow = appsPerRow;
|
||||
mRowAlgorithm = algo;
|
||||
mAppRows.clear();
|
||||
mAppRows.addAll(apps);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mAppRows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mAppRows.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
AppsRow info = mAppRows.get(position);
|
||||
ViewGroup row = (ViewGroup) convertView;
|
||||
if (row == null) {
|
||||
// Inflate the row and all the icon children necessary
|
||||
row = (ViewGroup) mLayoutInflater.inflate(mRowAlgorithm.getRowViewLayoutId(),
|
||||
parent, false);
|
||||
for (int i = 0; i < mAppsPerRow; i++) {
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
mRowAlgorithm.getIconViewLayoutId(), row, false);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, 1);
|
||||
lp.gravity = Gravity.CENTER_VERTICAL;
|
||||
icon.setLayoutParams(lp);
|
||||
icon.setOnTouchListener(mTouchListener);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
icon.setFocusable(true);
|
||||
row.addView(icon);
|
||||
}
|
||||
}
|
||||
// Bind the section header
|
||||
TextView tv = (TextView) row.findViewById(R.id.section);
|
||||
if (info.sectionDescription != null) {
|
||||
tv.setText(info.sectionDescription);
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
tv.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
// Bind the icons
|
||||
for (int i = 0; i < mAppsPerRow; i++) {
|
||||
BubbleTextView icon = (BubbleTextView) row.getChildAt(i + 1);
|
||||
if (i < info.apps.size()) {
|
||||
mRowAlgorithm.bindRowViewIconToInfo(icon, info.apps.get(i));
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
icon.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getSections() {
|
||||
ArrayList<Object> sections = new ArrayList<>();
|
||||
int prevSectionId = -1;
|
||||
for (AppsRow row : mAppRows) {
|
||||
if (row.sectionId != prevSectionId) {
|
||||
sections.add(row.sectionDescription.toUpperCase());
|
||||
prevSectionId = row.sectionId;
|
||||
}
|
||||
}
|
||||
return sections.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionForSection(int sectionIndex) {
|
||||
for (int i = 0; i < mAppRows.size(); i++) {
|
||||
AppsRow row = mAppRows.get(i);
|
||||
if (row.sectionId == sectionIndex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionForPosition(int position) {
|
||||
return mAppRows.get(position).sectionId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The alphabetically sorted list of applications.
|
||||
*/
|
||||
class AlphabeticalAppList {
|
||||
|
||||
/**
|
||||
* Callbacks for when this list is modified.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
public void onAppsUpdated();
|
||||
}
|
||||
|
||||
private List<AppInfo> mApps;
|
||||
private Callbacks mCb;
|
||||
|
||||
public AlphabeticalAppList(Callbacks cb) {
|
||||
mCb = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of applications.
|
||||
*/
|
||||
public List<AppInfo> getApps() {
|
||||
return mApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of apps.
|
||||
*/
|
||||
public void setApps(List<AppInfo> apps) {
|
||||
Collections.sort(apps, LauncherModel.getAppNameComparator());
|
||||
mApps = apps;
|
||||
mCb.onAppsUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new apps to the list.
|
||||
*/
|
||||
public void addApps(List<AppInfo> apps) {
|
||||
// We add it in place, in alphabetical order
|
||||
Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
|
||||
for (AppInfo info : apps) {
|
||||
// This call will return the exact index of where the item is if >= 0, or the index
|
||||
// where it should be inserted if < 0.
|
||||
int index = Collections.binarySearch(mApps, info, appNameComparator);
|
||||
if (index < 0) {
|
||||
mApps.add(-(index + 1), info);
|
||||
}
|
||||
}
|
||||
mCb.onAppsUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates existing apps in the list
|
||||
*/
|
||||
public void updateApps(List<AppInfo> apps) {
|
||||
Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
|
||||
for (AppInfo info : apps) {
|
||||
int index = mApps.indexOf(info);
|
||||
if (index != -1) {
|
||||
mApps.set(index, info);
|
||||
} else {
|
||||
index = Collections.binarySearch(mApps, info, appNameComparator);
|
||||
if (index < 0) {
|
||||
mApps.add(-(index + 1), info);
|
||||
}
|
||||
}
|
||||
}
|
||||
mCb.onAppsUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes some apps from the list.
|
||||
*/
|
||||
public void removeApps(List<AppInfo> apps) {
|
||||
for (AppInfo info : apps) {
|
||||
int removeIndex = findAppByComponent(mApps, info);
|
||||
if (removeIndex != -1) {
|
||||
mApps.remove(removeIndex);
|
||||
}
|
||||
}
|
||||
mCb.onAppsUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of an app given a target AppInfo.
|
||||
*/
|
||||
private int findAppByComponent(List<AppInfo> apps, AppInfo targetInfo) {
|
||||
ComponentName targetComponent = targetInfo.intent.getComponent();
|
||||
int length = apps.size();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
AppInfo info = apps.get(i);
|
||||
if (info.user.equals(info.user)
|
||||
&& info.intent.getComponent().equals(targetComponent)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The all apps list view container.
|
||||
*/
|
||||
public class AppsContainerView extends FrameLayout implements DragSource, View.OnTouchListener,
|
||||
View.OnLongClickListener, Insettable, AlphabeticalAppList.Callbacks {
|
||||
View.OnLongClickListener, Insettable, TextWatcher, TextView.OnEditorActionListener,
|
||||
LauncherTransitionable {
|
||||
|
||||
static final int GRID_LAYOUT = 0;
|
||||
static final int LIST_LAYOUT = 1;
|
||||
static final int USE_LAYOUT = LIST_LAYOUT;
|
||||
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
|
||||
|
||||
private static final int GRID_LAYOUT = 0;
|
||||
private static final int LIST_LAYOUT = 1;
|
||||
private static final int USE_LAYOUT = GRID_LAYOUT;
|
||||
|
||||
private Launcher mLauncher;
|
||||
private AppRowAlgorithm mAppRowsAlgorithm;
|
||||
private AppsListAdapter mAdapter;
|
||||
private AlphabeticalAppList mApps;
|
||||
private ListView mList;
|
||||
private int mAppsRowSize;
|
||||
private AlphabeticalAppsList mApps;
|
||||
private RecyclerView.Adapter mAdapter;
|
||||
private RecyclerView.LayoutManager mLayoutManager;
|
||||
private RecyclerView.ItemDecoration mItemDecoration;
|
||||
private AppsContainerRecyclerView mAppsListView;
|
||||
private EditText mSearchBar;
|
||||
private int mNumAppsPerRow;
|
||||
private Point mLastTouchDownPos = new Point();
|
||||
private Rect mPadding = new Rect();
|
||||
private int mContentMarginStart;
|
||||
|
||||
public AppsContainerView(Context context) {
|
||||
this(context, null);
|
||||
@@ -423,15 +78,22 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
|
||||
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
||||
|
||||
mLauncher = (Launcher) context;
|
||||
mApps = new AlphabeticalAppsList(context);
|
||||
if (USE_LAYOUT == GRID_LAYOUT) {
|
||||
mAppRowsAlgorithm = new SectionedAppsAlgorithm();
|
||||
mAppsRowSize = grid.allAppsRowsSize;
|
||||
mNumAppsPerRow = grid.appsViewNumCols;
|
||||
AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
|
||||
mLauncher, this);
|
||||
mLayoutManager = adapter.getLayoutManager(context);
|
||||
mItemDecoration = adapter.getItemDecoration();
|
||||
mAdapter = adapter;
|
||||
mContentMarginStart = adapter.getContentMarginStart();
|
||||
} else if (USE_LAYOUT == LIST_LAYOUT) {
|
||||
mAppRowsAlgorithm = new ListedAppsAlgorithm();
|
||||
mAppsRowSize = 1;
|
||||
mNumAppsPerRow = 1;
|
||||
AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
|
||||
mLayoutManager = adapter.getLayoutManager(context);
|
||||
mAdapter = adapter;
|
||||
}
|
||||
mAdapter = new AppsListAdapter(context, this, mLauncher, this);
|
||||
mApps = new AlphabeticalAppList(this);
|
||||
mApps.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -466,7 +128,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
|
||||
* Scrolls this list view to the top.
|
||||
*/
|
||||
public void scrollToTop() {
|
||||
mList.scrollTo(0, 0);
|
||||
mAppsListView.scrollToPosition(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,23 +142,37 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
|
||||
* Returns the reveal view used for the launcher transitions.
|
||||
*/
|
||||
public View getRevealView() {
|
||||
return findViewById(R.id.all_apps_transition_overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppsUpdated() {
|
||||
List<AppsRow> rows = mAppRowsAlgorithm.computeAppRows(mApps.getApps(), mAppsRowSize);
|
||||
mAdapter.setApps(rows, mAppsRowSize, mAppRowsAlgorithm);
|
||||
return findViewById(R.id.apps_view_transition_overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mList = (ListView) findViewById(R.id.apps_list);
|
||||
mList.setFastScrollEnabled(true);
|
||||
mList.setFastScrollAlwaysVisible(true);
|
||||
mList.setItemsCanFocus(true);
|
||||
mList.setAdapter(mAdapter);
|
||||
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
|
||||
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
||||
LAYOUT_DIRECTION_RTL);
|
||||
if (USE_LAYOUT == GRID_LAYOUT) {
|
||||
((AppsGridAdapter) mAdapter).setRtl(isRtl);
|
||||
}
|
||||
mSearchBar = (EditText) findViewById(R.id.app_search_box);
|
||||
mSearchBar.addTextChangedListener(this);
|
||||
mSearchBar.setOnEditorActionListener(this);
|
||||
mAppsListView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
|
||||
mAppsListView.setApps(mApps);
|
||||
mAppsListView.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mAppsListView.setLayoutManager(mLayoutManager);
|
||||
mAppsListView.setAdapter(mAdapter);
|
||||
mAppsListView.setHasFixedSize(true);
|
||||
if (isRtl) {
|
||||
mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(),
|
||||
mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingBottom());
|
||||
} else {
|
||||
mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, mAppsListView.getPaddingTop(),
|
||||
mAppsListView.getPaddingRight(), mAppsListView.getPaddingBottom());
|
||||
}
|
||||
if (mItemDecoration != null) {
|
||||
mAppsListView.addItemDecoration(mItemDecoration);
|
||||
}
|
||||
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
|
||||
getPaddingBottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -574,7 +250,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, boolean success) {
|
||||
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
|
||||
boolean success) {
|
||||
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
|
||||
!(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
|
||||
// Exit spring loaded mode if we have not successfully dropped or have not handled the
|
||||
@@ -606,4 +283,83 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
|
||||
d.deferDragViewCleanupPostAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if (s.toString().isEmpty()) {
|
||||
mApps.setFilter(null);
|
||||
} else {
|
||||
final AlphabeticIndexCompat indexer = mApps.getIndexer();
|
||||
final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
|
||||
mApps.setFilter(new AlphabeticalAppsList.Filter() {
|
||||
@Override
|
||||
public boolean retainApp(AppInfo info) {
|
||||
String title = info.title.toString();
|
||||
String sectionName = mApps.getSectionNameForApp(info);
|
||||
return sectionName.toLowerCase().contains(filterText) ||
|
||||
title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
mSearchBar.clearFocus();
|
||||
mAppsListView.getChildAt(apps.indexOf(appsWithoutSections.get(0))).performClick();
|
||||
InputMethodManager imm = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
|
||||
if (!toWorkspace) {
|
||||
// Disable the focus so that the search bar doesn't get focus
|
||||
mSearchBar.setFocusableInTouchMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherTransitionStep(Launcher l, float t) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
|
||||
if (toWorkspace) {
|
||||
// Clear the search bar
|
||||
mSearchBar.setText("");
|
||||
} else {
|
||||
mSearchBar.setFocusableInTouchMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.android.launcher3.compat.AlphabeticIndexCompat;
|
||||
|
||||
|
||||
/**
|
||||
* The grid view adapter of all the apps.
|
||||
*/
|
||||
class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
||||
|
||||
public static final String TAG = "AppsGridAdapter";
|
||||
|
||||
private static final int SECTION_BREAK_VIEW_TYPE = 0;
|
||||
private static final int ICON_VIEW_TYPE = 1;
|
||||
|
||||
/**
|
||||
* ViewHolder for each icon.
|
||||
*/
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public View mContent;
|
||||
public boolean mIsSectionRow;
|
||||
|
||||
public ViewHolder(View v, boolean isSectionRow) {
|
||||
super(v);
|
||||
mContent = v;
|
||||
mIsSectionRow = isSectionRow;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to size the grid items.
|
||||
*/
|
||||
public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
AppInfo info = mApps.getApps().get(position);
|
||||
if (info == AlphabeticalAppsList.SECTION_BREAK_INFO) {
|
||||
return mAppsPerRow;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to draw the section headers
|
||||
*/
|
||||
public class GridItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
@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 (mApps.getApps().get(holder.getPosition() - 1) ==
|
||||
AlphabeticalAppsList.SECTION_BREAK_INFO) {
|
||||
// Draw at the parent
|
||||
AppInfo info = mApps.getApps().get(holder.getPosition());
|
||||
String section = mApps.getSectionNameForApp(info);
|
||||
mSectionTextPaint.getTextBounds(section, 0, section.length(),
|
||||
mTmpBounds);
|
||||
if (mIsRtl) {
|
||||
c.drawText(section, parent.getWidth() - mStartMargin +
|
||||
(mStartMargin - mTmpBounds.width()) / 2,
|
||||
child.getTop() + (2 * child.getPaddingTop()) +
|
||||
mTmpBounds.height(), mSectionTextPaint);
|
||||
} else {
|
||||
c.drawText(section, (mStartMargin - mTmpBounds.width()) / 2,
|
||||
child.getTop() + (2 * child.getPaddingTop()) +
|
||||
mTmpBounds.height(), mSectionTextPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private LayoutInflater mLayoutInflater;
|
||||
private AlphabeticalAppsList mApps;
|
||||
private GridSpanSizer mGridSizer;
|
||||
private GridItemDecoration mItemDecoration;
|
||||
private View.OnTouchListener mTouchListener;
|
||||
private View.OnClickListener mIconClickListener;
|
||||
private View.OnLongClickListener mIconLongClickListener;
|
||||
private int mAppsPerRow;
|
||||
private boolean mIsRtl;
|
||||
|
||||
// Section drawing
|
||||
private int mStartMargin;
|
||||
private Paint mSectionTextPaint;
|
||||
private Rect mTmpBounds = new Rect();
|
||||
|
||||
|
||||
public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
|
||||
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
|
||||
View.OnLongClickListener iconLongClickListener) {
|
||||
Resources res = context.getResources();
|
||||
mApps = apps;
|
||||
mAppsPerRow = appsPerRow;
|
||||
mGridSizer = new GridSpanSizer();
|
||||
mItemDecoration = new GridItemDecoration();
|
||||
mLayoutInflater = LayoutInflater.from(context);
|
||||
mTouchListener = touchListener;
|
||||
mIconClickListener = iconClickListener;
|
||||
mIconLongClickListener = iconLongClickListener;
|
||||
mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
|
||||
mSectionTextPaint = new Paint();
|
||||
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
|
||||
R.dimen.apps_view_section_text_size));
|
||||
mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
|
||||
mSectionTextPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether we are in RTL mode.
|
||||
*/
|
||||
public void setRtl(boolean rtl) {
|
||||
mIsRtl = rtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid layout manager.
|
||||
*/
|
||||
public GridLayoutManager getLayoutManager(Context context) {
|
||||
GridLayoutManager layoutMgr = new GridLayoutManager(context, mAppsPerRow,
|
||||
GridLayoutManager.VERTICAL, false);
|
||||
layoutMgr.setSpanSizeLookup(mGridSizer);
|
||||
return layoutMgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item decoration for the recycler view.
|
||||
*/
|
||||
public RecyclerView.ItemDecoration getItemDecoration() {
|
||||
return mItemDecoration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the left padding for the recycler view.
|
||||
*/
|
||||
public int getContentMarginStart() {
|
||||
return mStartMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case SECTION_BREAK_VIEW_TYPE:
|
||||
return new ViewHolder(new View(parent.getContext()), true);
|
||||
case ICON_VIEW_TYPE:
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
R.layout.apps_grid_row_icon_view, parent, false);
|
||||
icon.setOnTouchListener(mTouchListener);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
icon.setFocusable(true);
|
||||
return new ViewHolder(icon, false);
|
||||
default:
|
||||
throw new RuntimeException("Unexpected view type");
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mApps.getApps().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
|
||||
return SECTION_BREAK_VIEW_TYPE;
|
||||
}
|
||||
return ICON_VIEW_TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
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.
|
||||
*/
|
||||
class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
|
||||
|
||||
/**
|
||||
* ViewHolder for each row.
|
||||
*/
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public View mContent;
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
mContent = v;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SECTION_BREAK_VIEW_TYPE = 0;
|
||||
private static final int ICON_VIEW_TYPE = 1;
|
||||
|
||||
private LayoutInflater mLayoutInflater;
|
||||
private AlphabeticalAppsList mApps;
|
||||
private View.OnTouchListener mTouchListener;
|
||||
private View.OnClickListener mIconClickListener;
|
||||
private View.OnLongClickListener mIconLongClickListener;
|
||||
|
||||
public AppsListAdapter(Context context, AlphabeticalAppsList apps,
|
||||
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
|
||||
View.OnLongClickListener iconLongClickListener) {
|
||||
mApps = apps;
|
||||
mLayoutInflater = LayoutInflater.from(context);
|
||||
mTouchListener = touchListener;
|
||||
mIconClickListener = iconClickListener;
|
||||
mIconLongClickListener = iconLongClickListener;
|
||||
}
|
||||
|
||||
public RecyclerView.LayoutManager getLayoutManager(Context context) {
|
||||
return new LinearLayoutManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case SECTION_BREAK_VIEW_TYPE:
|
||||
return new ViewHolder(new View(parent.getContext()));
|
||||
case ICON_VIEW_TYPE:
|
||||
// Inflate the row and all the icon children necessary
|
||||
ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view,
|
||||
parent, false);
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
R.layout.apps_list_row_icon_view, row, false);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, 1);
|
||||
lp.gravity = Gravity.CENTER_VERTICAL;
|
||||
icon.setLayoutParams(lp);
|
||||
icon.setOnTouchListener(mTouchListener);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
icon.setFocusable(true);
|
||||
row.addView(icon);
|
||||
return new ViewHolder(row);
|
||||
default:
|
||||
throw new RuntimeException("Unexpected view type");
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mApps.getApps().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
|
||||
return SECTION_BREAK_VIEW_TYPE;
|
||||
}
|
||||
return ICON_VIEW_TYPE;
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ public class BubbleTextView extends TextView {
|
||||
|
||||
private float mSlop;
|
||||
|
||||
private final boolean mDeferShadowGenerationOnTouch;
|
||||
private final boolean mCustomShadowsEnabled;
|
||||
private final boolean mLayoutHorizontal;
|
||||
private final int mIconSize;
|
||||
@@ -96,6 +97,8 @@ public class BubbleTextView extends TextView {
|
||||
grid.iconDrawablePaddingPx);
|
||||
mTextSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_textSizeOverride,
|
||||
grid.allAppsIconTextSizePx);
|
||||
mDeferShadowGenerationOnTouch =
|
||||
a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
|
||||
a.recycle();
|
||||
|
||||
if (mCustomShadowsEnabled) {
|
||||
@@ -218,7 +221,7 @@ public class BubbleTextView extends TextView {
|
||||
// So that the pressed outline is visible immediately on setStayPressed(),
|
||||
// we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
|
||||
// to create it)
|
||||
if (mPressedBackground == null) {
|
||||
if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
|
||||
mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
|
||||
}
|
||||
|
||||
@@ -247,6 +250,10 @@ public class BubbleTextView extends TextView {
|
||||
mStayPressed = stayPressed;
|
||||
if (!stayPressed) {
|
||||
mPressedBackground = null;
|
||||
} else {
|
||||
if (mPressedBackground == null) {
|
||||
mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Only show the shadow effect when persistent pressed state is set.
|
||||
|
||||
@@ -122,8 +122,7 @@ public class DeviceProfile {
|
||||
int hotseatAllAppsRank;
|
||||
int allAppsNumRows;
|
||||
int allAppsNumCols;
|
||||
// TODO(winsonc): to be used with the grid layout
|
||||
int allAppsRowsSize;
|
||||
int appsViewNumCols;
|
||||
int searchBarSpaceWidthPx;
|
||||
int searchBarSpaceHeightPx;
|
||||
int pageIndicatorHeightPx;
|
||||
@@ -365,7 +364,7 @@ public class DeviceProfile {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIconSize(float scale, int drawablePadding, Resources resources,
|
||||
private void updateIconSize(float scale, int drawablePadding, Resources res,
|
||||
DisplayMetrics dm) {
|
||||
iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
|
||||
iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
|
||||
@@ -374,9 +373,9 @@ public class DeviceProfile {
|
||||
|
||||
// Search Bar
|
||||
searchBarSpaceWidthPx = Math.min(widthPx,
|
||||
resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
|
||||
res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
|
||||
searchBarSpaceHeightPx = getSearchBarTopOffset()
|
||||
+ resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
|
||||
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
|
||||
|
||||
// Calculate the actual text height
|
||||
Paint textPaint = new Paint();
|
||||
@@ -384,7 +383,7 @@ public class DeviceProfile {
|
||||
FontMetrics fm = textPaint.getFontMetrics();
|
||||
cellWidthPx = iconSizePx;
|
||||
cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
|
||||
final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
|
||||
final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
|
||||
dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
|
||||
|
||||
// Hotseat
|
||||
@@ -402,11 +401,11 @@ public class DeviceProfile {
|
||||
allAppsCellWidthPx = allAppsIconSizePx;
|
||||
allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
|
||||
int maxLongEdgeCellCount =
|
||||
resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
|
||||
res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
|
||||
int maxShortEdgeCellCount =
|
||||
resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
|
||||
res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
|
||||
int minEdgeCellCount =
|
||||
resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
|
||||
res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
|
||||
int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
|
||||
int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
|
||||
|
||||
@@ -417,10 +416,17 @@ public class DeviceProfile {
|
||||
allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
|
||||
(allAppsCellHeightPx + allAppsCellPaddingPx);
|
||||
allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
|
||||
allAppsNumCols = (availableWidthPx) /
|
||||
(allAppsCellWidthPx + allAppsCellPaddingPx);
|
||||
allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
|
||||
allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
|
||||
}
|
||||
|
||||
int appsContainerViewPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
|
||||
int appsViewLeftMarginPx =
|
||||
res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
|
||||
int availableAppsWidthPx = (appsContainerViewPx > 0) ? appsContainerViewPx :
|
||||
availableWidthPx;
|
||||
appsViewNumCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
|
||||
(allAppsCellWidthPx + allAppsCellPaddingPx);
|
||||
}
|
||||
|
||||
void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.android.launcher3.compat;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Fallback class to support Alphabetic indexing if not supported by the framework.
|
||||
* TODO(winsonc): disable for non-english locales
|
||||
*/
|
||||
class BaseAlphabeticIndex {
|
||||
|
||||
private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
|
||||
private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
|
||||
|
||||
public BaseAlphabeticIndex() {}
|
||||
|
||||
/**
|
||||
* Sets the max number of the label buckets in this index.
|
||||
*/
|
||||
public void setMaxLabelCount(int count) {
|
||||
// Not currently supported
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the bucket in which the given string should appear.
|
||||
*/
|
||||
public int getBucketIndex(String s) {
|
||||
if (s.isEmpty()) {
|
||||
return UNKNOWN_BUCKET_INDEX;
|
||||
}
|
||||
int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
|
||||
if (index != -1) {
|
||||
return index;
|
||||
}
|
||||
return UNKNOWN_BUCKET_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for the bucket at the given index (as returned by getBucketIndex).
|
||||
*/
|
||||
public String getBucketLabel(int index) {
|
||||
return BUCKETS.substring(index, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
|
||||
*/
|
||||
public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
|
||||
|
||||
private Object mAlphabeticIndex;
|
||||
private Method mAddLabelsMethod;
|
||||
private Method mSetMaxLabelCountMethod;
|
||||
private Method mGetBucketIndexMethod;
|
||||
private Method mGetBucketLabelMethod;
|
||||
private boolean mHasValidAlphabeticIndex;
|
||||
|
||||
public AlphabeticIndexCompat(Context context) {
|
||||
super();
|
||||
try {
|
||||
Locale curLocale = context.getResources().getConfiguration().locale;
|
||||
Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
|
||||
Constructor ctor = clazz.getConstructor(Locale.class);
|
||||
mAddLabelsMethod = clazz.getDeclaredMethod("addLabels", Locale.class);
|
||||
mSetMaxLabelCountMethod = clazz.getDeclaredMethod("setMaxLabelCount", int.class);
|
||||
mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
|
||||
mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
|
||||
mAlphabeticIndex = ctor.newInstance(curLocale);
|
||||
try {
|
||||
// Ensure we always have some base English locale buckets
|
||||
if (!curLocale.getLanguage().equals(new Locale("en").getLanguage())) {
|
||||
mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mHasValidAlphabeticIndex = true;
|
||||
} catch (Exception e) {
|
||||
mHasValidAlphabeticIndex = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max number of the label buckets in this index.
|
||||
* (ICU 51 default is 99)
|
||||
*/
|
||||
public void setMaxLabelCount(int count) {
|
||||
if (mHasValidAlphabeticIndex) {
|
||||
try {
|
||||
mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
super.setMaxLabelCount(count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (mHasValidAlphabeticIndex) {
|
||||
try {
|
||||
return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return super.getBucketIndex(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for the bucket at the given index (as returned by getBucketIndex).
|
||||
*/
|
||||
public String getBucketLabel(int index) {
|
||||
if (mHasValidAlphabeticIndex) {
|
||||
try {
|
||||
return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return super.getBucketLabel(index);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user