Refactoring all apps search to support external search bar.

- Adding support for an external search bar that can be used
  to search a container view.  This adds a new interface
  AllAppsSearchController which manages the external search
  bar.  Each controller will have its own search implementation
  which means that we no longer need a common AppSearchManager
  interface.
- Removing elevation controller as we no longer have a builtin
  search bar in all apps
- Refactoring container view insets so that they behave
  the same in all containers.
- Refactoring apps view to ensure that we only update the number
  of columns with the available width
- Cleaning up LauncherCallbacks interface

Bug: 20127840
Bug: 21494973

Change-Id: I710b8e18196961d77d8a29f0c345531d480936fe
This commit is contained in:
Winson Chung 2015-06-04 17:18:17 -07:00
parent e89cf793ab
commit ef7f874a88
29 changed files with 1087 additions and 1064 deletions

View File

@ -14,10 +14,7 @@
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/quantum_panel_bg_color" />
<corners
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/quantum_panel"
android:insetTop="@dimen/container_bounds_minus_quantum_panel_padding_inset"
android:insetBottom="@dimen/container_bounds_minus_quantum_panel_padding_inset" />

View File

@ -1,33 +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.
-->
<com.android.launcher3.allapps.AllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/all_apps_container_inset"
android:descendantFocusability="afterDescendants">
<include
layout="@layout/all_apps_reveal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<include
layout="@layout/all_apps_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</com.android.launcher3.allapps.AllAppsContainerView>

View File

@ -21,15 +21,37 @@
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants">
<include
layout="@layout/all_apps_reveal"
android:orientation="vertical">
<!-- Both android:focusable and android:focusableInTouchMode are needed for
the view to get focus change events. -->
<FrameLayout
android:id="@+id/search_box_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<include
layout="@layout/all_apps_container"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:visibility="gone" />
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:id="@+id/all_apps_reveal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="false"
android:elevation="2dp"
android:visibility="invisible" />
<include
layout="@layout/all_apps_container"
android:id="@+id/all_apps_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
</com.android.launcher3.allapps.AllAppsContainerView>

View File

@ -14,95 +14,35 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Both android:focusable and android:focusableInTouchMode are needed for
the view to get focus change events. -->
<com.android.launcher3.allapps.AllAppsRecyclerViewContainerView
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:elevation="15dp"
android:focusableInTouchMode="true"
android:visibility="gone" >
android:focusable="true"
android:focusableInTouchMode="true">
<com.android.launcher3.allapps.AllAppsRecyclerView
android:id="@+id/apps_list_view"
android:id="@+id/collection"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/all_apps_search_bar_height"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true" />
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/all_apps_search_bar_height"
android:paddingBottom="@dimen/all_apps_prediction_bar_bottom_padding"
android:paddingTop="@dimen/all_apps_prediction_bar_top_bottom_padding"
android:paddingBottom="@dimen/all_apps_prediction_bar_top_bottom_padding"
android:orientation="horizontal"
android:descendantFocusability="afterDescendants"
android:focusable="true"
android:descendantFocusability="afterDescendants"
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/all_apps_search_bar_height"
android:background="@drawable/all_apps_search_bg" >
<LinearLayout
android:id="@+id/app_search_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="invisible" >
<ImageView
android:id="@+id/dismiss_search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:contentDescription="@string/all_apps_button_label"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_arrow_back_grey" />
<com.android.launcher3.allapps.AllAppsSearchEditView
android:id="@+id/apps_search_box"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:focusableInTouchMode="true"
android:gravity="fill_horizontal"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionDone|flagNoExtractUi"
android:maxLines="1"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingTop="16dp"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="#4c4c4c"
android:textColorHint="#9c9c9c"
android:textSize="16sp" />
</LinearLayout>
<ImageView
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:contentDescription="@string/all_apps_search_bar_hint"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_search_grey" />
</FrameLayout>
</com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>

View File

@ -1,24 +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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view_transition_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:elevation="2dp"
android:visibility="invisible"
android:focusable="false" />

View File

@ -0,0 +1,72 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/all_apps_search_bg" >
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="start|center_vertical"
android:orientation="horizontal"
android:visibility="invisible" >
<ImageView
android:id="@+id/dismiss_search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:contentDescription="@string/all_apps_button_label"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_arrow_back_grey" />
<com.android.launcher3.allapps.AllAppsSearchEditView
android:id="@+id/search_box"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:focusableInTouchMode="true"
android:gravity="fill_horizontal|center_vertical"
android:hint="@string/all_apps_search_bar_hint"
android:inputType="text|textNoSuggestions|textCapWords"
android:imeOptions="actionDone|flagNoExtractUi"
android:maxLines="1"
android:paddingLeft="8dp"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="#4c4c4c"
android:textColorHint="#9c9c9c"
android:textSize="16sp" />
</LinearLayout>
<ImageView
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:contentDescription="@string/all_apps_search_bar_hint"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_search_grey" />
</FrameLayout>

View File

@ -21,25 +21,29 @@
android:id="@+id/widgets_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/widget_container_inset"
android:paddingBottom="@dimen/widget_container_inset"
android:orientation="vertical"
android:descendantFocusability="afterDescendants">
<FrameLayout
android:id="@+id/widgets_reveal_view"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:elevation="2dp"
android:focusable="false"
android:visibility="invisible" />
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/widgets_reveal_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="false"
android:elevation="2dp"
android:visibility="invisible" />
<com.android.launcher3.widget.WidgetsRecyclerView
android:id="@+id/widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/quantum_panel_dark"
android:layout_gravity="center"
android:elevation="15dp"
android:visibility="gone" />
</FrameLayout>
</com.android.launcher3.widget.WidgetsContainerView>

View File

@ -16,7 +16,6 @@
<resources>
<!-- All Apps -->
<dimen name="all_apps_container_inset">18dp</dimen>
<dimen name="all_apps_grid_view_start_margin">0dp</dimen>
<dimen name="all_apps_grid_section_text_size">26sp</dimen>
<dimen name="all_apps_icon_top_bottom_padding">12dp</dimen>

View File

@ -51,17 +51,18 @@
<!-- All Apps -->
<!-- Note: This needs to match the fixed insets for the search box. -->
<dimen name="container_fixed_bounds_inset">8dp</dimen>
<dimen name="container_bounds_inset">8dp</dimen>
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
<dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
<dimen name="all_apps_container_inset">8dp</dimen>
<dimen name="all_apps_grid_view_start_margin">56dp</dimen>
<dimen name="all_apps_grid_section_y_offset">8dp</dimen>
<dimen name="all_apps_grid_section_text_size">24sp</dimen>
<dimen name="all_apps_search_bar_height">52dp</dimen>
<dimen name="all_apps_search_bar_height">48dp</dimen>
<dimen name="all_apps_search_bar_prediction_bar_padding">8dp</dimen>
<dimen name="all_apps_icon_top_bottom_padding">8dp</dimen>
<dimen name="all_apps_icon_width_gap">24dp</dimen>
<dimen name="all_apps_prediction_bar_bottom_padding">16dp</dimen>
<dimen name="all_apps_prediction_bar_top_bottom_padding">16dp</dimen>
<dimen name="all_apps_fast_scroll_bar_width">4dp</dimen>
<dimen name="all_apps_fast_scroll_scrubber_touch_inset">-16dp</dimen>

View File

@ -19,16 +19,25 @@ package com.android.launcher3;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
/**
* A base container view, which supports resizing.
*/
public class BaseContainerView extends FrameLayout implements Insettable {
public abstract class BaseContainerView extends LinearLayout implements Insettable {
protected Rect mInsets = new Rect();
protected Rect mFixedBounds = new Rect();
protected int mFixedBoundsContainerInset;
// The window insets
private Rect mInsets = new Rect();
// The bounds of the search bar. Only the left, top, right are used to inset the
// search bar and the height is determined by the measurement of the layout
private Rect mSearchBarBounds = new Rect();
// The bounds of the container
protected Rect mContentBounds = new Rect();
// The padding to apply to the container to achieve the bounds
protected Rect mContentPadding = new Rect();
// The inset to apply to the edges and between the search bar and the container
private int mContainerBoundsInset;
private boolean mHasSearchBar;
public BaseContainerView(Context context) {
this(context, null);
@ -40,62 +49,73 @@ public class BaseContainerView extends FrameLayout implements Insettable {
public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
R.dimen.container_fixed_bounds_inset);
mContainerBoundsInset = getResources().getDimensionPixelSize(R.dimen.container_bounds_inset);
}
@Override
final public void setInsets(Rect insets) {
mInsets.set(insets);
onUpdateBackgrounds();
onUpdatePaddings();
updateBackgroundAndPaddings();
}
protected void setHasSearchBar() {
mHasSearchBar = true;
}
/**
* Sets the fixed bounds for this container view.
* Sets the search bar bounds for this container view to match.
*/
final public void setFixedBounds(Rect fixedBounds) {
if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
mFixedBounds.set(fixedBounds);
if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
mFixedBounds.top = mInsets.top;
mFixedBounds.bottom = mInsets.bottom;
}
// To ensure that the child RecyclerView has the full width to handle touches right to
// the edge of the screen, we only apply the top and bottom padding to the bounds
mFixedBounds.top += mFixedBoundsContainerInset;
mFixedBounds.bottom += mFixedBoundsContainerInset;
onFixedBoundsUpdated();
}
final public void setSearchBarBounds(Rect bounds) {
mSearchBarBounds.set(bounds);
// Post the updates since they can trigger a relayout, and this call can be triggered from
// a layout pass itself.
post(new Runnable() {
@Override
public void run() {
onUpdateBackgrounds();
onUpdatePaddings();
updateBackgroundAndPaddings();
}
});
}
/**
* Update the UI in response to a change in the fixed bounds.
* Update the backgrounds and padding in response to a change in the bounds or insets.
*/
protected void onFixedBoundsUpdated() {
// Do nothing
protected void updateBackgroundAndPaddings() {
Rect padding;
Rect searchBarBounds = new Rect(mSearchBarBounds);
if (mSearchBarBounds.isEmpty()) {
// Use the normal bounds
padding = new Rect(mInsets.left + mContainerBoundsInset,
(mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
mInsets.right + mContainerBoundsInset,
mInsets.bottom + mContainerBoundsInset);
// Special case -- we have the search bar, but no specific bounds, so just give it
// the inset bounds without a height.
searchBarBounds.set(mInsets.left + mContainerBoundsInset,
mInsets.top + mContainerBoundsInset,
getMeasuredWidth() - (mInsets.right + mContainerBoundsInset), 0);
} else {
// Use the search bounds, if there is a search bar, the bounds will contain
// the offsets for the insets so we can ignore that
padding = new Rect(mSearchBarBounds.left,
(mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
getMeasuredWidth() - mSearchBarBounds.right,
mInsets.bottom + mContainerBoundsInset);
}
if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
mContentPadding.set(padding);
mContentBounds.set(padding.left, padding.top,
getMeasuredWidth() - padding.right,
getMeasuredHeight() - padding.bottom);
mSearchBarBounds.set(searchBarBounds);
onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
}
}
/**
* Update the paddings in response to a change in the bounds or insets.
* To be implemented by container views to update themselves when the bounds changes.
*/
protected void onUpdatePaddings() {
// Do nothing
}
/**
* Update the backgrounds in response to a change in the bounds or insets.
*/
protected void onUpdateBackgrounds() {
// Do nothing
}
protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding);
}

View File

@ -89,9 +89,7 @@ public class BaseRecyclerView extends RecyclerView
private int mLastY;
private int mScrollbarWidth;
private int mScrollbarInset;
private Rect mBackgroundPadding = new Rect();
protected Rect mBackgroundPadding = new Rect();
public BaseRecyclerView(Context context) {
this(context, null);
@ -230,6 +228,10 @@ public class BaseRecyclerView extends RecyclerView
return false;
}
public void updateBackgroundPadding(Rect padding) {
mBackgroundPadding.set(padding);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@ -340,9 +342,10 @@ public class BaseRecyclerView extends RecyclerView
// Calculate the position for the fast scroller popup
Rect bgBounds = mFastScrollerBg.getBounds();
if (Utilities.isRtl(getResources())) {
x = mBackgroundPadding.left + getScrollBarSize();
x = mBackgroundPadding.left + (2 * getScrollbarWidth());
} else {
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
x = getWidth() - mBackgroundPadding.right - (2 * getScrollbarWidth()) -
bgBounds.width();
}
y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -

View File

@ -223,26 +223,22 @@ public class DeviceProfile {
folderCellHeightPx = cellHeightPx + edgeMarginPx;
folderBackgroundOffset = -edgeMarginPx;
folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
updateAppsViewNumCols(res, 0);
}
public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
/**
* @param recyclerViewWidth the available width of the AllAppsRecyclerView
*/
public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) {
int appsViewLeftMarginPx =
res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
int allAppsCellWidthGap =
res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
(allAppsIconSizePx + allAppsCellWidthGap);
int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols);
if ((numAppsCols != allAppsNumCols) ||
(numPredictiveAppCols != allAppsNumPredictiveCols)) {
allAppsNumCols = numAppsCols;
allAppsNumPredictiveCols = numPredictiveAppCols;
return true;
}
return false;
allAppsNumCols = numAppsCols;
allAppsNumPredictiveCols = numPredictiveAppCols;
}
/** Returns the search bar top offset */

View File

@ -99,7 +99,7 @@ import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.PagedView.PageSwitchListener;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AppSearchManager;
import com.android.launcher3.allapps.AllAppsSearchBarController;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
@ -138,9 +138,6 @@ public class Launcher extends Activity
static final String TAG = "Launcher";
static final boolean LOGD = false;
// Temporary flag
static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
static final boolean PROFILE_STARTUP = false;
static final boolean DEBUG_WIDGETS = true;
static final boolean DEBUG_STRICT_MODE = false;
@ -573,32 +570,6 @@ public class Launcher extends Activity
public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
mLauncherCallbacks = callbacks;
mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
@Override
public void onAllAppsBoundsChanged(Rect bounds) {
if (LOGD) {
Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
}
mAppsView.setFixedBounds(bounds);
mWidgetsView.setFixedBounds(bounds);
}
@Override
public void dismissAllApps() {
if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
// Dismiss All Apps if we aren't already paused/invisible
if (!mPaused) {
showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
}
}
}
@Override
public void setSearchManager(AppSearchManager manager) {
mAppsView.setSearchManager(manager);
}
});
mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() {
private boolean mImportanceStored = false;
private int mWorkspaceImportanceForAccessibility =
@ -638,6 +609,14 @@ public class Launcher extends Activity
}
}
/**
* Updates the bounds of all the overlays to match the new fixed bounds.
*/
public void updateOverlayBounds(Rect newBounds) {
mAppsView.setSearchBarBounds(newBounds);
mWidgetsView.setSearchBarBounds(newBounds);
}
/** To be overridden by subclasses to hint to Launcher that we have custom content */
protected boolean hasCustomContentToLeft() {
if (mLauncherCallbacks != null) {
@ -1012,16 +991,6 @@ public class Launcher extends Activity
}
mOnResumeState = State.NONE;
// Restore the apps state if we are in all apps
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
// Otherwise, notify the callbacks if we are in all apps mode
if (mState == State.APPS) {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
}
}
}
// Background was set to gradient in onPause(), restore to transparent if in all apps.
setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_GRADIENT
: WORKSPACE_BACKGROUND_TRANSPARENT);
@ -1167,17 +1136,20 @@ public class Launcher extends Activity
* Updates launcher to the available space that AllApps can take so as not to overlap with
* any other views.
*/
@Deprecated
public void onAllAppsBoundsChanged(Rect bounds);
/**
* Called to dismiss all apps if it is showing.
*/
@Deprecated
public void dismissAllApps();
/**
* Sets the search manager to be used for app search.
*/
public void setSearchManager(AppSearchManager manager);
@Deprecated
public void setSearchManager(Object manager);
}
public interface LauncherSearchCallbacks {
@ -1463,14 +1435,14 @@ public class Launcher extends Activity
mSearchDropTargetBar = (SearchDropTargetBar)
mDragLayer.findViewById(R.id.search_drop_target_bar);
// Setup Apps
// Setup Apps and Widgets
mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
if (isAllAppsSearchOverridden()) {
mAppsView.hideHeaderBar();
}
// Setup AppsCustomize
mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
} else {
mAppsView.setSearchBarController(mAppsView.newDefaultAppSearchController());
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
dragController.setDragScoller(mWorkspace);
@ -2866,17 +2838,8 @@ public class Launcher extends Activity
public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
// Only update the interacting state if we are transitioning to/from a view with an
// overlay
boolean fromStateWithOverlay;
boolean toStateWithOverlay;
if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
fromStateWithOverlay = fromState != Workspace.State.NORMAL;
toStateWithOverlay = toState != Workspace.State.NORMAL;
} else {
fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
fromState != Workspace.State.NORMAL_HIDDEN;
toStateWithOverlay = toState != Workspace.State.NORMAL &&
toState != Workspace.State.NORMAL_HIDDEN;
}
boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL;
boolean toStateWithOverlay = toState != Workspace.State.NORMAL;
if (toStateWithOverlay) {
onInteractionBegin();
} else if (fromStateWithOverlay) {
@ -3320,21 +3283,19 @@ public class Launcher extends Activity
}
public void showWorkspace(boolean animated) {
showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null,
true);
showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
}
public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
onCompleteRunnable, true);
onCompleteRunnable);
}
protected void showWorkspace(int snapToPage, boolean animated) {
showWorkspace(snapToPage, animated, null, true);
showWorkspace(snapToPage, animated, null);
}
void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable,
boolean notifyLauncherCallbacks) {
void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) {
boolean changed = mState != State.WORKSPACE ||
mWorkspace.getState() != Workspace.State.NORMAL;
if (changed) {
@ -3366,12 +3327,6 @@ public class Launcher extends Activity
// Send an accessibility event to announce the context change
getWindow().getDecorView()
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (notifyLauncherCallbacks) {
// Dismiss all apps when the workspace is shown
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsHidden();
}
}
}
}
@ -3431,10 +3386,7 @@ public class Launcher extends Activity
}
if (toState == State.APPS) {
mStateTransitionAnimation.startAnimationToAllApps(animated);
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
}
mStateTransitionAnimation.startAnimationToAllApps(mState, animated);
} else {
mStateTransitionAnimation.startAnimationToWidgets(animated);
}
@ -3458,9 +3410,10 @@ public class Launcher extends Activity
* new state.
*/
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
boolean animated, HashMap<View, Integer> layerViews) {
boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
Workspace.State fromState = mWorkspace.getState();
Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated,
hasOverlaySearchBar, layerViews);
updateInteraction(fromState, toState);
return anim;
}
@ -3482,14 +3435,6 @@ public class Launcher extends Activity
final Runnable onCompleteRunnable) {
if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
if (successfulDrop) {
// We need to trigger all apps hidden to notify search to update itself before the
// delayed call to showWorkspace below
if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsHidden();
}
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
@ -4473,20 +4418,6 @@ public class Launcher extends Activity
return null;
}
/**
* Returns whether the launcher callbacks overrides search in all apps.
*/
@Thunk boolean isAllAppsSearchOverridden() {
if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
return false;
}
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.overrideAllAppsSearch();
}
return false;
}
private boolean shouldRunFirstRunActivity() {
return !ActivityManager.isRunningInTestHarness() &&
!mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);

View File

@ -7,6 +7,7 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.allapps.AllAppsSearchBarController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -51,12 +52,9 @@ public interface LauncherCallbacks {
public void onLauncherProviderChange();
public void finishBindingItems(final boolean upgradePath);
public void onClickAllAppsButton(View v);
public void onAllAppsShown();
public void onAllAppsHidden();
public void bindAllApplications(ArrayList<AppInfo> apps);
public void onClickFolderIcon(View v);
public void onClickAppShortcut(View v);
@Deprecated
public void onClickPagedViewIcon(View v);
public void onClickWallpaperPicker(View v);
@ -89,10 +87,11 @@ public interface LauncherCallbacks {
public View getIntroScreen();
public boolean shouldMoveToDefaultScreenOnHomeIntent();
public boolean hasSettings();
@Deprecated
public ComponentName getWallpaperPickerComponent();
public boolean overrideWallpaperDimensions();
public boolean isLauncherPreinstalled();
public boolean overrideAllAppsSearch();
public AllAppsSearchBarController getAllAppsSearchBarController();
public List<ComponentName> getPredictedApps();
/**
@ -113,14 +112,6 @@ public interface LauncherCallbacks {
public Launcher.LauncherOverlay setLauncherOverlayView(InsettableFrameLayout container,
Launcher.LauncherOverlayCallbacks callbacks);
/**
* Sets the callbacks to allow any extensions to callback to the launcher.
*
* @param callbacks A set of callbacks to the Launcher, is actually a LauncherAppsCallback, but
* for implementation purposes is passed around as an object.
*/
public void setLauncherAppsCallback(Object callbacks);
/**
* Sets the callbacks to allow reacting the actions of search overlays of the launcher.
*

View File

@ -11,6 +11,7 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.allapps.AllAppsSearchBarController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -124,14 +125,6 @@ public class LauncherExtension extends Launcher {
public void onClickAllAppsButton(View v) {
}
@Override
public void onAllAppsShown() {
}
@Override
public void onAllAppsHidden() {
}
@Override
public void bindAllApplications(ArrayList<AppInfo> apps) {
}
@ -255,8 +248,8 @@ public class LauncherExtension extends Launcher {
}
@Override
public boolean overrideAllAppsSearch() {
return false;
public AllAppsSearchBarController getAllAppsSearchBarController() {
return null;
}
@Override
@ -284,11 +277,6 @@ public class LauncherExtension extends Launcher {
return mLauncherOverlay;
}
@Override
public void setLauncherAppsCallback(Object callbacks) {
// Do nothing
}
@Override
public void setLauncherSearchCallback(Object callbacks) {
// Do nothing

View File

@ -129,7 +129,7 @@ public class LauncherStateTransitionAnimation {
/**
* Starts an animation to the apps view.
*/
public void startAnimationToAllApps(final boolean animated) {
public void startAnimationToAllApps(final Launcher.State fromState, final boolean animated) {
final AllAppsContainerView toView = mLauncher.getAppsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
private int[] mAllAppsToPanelDelta;
@ -137,7 +137,6 @@ public class LauncherStateTransitionAnimation {
@Override
public void onRevealViewVisible(View revealView, View contentView,
View allAppsButtonView) {
toView.setBackground(null);
// Get the y delta between the center of the page and the center of the all apps
// button
mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
@ -173,9 +172,9 @@ public class LauncherStateTransitionAnimation {
};
}
};
// Only animate the search bar if animating from spring loaded mode back to all apps
startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(),
toView.getRevealView(), animated,
!mLauncher.isAllAppsSearchOverridden() /* hideSearchBar */, cb);
toView.getRevealView(), toView.getSearchBarView(), animated, true, cb);
}
/**
@ -188,7 +187,6 @@ public class LauncherStateTransitionAnimation {
@Override
public void onRevealViewVisible(View revealView, View contentView,
View allAppsButtonView) {
revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
}
@Override
public float getMaterialRevealViewFinalAlpha(View revealView) {
@ -200,8 +198,8 @@ public class LauncherStateTransitionAnimation {
}
};
startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView,
toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */,
cb);
toView.getContentView(), toView.getRevealView(), null, animated,
true /* hideSearchBar */, cb);
}
/**
@ -217,10 +215,10 @@ public class LauncherStateTransitionAnimation {
}
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, toWorkspacePage,
startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage,
animated, onCompleteRunnable);
} else {
startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, toWorkspacePage,
startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage,
animated, onCompleteRunnable);
}
}
@ -230,8 +228,9 @@ public class LauncherStateTransitionAnimation {
*/
@SuppressLint("NewApi")
private void startAnimationToOverlay(final Workspace.State toWorkspaceState, final View toView,
final View contentView, final View revealView, final boolean animated,
final boolean hideSearchBar, final PrivateTransitionCallbacks pCb) {
final View contentView, final View revealView, final View overlaySearchBarView,
final boolean animated, final boolean hideSearchBar,
final PrivateTransitionCallbacks pCb) {
final Resources res = mLauncher.getResources();
final boolean material = Utilities.isLmpOrAbove();
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
@ -252,7 +251,7 @@ public class LauncherStateTransitionAnimation {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
animated, layerViews);
animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
if (animated && initialized) {
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@ -297,6 +296,15 @@ public class LauncherStateTransitionAnimation {
layerViews.put(revealView, BUILD_AND_SET_LAYER);
mStateAnimation.play(panelAlphaAndDrift);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(0f);
ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f);
searchBarAlpha.setDuration(100);
searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
mStateAnimation.play(searchBarAlpha);
}
// Setup the animation for the content view
contentView.setVisibility(View.VISIBLE);
contentView.setAlpha(0f);
@ -426,9 +434,8 @@ public class LauncherStateTransitionAnimation {
/**
* Starts and animation to the workspace from the apps view.
*/
private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState,
final Workspace.State toWorkspaceState, final int toWorkspacePage,
final boolean animated, final Runnable onCompleteRunnable) {
private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
AllAppsContainerView appsView = mLauncher.getAppsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
int[] mAllAppsToPanelDelta;
@ -479,24 +486,23 @@ public class LauncherStateTransitionAnimation {
};
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, appsView,
appsView.getContentView(), appsView.getRevealView(), animated, onCompleteRunnable,
cb);
appsView.getContentView(), appsView.getRevealView(), appsView.getSearchBarView(),
animated, onCompleteRunnable, cb);
}
/**
* Starts and animation to the workspace from the widgets view.
*/
private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState,
final Workspace.State toWorkspaceState, final int toWorkspacePage,
final boolean animated, final Runnable onCompleteRunnable) {
private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
final Resources res = mLauncher.getResources();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@Override
public void onRevealViewVisible(View revealView, View contentView,
View allAppsButtonView) {
revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
}
@Override
public float getMaterialRevealViewFinalYDrift(View revealView) {
@ -518,7 +524,7 @@ public class LauncherStateTransitionAnimation {
}
};
startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView,
widgetsView.getContentView(), widgetsView.getRevealView(), animated,
widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
onCompleteRunnable, cb);
}
@ -527,8 +533,8 @@ public class LauncherStateTransitionAnimation {
*/
private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
final int toWorkspacePage, final View fromView, final View contentView,
final View revealView, final boolean animated, final Runnable onCompleteRunnable,
final PrivateTransitionCallbacks pCb) {
final View revealView, final View overlaySearchBarView, final boolean animated,
final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) {
final Resources res = mLauncher.getResources();
final boolean material = Utilities.isLmpOrAbove();
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
@ -549,7 +555,8 @@ public class LauncherStateTransitionAnimation {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
toWorkspacePage, animated, layerViews);
toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
layerViews);
if (animated && initialized) {
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@ -633,6 +640,16 @@ public class LauncherStateTransitionAnimation {
itemsAlpha.setInterpolator(decelerateInterpolator);
mStateAnimation.play(itemsAlpha);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(1f);
ObjectAnimator searchAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 1f, 0f);
searchAlpha.setDuration(material ? 100 : 150);
searchAlpha.setInterpolator(decelerateInterpolator);
searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
mStateAnimation.play(searchAlpha);
}
if (material) {
// Animate the all apps button
float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
@ -681,6 +698,9 @@ public class LauncherStateTransitionAnimation {
contentView.setTranslationY(0);
contentView.setAlpha(1);
}
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(1f);
}
// This can hold unnecessary references to views.
mStateAnimation = null;

View File

@ -16,13 +16,10 @@
package com.android.launcher3;
import android.view.View;
/**
* An interface to get callbacks during a launcher transition.
*/
public interface LauncherTransitionable {
View getContent();
void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
void onLauncherTransitionStep(Launcher l, float t);

View File

@ -1998,10 +1998,10 @@ public class Workspace extends PagedView
* to that new state.
*/
public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
HashMap<View, Integer> layerViews) {
boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
// Create the animation to the new state
Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(getState(),
toState, toPage, animated, layerViews);
toState, toPage, animated, hasOverlaySearchBar, layerViews);
// Update the current state
mState = toState;
@ -2100,11 +2100,6 @@ public class Workspace extends PagedView
}
}
@Override
public View getContent() {
return this;
}
/**
* Returns the drawable for the given text view.
*/

View File

@ -121,6 +121,52 @@ class ZoomInInterpolator implements TimeInterpolator {
}
}
/**
* Stores the transition states for convenience.
*/
class TransitionStates {
// Raw states
final boolean oldStateIsNormal;
final boolean oldStateIsSpringLoaded;
final boolean oldStateIsNormalHidden;
final boolean oldStateIsOverviewHidden;
final boolean oldStateIsOverview;
final boolean stateIsNormal;
final boolean stateIsSpringLoaded;
final boolean stateIsNormalHidden;
final boolean stateIsOverviewHidden;
final boolean stateIsOverview;
// Convenience members
final boolean workspaceToAllApps;
final boolean overviewToAllApps;
final boolean allAppsToWorkspace;
final boolean workspaceToOverview;
final boolean overviewToWorkspace;
public TransitionStates(final Workspace.State fromState, final Workspace.State toState) {
oldStateIsNormal = (fromState == Workspace.State.NORMAL);
oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
stateIsNormal = (toState == Workspace.State.NORMAL);
stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
stateIsOverview = (toState == Workspace.State.OVERVIEW);
workspaceToOverview = (oldStateIsNormal && stateIsOverview);
workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
}
}
/**
* Manages the animations between each of the workspace states.
*/
@ -175,9 +221,17 @@ public class WorkspaceStateTransitionAnimation {
}
public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
int toPage, boolean animated,
HashMap<View, Integer> layerViews) {
getAnimation(fromState, toState, toPage, animated, layerViews);
int toPage, boolean animated, boolean hasOverlaySearchBar,
HashMap<View, Integer> layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
final boolean accessibilityEnabled = am.isEnabled();
TransitionStates states = new TransitionStates(fromState, toState);
int duration = getAnimationDuration(states);
animateWorkspace(states, toPage, animated, duration, layerViews,
accessibilityEnabled);
animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
accessibilityEnabled);
return mStateAnimator;
}
@ -185,16 +239,38 @@ public class WorkspaceStateTransitionAnimation {
return mNewScale;
}
/**
* Reinitializes the arrays that we need for the animations on each page.
*/
private void reinitializeAnimationArrays() {
final int childCount = mWorkspace.getChildCount();
if (mLastChildCount == childCount) return;
mOldBackgroundAlphas = new float[childCount];
mOldAlphas = new float[childCount];
mNewBackgroundAlphas = new float[childCount];
mNewAlphas = new float[childCount];
}
/**
* Returns the proper animation duration for a transition.
*/
private int getAnimationDuration(TransitionStates states) {
if (states.workspaceToAllApps || states.overviewToAllApps) {
return mAllAppsTransitionTime;
} else if (states.workspaceToOverview || states.overviewToWorkspace) {
return mOverviewTransitionTime;
} else {
return mOverlayTransitionTime;
}
}
/**
* Starts a transition animation for the workspace.
*/
private void getAnimation(final Workspace.State fromState, final Workspace.State toState,
int toPage, final boolean animated,
final HashMap<View, Integer> layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
final boolean accessibilityEnabled = am.isEnabled();
private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated,
final int duration, final HashMap<View, Integer> layerViews,
final boolean accessibilityEnabled) {
// Reinitialize animation arrays for the current workspace state
reinitializeAnimationArrays();
@ -205,32 +281,12 @@ public class WorkspaceStateTransitionAnimation {
}
// Update the workspace state
final boolean oldStateIsNormal = (fromState == Workspace.State.NORMAL);
final boolean oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
final boolean oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
final boolean oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
final boolean oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
final boolean stateIsNormal = (toState == Workspace.State.NORMAL);
final boolean stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
final boolean stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
final boolean stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
final boolean stateIsOverview = (toState == Workspace.State.OVERVIEW);
final boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
final boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
final boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
final boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
final boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
// We keep the search bar visible on the workspace and in AllApps now
boolean showSearchBar = stateIsNormal ||
(mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
1.0f : 0f;
float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ?
1f : 0f;
float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ?
mWorkspace.getOverviewModeTranslationY() : 0;
final int childCount = mWorkspace.getChildCount();
@ -238,29 +294,20 @@ public class WorkspaceStateTransitionAnimation {
mNewScale = 1.0f;
if (oldStateIsOverview) {
if (states.oldStateIsOverview) {
mWorkspace.disableFreeScroll();
} else if (stateIsOverview) {
} else if (states.stateIsOverview) {
mWorkspace.enableFreeScroll();
}
if (!stateIsNormal) {
if (stateIsSpringLoaded) {
if (!states.stateIsNormal) {
if (states.stateIsSpringLoaded) {
mNewScale = mSpringLoadedShrinkFactor;
} else if (stateIsOverview || stateIsOverviewHidden) {
} else if (states.stateIsOverview || states.stateIsOverviewHidden) {
mNewScale = mOverviewModeShrinkFactor;
}
}
final int duration;
if (workspaceToAllApps || overviewToAllApps) {
duration = mAllAppsTransitionTime;
} else if (workspaceToOverview || overviewToWorkspace) {
duration = mOverviewTransitionTime;
} else {
duration = mOverlayTransitionTime;
}
if (toPage == SCROLL_TO_CURRENT_PAGE) {
toPage = mWorkspace.getPageNearestToCenterOfScreen();
}
@ -271,9 +318,9 @@ public class WorkspaceStateTransitionAnimation {
boolean isCurrentPage = (i == toPage);
float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
float finalAlpha;
if (stateIsNormalHidden || stateIsOverviewHidden) {
if (states.stateIsNormalHidden || states.stateIsOverviewHidden) {
finalAlpha = 0f;
} else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
} else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
} else {
finalAlpha = 1f;
@ -282,8 +329,8 @@ public class WorkspaceStateTransitionAnimation {
// If we are animating to/from the small state, then hide the side pages and fade the
// current page in
if (!mWorkspace.isSwitchingState()) {
if (workspaceToAllApps || allAppsToWorkspace) {
if (allAppsToWorkspace && isCurrentPage) {
if (states.workspaceToAllApps || states.allAppsToWorkspace) {
if (states.allAppsToWorkspace && isCurrentPage) {
initialAlpha = 0f;
} else if (!isCurrentPage) {
initialAlpha = finalAlpha = 0f;
@ -303,7 +350,6 @@ public class WorkspaceStateTransitionAnimation {
}
}
final View searchBar = mLauncher.getOrCreateQsbBar();
final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
final View pageIndicator = mWorkspace.getPageIndicator();
@ -345,7 +391,7 @@ public class WorkspaceStateTransitionAnimation {
}
}
}
Animator pageIndicatorAlpha = null;
Animator pageIndicatorAlpha;
if (pageIndicator != null) {
pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
.alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
@ -380,11 +426,11 @@ public class WorkspaceStateTransitionAnimation {
overviewPanelAlpha.withLayer();
}
if (workspaceToOverview) {
if (states.workspaceToOverview) {
pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
overviewPanelAlpha.setInterpolator(null);
} else if (overviewToWorkspace) {
} else if (states.overviewToWorkspace) {
pageIndicatorAlpha.setInterpolator(null);
hotseatAlpha.setInterpolator(null);
overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
@ -394,26 +440,6 @@ public class WorkspaceStateTransitionAnimation {
pageIndicatorAlpha.setDuration(duration);
hotseatAlpha.setDuration(duration);
// TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
// bar has no idea that it is hidden, and this has no idea what state the bar is
// actually in.
if (searchBar != null) {
LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
.alpha(finalSearchBarAlpha);
searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, accessibilityEnabled));
searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
if (layerViews != null) {
// If layerViews is not null, we add these views, and indicate that
// the caller can manage layer state.
layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
} else {
// Otherwise let the animator handle layer management.
searchBarAlpha.withLayer();
}
searchBarAlpha.setDuration(duration);
mStateAnimator.play(searchBarAlpha);
}
mStateAnimator.play(overviewPanelAlpha);
mStateAnimator.play(hotseatAlpha);
mStateAnimator.play(pageIndicatorAlpha);
@ -437,10 +463,6 @@ public class WorkspaceStateTransitionAnimation {
pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled);
}
if (searchBar != null) {
searchBar.setAlpha(finalSearchBarAlpha);
AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
}
mWorkspace.updateCustomContentVisibility();
mWorkspace.setScaleX(mNewScale);
mWorkspace.setScaleY(mNewScale);
@ -452,7 +474,7 @@ public class WorkspaceStateTransitionAnimation {
}
}
if (stateIsNormal) {
if (states.stateIsNormal) {
animateBackgroundGradient(0f, animated);
} else {
animateBackgroundGradient(mWorkspaceScrimAlpha, animated);
@ -460,16 +482,69 @@ public class WorkspaceStateTransitionAnimation {
}
/**
* Reinitializes the arrays that we need for the animations on each page.
* Coordinates with the workspace animation to animate the search bar.
*
* TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
* bar has no idea that it is hidden, and this has no idea what state the bar is
* actually in.
*/
private void reinitializeAnimationArrays() {
final int childCount = mWorkspace.getChildCount();
if (mLastChildCount == childCount) return;
private void animateSearchBar(TransitionStates states, boolean animated, int duration,
boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
final boolean accessibilityEnabled) {
mOldBackgroundAlphas = new float[childCount];
mOldAlphas = new float[childCount];
mNewBackgroundAlphas = new float[childCount];
mNewAlphas = new float[childCount];
// The search bar is only visible in the workspace
final View searchBar = mLauncher.getOrCreateQsbBar();
if (searchBar != null) {
final boolean searchBarWillBeShown = states.stateIsNormal;
final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
if (animated) {
if (hasOverlaySearchBar) {
// If there is an overlay search bar, then we will coordinate with it.
mStateAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// If we are transitioning to a visible search bar, show it immediately
// and let the overlay search bar has faded out
if (searchBarWillBeShown) {
searchBar.setAlpha(finalSearchBarAlpha);
AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
}
}
@Override
public void onAnimationEnd(Animator animation) {
// If we are transitioning to a hidden search bar, hide it only after
// the overlay search bar has faded in
if (!searchBarWillBeShown) {
searchBar.setAlpha(finalSearchBarAlpha);
AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
}
}
});
} else {
// Otherwise, we can just do the normal animation
LauncherViewPropertyAnimator searchBarAlpha =
new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
accessibilityEnabled));
searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
if (layerViews != null) {
// If layerViews is not null, we add these views, and indicate that
// the caller can manage layer state.
layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
} else {
// Otherwise let the animator handle layer management.
searchBarAlpha.withLayer();
}
searchBarAlpha.setDuration(duration);
mStateAnimator.play(searchBarAlpha);
}
} else {
// Set the search bar state immediately
searchBar.setAlpha(finalSearchBarAlpha);
AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
}
}
}
/**

View File

@ -20,17 +20,15 @@ import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -39,11 +37,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
import com.android.launcher3.BubbleTextView;
@ -54,7 +49,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Folder;
import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherTransitionable;
@ -62,131 +56,24 @@ import com.android.launcher3.R;
import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AppSearchManager.AppSearchResultCallback;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
import java.util.List;
/**
* Interface for controlling the header elevation in response to RecyclerView scroll.
*/
interface HeaderElevationController {
void onScroll(int scrollY);
void updateBackgroundPadding(Drawable bg);
void disable();
}
/**
* Implementation of the header elevation mechanism for pre-L devices. It simulates elevation
* by drawing a gradient under the header bar.
*/
final class HeaderElevationControllerV16 implements HeaderElevationController {
private final View mShadow;
private final float mScrollToElevation;
private final Rect mTmpRect = new Rect();
public HeaderElevationControllerV16(View header) {
Resources res = header.getContext().getResources();
mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
mShadow = new View(header.getContext());
mShadow.setBackground(new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000}));
mShadow.setAlpha(0);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
((ViewGroup) header.getParent()).addView(mShadow, lp);
}
@Override
public void onScroll(int scrollY) {
float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
mScrollToElevation;
mShadow.setAlpha(elevationPct);
}
@Override
public void updateBackgroundPadding(Drawable bg) {
bg.getPadding(mTmpRect);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams();
lp.leftMargin = mTmpRect.left;
lp.rightMargin = mTmpRect.right;
mShadow.requestLayout();
}
@Override
public void disable() {
ViewGroup parent = (ViewGroup) mShadow.getParent();
if (parent != null) {
parent.removeView(mShadow);
}
}
}
/**
* Implementation of the header elevation mechanism for L+ devices, which makes use of the native
* view elevation.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
final class HeaderElevationControllerVL implements HeaderElevationController {
private final View mHeader;
private final float mMaxElevation;
private final float mScrollToElevation;
public HeaderElevationControllerVL(View header) {
mHeader = header;
Resources res = header.getContext().getResources();
mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
}
@Override
public void onScroll(int scrollY) {
float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
mScrollToElevation;
float newElevation = mMaxElevation * elevationPct;
if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
mHeader.setElevation(newElevation);
}
}
@Override
public void updateBackgroundPadding(Drawable bg) {
// Do nothing, the background padding on the header view is already applied
}
@Override
public void disable() { }
}
/**
* The all apps view container.
*/
public class AllAppsContainerView extends BaseContainerView implements DragSource, Insettable,
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
AlphabeticalAppsList.AdapterChangedCallback, AllAppsGridAdapter.PredictionBarSpacerCallbacks,
View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
ViewTreeObserver.OnPreDrawListener, AppSearchResultCallback, Stats.LaunchSourceProvider {
public class AllAppsContainerView extends BaseContainerView implements DragSource,
LauncherTransitionable, AlphabeticalAppsList.AdapterChangedCallback,
AllAppsGridAdapter.PredictionBarSpacerCallbacks, View.OnTouchListener,
View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener,
AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider {
public static final boolean GRID_MERGE_SECTIONS = true;
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
private static final boolean DYNAMIC_HEADER_ELEVATION = true;
private static final boolean DISMISS_SEARCH_ON_BACK = true;
private static final int FADE_IN_DURATION = 175;
private static final int FADE_OUT_DURATION = 100;
private static final int SEARCH_TRANSLATION_X_DP = 18;
@Thunk Launcher mLauncher;
@Thunk AlphabeticalAppsList mApps;
private LayoutInflater mLayoutInflater;
@ -194,16 +81,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerView.ItemDecoration mItemDecoration;
@Thunk FrameLayout mContentView;
@Thunk View mContent;
@Thunk View mContainerView;
@Thunk View mRevealView;
@Thunk AllAppsRecyclerView mAppsRecyclerView;
@Thunk ViewGroup mPredictionBarView;
private View mHeaderView;
@Thunk View mSearchBarContainerView;
private View mSearchButtonView;
private View mDismissSearchButtonView;
@Thunk AllAppsSearchEditView mSearchBarEditView;
private HeaderElevationController mElevationController;
@Thunk AllAppsSearchBarController mSearchBarController;
private ViewGroup mSearchBarContainerView;
private View mSearchBarView;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
@ -213,18 +98,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private final Point mIconLastTouchPos = new Point();
// This coordinate is used to proxy click and long-click events to the prediction bar icons
private final Point mPredictionIconTouchDownPos = new Point();
private int mContentMarginStart;
// Normal container insets
private int mContainerInset;
private int mPredictionBarHeight;
private int mLastRecyclerViewScrollPos = -1;
@Thunk boolean mFocusPredictionBarOnFirstBind;
private SpannableStringBuilder mSearchQueryBuilder = null;
private CheckLongPressHelper mPredictionIconCheckForLongPress;
private View mPredictionIconUnderTouch;
private AppSearchManager mSearchManager;
public AllAppsContainerView(Context context) {
this(context, null);
}
@ -238,30 +121,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
Resources res = context.getResources();
mLauncher = (Launcher) context;
mLayoutInflater = LayoutInflater.from(context);
DeviceProfile grid = mLauncher.getDeviceProfile();
mContainerInset = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding));
2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
mLayoutInflater = LayoutInflater.from(context);
mNumAppsPerRow = grid.allAppsNumCols;
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
mApps = new AlphabeticalAppsList(context, mNumAppsPerRow, mNumPredictedAppsPerRow);
mApps = new AlphabeticalAppsList(context);
mApps.setAdapterChangedCallback(this);
mAdapter = new AllAppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this);
mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mAdapter.setPredictionRowHeight(mPredictionBarHeight);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
mContentMarginStart = mAdapter.getContentMarginStart();
mApps.setAdapter(mAdapter);
mSearchManager = mApps.newSimpleAppSearchManager();
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
}
/**
@ -285,11 +162,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mApps.addApps(apps);
}
public void setSearchManager(AppSearchManager searchManager) {
mSearchManager.cancel(true);
mSearchManager = searchManager;
}
/**
* Updates existing apps in the list
*/
@ -305,13 +177,23 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
/**
* Hides the header bar
* Sets the search bar that shows above the a-z list.
*/
public void hideHeaderBar() {
mHeaderView.setVisibility(View.GONE);
mElevationController.disable();
onUpdateBackgrounds();
onUpdatePaddings();
public void setSearchBarController(AllAppsSearchBarController searchController) {
if (mSearchBarController != null) {
throw new RuntimeException("Expected search bar controller to only be set once");
}
mSearchBarController = searchController;
mSearchBarController.initialize(mApps, this);
// Add the new search view to the layout
View searchBarView = searchController.getView(mSearchBarContainerView);
mSearchBarContainerView.addView(searchBarView);
mSearchBarContainerView.setVisibility(View.VISIBLE);
mSearchBarView = searchBarView;
setHasSearchBar();
updateBackgroundAndPaddings();
}
/**
@ -325,28 +207,43 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
* Returns the content view used for the launcher transitions.
*/
public View getContentView() {
return mContentView;
return mContainerView;
}
/**
* Returns the all apps search view.
*/
public View getSearchBarView() {
return mSearchBarView;
}
/**
* Returns the reveal view used for the launcher transitions.
*/
public View getRevealView() {
return findViewById(R.id.apps_view_transition_overlay);
return mRevealView;
}
/**
* Returns an new instance of the default app search controller.
*/
public AllAppsSearchBarController newDefaultAppSearchController() {
return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
boolean isRtl = Utilities.isRtl(getResources());
mAdapter.setRtl(isRtl);
mContent = findViewById(R.id.content);
// 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 = (FrameLayout) findViewById(R.id.apps_list);
mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
// This is a focus listener that proxies focus from a view into the list view. This is to
// work around the search box from getting first focus and showing the cursor.
View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (v == mContentView && hasFocus) {
if (hasFocus) {
if (!mApps.getPredictedApps().isEmpty()) {
// If the prediction bar is going to be bound, then defer focusing until
// it is first bound
@ -360,52 +257,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
}
});
};
mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container);
mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener);
mContainerView = findViewById(R.id.all_apps_container);
mContainerView.setOnFocusChangeListener(focusProxyListener);
mRevealView = findViewById(R.id.all_apps_reveal);
// Fix the header view elevation if not dynamically calculating it
mHeaderView = findViewById(R.id.header);
mHeaderView.setOnClickListener(this);
mElevationController = Utilities.isLmpOrAbove() ?
new HeaderElevationControllerVL(mHeaderView) :
new HeaderElevationControllerV16(mHeaderView);
if (!DYNAMIC_HEADER_ELEVATION) {
mElevationController.onScroll(getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_scroll_to_elevation));
}
// 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);
mDismissSearchButtonView.setOnClickListener(this);
mSearchBarEditView = (AllAppsSearchEditView) findViewById(R.id.apps_search_box);
if (mSearchBarEditView != null) {
mSearchBarEditView.addTextChangedListener(this);
mSearchBarEditView.setOnEditorActionListener(this);
if (DISMISS_SEARCH_ON_BACK) {
mSearchBarEditView.setOnBackKeyListener(
new AllAppsSearchEditView.OnBackKeyListener() {
@Override
public void onBackKey() {
// Only hide the search field if there is no query, or if there
// are no filtered results
String query = Utilities.trim(
mSearchBarEditView.getEditableText().toString());
if (query.isEmpty() || mApps.hasNoFilteredResults()) {
hideSearchField(true, true);
}
}
});
}
}
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
// Load the all apps recycler view
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.collection);
mAppsRecyclerView.setApps(mApps);
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
@ -413,8 +274,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
if (mItemDecoration != null) {
mAppsRecyclerView.addItemDecoration(mItemDecoration);
}
onUpdateBackgrounds();
onUpdatePaddings();
// Fix the prediction bar height
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
lp.height = mPredictionBarHeight;
updateBackgroundAndPaddings();
}
@Override
public void onBoundsChanged(Rect newBounds) {
mLauncher.updateOverlayBounds(newBounds);
}
@Override
@ -422,6 +293,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
updatePredictionBarVisibility();
List<AppInfo> predictedApps = mApps.getPredictedApps();
// Remove extra prediction icons
while (mPredictionBarView.getChildCount() > mNumPredictedAppsPerRow) {
mPredictionBarView.removeViewAt(mPredictionBarView.getChildCount() - 1);
}
int childCount = mPredictionBarView.getChildCount();
for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
BubbleTextView icon;
@ -455,97 +332,113 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
@Override
protected void onFixedBoundsUpdated() {
// Update the number of items in the grid
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Update the number of items in the grid before we measure the view
int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
MeasureSpec.getSize(widthMeasureSpec);
DeviceProfile grid = mLauncher.getDeviceProfile();
if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
grid.updateAppsViewNumCols(getResources(), availableWidth);
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* Update the padding of the Apps view and children. To ensure that the RecyclerView has the
* full width to handle touches right to the edge of the screen, we only apply the top and
* bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
* itself. In particular, the left/right padding is applied to the background of the view,
* and then additionally inset by the start margin.
* Update the background and padding of the Apps view and children. Instead of insetting the
* container view, we inset the background and padding of the recycler view to allow for the
* recycler view to handle touch events (for fast scrolling) all the way to the edge.
*/
@Override
protected void onUpdatePaddings() {
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
boolean isRtl = Utilities.isRtl(getResources());
boolean hasSearchBar = (mSearchBarEditView != null) &&
(mSearchBarEditView.getVisibility() == View.VISIBLE);
// Set the background on the container, but let the recyclerView extend the full screen,
// so that the fast-scroller works on the edge as well.
mContentView.setPadding(0, 0, 0, 0);
if (mFixedBounds.isEmpty()) {
// If there are no fixed bounds, then use the default padding and insets
setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
mContainerInset + mInsets.bottom);
} else {
// If there are fixed bounds, then we update the padding to reflect the fixed bounds.
setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
mFixedBounds.bottom);
}
// Update the apps recycler view, inset it by the container inset as well
DeviceProfile grid = mLauncher.getDeviceProfile();
int startMargin = grid.isPhone ? mContentMarginStart : 0;
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
if (isRtl) {
mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0,
inset + startMargin, 0);
} else {
mAppsRecyclerView.setPadding(inset + startMargin, 0,
inset + mAppsRecyclerView.getScrollbarWidth(), 0);
}
// Update the header bar
if (hasSearchBar) {
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();
}
/**
* Update the background of the Apps view and children.
*/
@Override
protected void onUpdateBackgrounds() {
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
// Update the background of the reveal view and list to be inset with the fixed bound
// insets instead of the default insets
// TODO: Use quantum_panel instead of quantum_panel_shape.
InsetDrawable background = new InsetDrawable(
getContext().getResources().getDrawable(R.drawable.quantum_panel_shape),
inset, 0, inset, 0);
mContentView.setBackground(background);
mAppsRecyclerView.updateBackgroundPadding(background);
mAdapter.updateBackgroundPadding(background);
mElevationController.updateBackgroundPadding(background);
getRevealView().setBackground(background.getConstantState().newDrawable());
getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
padding.right, 0);
mContainerView.setBackground(background);
mRevealView.setBackground(background.getConstantState().newDrawable());
mAppsRecyclerView.updateBackgroundPadding(padding);
mAdapter.updateBackgroundPadding(padding);
// Hack: We are going to let the recycler view take the full width, so reset the padding on
// the container to zero after setting the background and apply the top-bottom padding to
// the content view instead so that the launcher transition clips correctly.
mContent.setPadding(0, padding.top, 0, padding.bottom);
mContainerView.setPadding(0, 0, 0, 0);
// Pad the recycler view by the background padding plus the start margin (for the section
// names)
DeviceProfile grid = mLauncher.getDeviceProfile();
int startMargin = grid.isPhone ? getResources().getDimensionPixelSize(
R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
if (isRtl) {
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
padding.right + startMargin, 0);
} else {
mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
}
// Inset the search bar to fit its bounds above the container
if (mSearchBarView != null) {
Rect backgroundPadding = new Rect();
if (mSearchBarView.getBackground() != null) {
mSearchBarView.getBackground().getPadding(backgroundPadding);
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
mSearchBarContainerView.getLayoutParams();
lp.leftMargin = searchBarBounds.left - backgroundPadding.left;
lp.topMargin = searchBarBounds.top - backgroundPadding.top;
lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right;
mSearchBarContainerView.requestLayout();
}
// Update the prediction bar insets as well
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
mPredictionBarView.requestLayout();
}
@Override
public boolean onPreDraw() {
synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
if (mNumAppsPerRow > 0) {
// Update the position of the prediction bar to match the scroll of the all apps list
synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
}
return true;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Determine if the key event was actual text, if so, focus the search bar and then dispatch
// the key normally so that it can process this key event
if (!mSearchBarController.isSearchFieldFocused() &&
event.getAction() == KeyEvent.ACTION_DOWN) {
final int unicodeChar = event.getUnicodeChar();
final boolean isKeyNotWhitespace = unicodeChar > 0 &&
!Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
if (isKeyNotWhitespace) {
boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
event.getKeyCode(), event);
if (gotKey && mSearchQueryBuilder.length() > 0) {
mSearchBarController.focusSearchField();
}
}
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return handleTouchEvent(ev);
@ -569,15 +462,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return false;
}
@Override
public void onClick(View v) {
if (v == mHeaderView) {
showSearchField();
} else if (v == mDismissSearchButtonView) {
hideSearchField(true, true);
}
}
@Override
public boolean onLongClick(View v) {
// Return early if this is not initiated from a touch
@ -660,70 +544,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
@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) {
String queryText = s.toString();
if (queryText.isEmpty()) {
mSearchManager.cancel(true);
mApps.setOrderedFilter(null);
} else {
String formatStr = getResources().getString(R.string.all_apps_no_search_results);
mAdapter.setEmptySearchText(String.format(formatStr, queryText));
mSearchManager.cancel(false);
mSearchManager.doSearch(queryText, this);
}
scrollToTop();
}
@Override
public void onSearchResult(ArrayList<ComponentName> apps) {
if (apps != null) {
mApps.setOrderedFilter(apps);
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
// Skip the quick-launch if there isn't exactly one item
if (mApps.getSize() != 1) {
return false;
}
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
mAppsRecyclerView.getChildAt(i).performClick();
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
return true;
}
}
}
return false;
}
@Override
public void onAdapterItemsChanged() {
updatePredictionBarVisibility();
}
@Override
public View getContent() {
return null;
}
@Override
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
// Register for a pre-draw listener to synchronize the recycler view scroll to other views
@ -745,14 +570,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
if (mSearchBarEditView != null) {
if (toWorkspace) {
hideSearchField(false, false);
}
}
if (toWorkspace) {
getViewTreeObserver().removeOnPreDrawListener(this);
mLastRecyclerViewScrollPos = -1;
// Reset the search bar after transitioning home
mSearchBarController.reset();
}
}
@ -763,9 +586,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private void synchronizeToRecyclerViewScrollPosition(int scrollY) {
if (mLastRecyclerViewScrollPos != scrollY) {
mLastRecyclerViewScrollPos = scrollY;
if (DYNAMIC_HEADER_ELEVATION) {
mElevationController.onScroll(scrollY);
}
// Scroll the prediction bar with the contents of the recycler view
mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop());
@ -806,9 +626,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
if (!mFixedBounds.isEmpty()) {
if (!mContentBounds.isEmpty()) {
// Outset the fixed bounds and check if the touch is outside all apps
Rect tmpRect = new Rect(mFixedBounds);
Rect tmpRect = new Rect(mContentBounds);
tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
mBoundsCheckLastTouchDownPos.set(x, y);
@ -874,6 +694,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return false;
}
@Override
public void onSearchResult(String query, ArrayList<ComponentName> apps) {
if (apps != null) {
if (apps.isEmpty()) {
String formatStr = getResources().getString(R.string.all_apps_no_search_results);
mAdapter.setEmptySearchText(String.format(formatStr, query));
} else {
mAppsRecyclerView.scrollToTop();
}
mApps.setOrderedFilter(apps);
}
}
@Override
public void clearSearchResult() {
mApps.setOrderedFilter(null);
// Clear the search query
mSearchQueryBuilder.clear();
mSearchQueryBuilder.clearSpans();
Selection.setSelection(mSearchQueryBuilder, 0);
}
@Override
public void fillInLaunchSourceData(Bundle sourceData) {
// Since the other cases are caught by the AllAppsRecyclerView LaunchSourceProvider, we just
@ -889,11 +732,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private View findPredictedAppAtCoordinate(int x, int y) {
Rect hitRect = new Rect();
// Ensure we aren't hitting the search bar
// Ensure that are touching in the recycler view
int[] coord = {x, y};
Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord);
mHeaderView.getHitRect(hitRect);
if (hitRect.contains(coord[0], coord[1])) {
Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, coord);
mAppsRecyclerView.getHitRect(hitRect);
if (!hitRect.contains(coord[0], coord[1])) {
return null;
}
@ -914,95 +757,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return null;
}
/**
* Shows the search field.
*/
private void showSearchField() {
mSearchManager.connect();
// Show the search bar and focus the search
final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
getContext().getResources().getDisplayMetrics());
mSearchBarContainerView.setVisibility(View.VISIBLE);
mSearchBarContainerView.setAlpha(0f);
mSearchBarContainerView.setTranslationX(translationX);
mSearchBarContainerView.animate()
.alpha(1f)
.translationX(0)
.setDuration(FADE_IN_DURATION)
.withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
mSearchBarEditView.requestFocus();
getInputMethodManager().showSoftInput(mSearchBarEditView,
InputMethodManager.SHOW_IMPLICIT);
}
});
mSearchButtonView.animate()
.alpha(0f)
.translationX(-translationX)
.setDuration(FADE_OUT_DURATION)
.withLayer();
}
/**
* Hides the search field.
*/
@Thunk void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
mSearchManager.cancel(true);
final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
getContext().getResources().getDisplayMetrics());
if (animated) {
// Hide the search bar and focus the recycler view
mSearchBarContainerView.animate()
.alpha(0f)
.translationX(0)
.setDuration(FADE_IN_DURATION)
.withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
if (resetTextField) {
mSearchBarEditView.setText("");
}
mApps.setOrderedFilter(null);
if (returnFocusToRecyclerView) {
mAppsRecyclerView.requestFocus();
}
}
});
mSearchButtonView.setTranslationX(-translationX);
mSearchButtonView.animate()
.alpha(1f)
.translationX(0)
.setDuration(FADE_OUT_DURATION)
.withLayer();
} else {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
if (resetTextField) {
mSearchBarEditView.setText("");
}
mApps.setOrderedFilter(null);
mSearchButtonView.setAlpha(1f);
mSearchButtonView.setTranslationX(0f);
if (returnFocusToRecyclerView) {
mAppsRecyclerView.requestFocus();
}
}
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());
boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() &&
(!mApps.hasFilter() || mSearchBarController.shouldShowPredictionBar());
if (showPredictionBar) {
mPredictionBarView.setVisibility(View.VISIBLE);
} else if (!showPredictionBar) {
@ -1010,11 +771,4 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
return showPredictionBar;
}
/**
* Returns an input method manager.
*/
@Thunk InputMethodManager getInputMethodManager() {
return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
}
}

View File

@ -109,6 +109,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private static final boolean DEBUG_SECTION_MARGIN = false;
private static final boolean FADE_OUT_SECTIONS = false;
private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
@ -121,10 +122,17 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mApps.hasFilter()) {
if (mApps.hasFilter() || mAppsPerRow == 0) {
return;
}
if (DEBUG_SECTION_MARGIN) {
Paint p = new Paint();
p.setColor(0x33ff0000);
c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mStartMargin,
parent.getMeasuredHeight(), p);
}
DeviceProfile grid = mLauncher.getDeviceProfile();
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
boolean hasDrawnPredictedAppsDivider = false;
@ -171,8 +179,8 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
// Calculate where to draw the section
int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
mPaddingStart;
int x = mIsRtl ? parent.getWidth() - mBackgroundPadding.left - mStartMargin :
mBackgroundPadding.left;
x += (int) ((mStartMargin - sectionBounds.x) / 2f);
int y = child.getTop() + sectionBaseline;
@ -301,23 +309,20 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
private String mEmptySearchText;
// Section drawing
@Thunk int mPaddingStart;
@Thunk int mStartMargin;
@Thunk int mSectionHeaderOffset;
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
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);
mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mItemDecoration = new GridItemDecoration(context);
mLayoutInflater = LayoutInflater.from(context);
@ -326,7 +331,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mIconLongClickListener = iconLongClickListener;
mStartMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
mPaddingStart = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
mSectionTextPaint = new Paint();
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
@ -339,7 +343,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mPredictedAppsDividerPaint.setColor(0x1E000000);
mPredictedAppsDividerPaint.setAntiAlias(true);
mPredictionBarBottomPadding =
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding);
res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding);
}
/**
@ -375,8 +379,8 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
* Notifies the adapter of the background padding so that it can draw things correctly in the
* item decorator.
*/
public void updateBackgroundPadding(Drawable background) {
background.getPadding(mBackgroundPadding);
public void updateBackgroundPadding(Rect padding) {
mBackgroundPadding.set(padding);
}
/**
@ -394,13 +398,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
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) {

View File

@ -16,8 +16,6 @@
package com.android.launcher3.allapps;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -40,11 +38,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
private int mPredictionBarHeight;
private int mScrollbarMinHeight;
private Rect mBackgroundPadding = new Rect();
private Launcher mLauncher;
@ -89,10 +83,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
}
public void updateBackgroundPadding(Drawable background) {
background.getPadding(mBackgroundPadding);
}
/**
* Sets the prediction bar height.
*/
@ -124,6 +114,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// Bind event handlers
addOnItemTouchListener(this);
}
@ -227,8 +219,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
public void updateVerticalScrollbarBounds() {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items.
if (items.isEmpty()) {
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
verticalScrollbarBounds.setEmpty();
return;
}
@ -242,8 +234,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight;
if (totalScrollHeight > height) {
int scrollbarHeight = Math.max(mScrollbarMinHeight,
(int) (height / ((float) totalScrollHeight / height)));
int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height));
// Calculate the position and size of the scroll bar
if (Utilities.isRtl(getResources())) {
@ -277,8 +268,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
// Return early if there are no items
if (items.isEmpty()) {
// Return early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
return;
}

View File

@ -0,0 +1,97 @@
/*
* 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.allapps;
import android.content.ComponentName;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* An interface to a search box that AllApps can command.
*/
public abstract class AllAppsSearchBarController {
protected AlphabeticalAppsList mApps;
protected Callbacks mCb;
/**
* Sets the references to the apps model and the search result callback.
*/
public final void initialize(AlphabeticalAppsList apps, Callbacks cb) {
mApps = apps;
mCb = cb;
onInitialize();
}
/**
* To be overridden by subclasses. This method will get called when the controller is set,
* before getView().
*/
protected abstract void onInitialize();
/**
* Returns the search bar view.
* @param parent the parent to attach the search bar view to.
*/
public abstract View getView(ViewGroup parent);
/**
* Focuses the search field to handle key events.
*/
public abstract void focusSearchField();
/**
* Returns whether the search field is focused.
*/
public abstract boolean isSearchFieldFocused();
/**
* Resets the search bar state.
*/
public abstract void reset();
/**
* Returns whether the prediction bar should currently be visible depending on the state of
* the search bar.
*/
public abstract boolean shouldShowPredictionBar();
/**
* Callback for getting search results.
*/
public interface Callbacks {
/**
* Called when the bounds of the search bar has changed.
*/
void onBoundsChanged(Rect newBounds);
/**
* Called when the search is complete.
*
* @param apps sorted list of matching components or null if in case of failure.
*/
void onSearchResult(String query, ArrayList<ComponentName> apps);
/**
* Called when the search results should be cleared.
*/
void clearSearchResult();
}
}

View File

@ -235,11 +235,10 @@ public class AlphabeticalAppsList {
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) {
public AlphabeticalAppsList(Context context) {
mLauncher = (Launcher) context;
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context);
setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
}
/**
@ -249,10 +248,6 @@ public class AlphabeticalAppsList {
mAdapterChangedCallback = cb;
}
public SimpleAppSearchManagerImpl newSimpleAppSearchManager() {
return new SimpleAppSearchManagerImpl(mApps);
}
/**
* Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
*/
@ -269,7 +264,7 @@ public class AlphabeticalAppsList {
mNumAppsPerRow = numAppsPerRow;
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
onAppsUpdated();
updateAdapterItems();
}
/**
@ -279,6 +274,13 @@ public class AlphabeticalAppsList {
mAdapter = adapter;
}
/**
* Returns all the apps.
*/
public List<AppInfo> getApps() {
return mApps;
}
/**
* Returns sections of all the current filtered applications.
*/
@ -597,6 +599,11 @@ public class AlphabeticalAppsList {
* Merges multiple sections to reduce visual raggedness.
*/
private void mergeSections() {
// Ignore merging until we have a valid row size
if (mNumAppsPerRow == 0) {
return;
}
// Go through each section and try and merge some of the sections
if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
int sectionAppCount = 0;

View File

@ -1,59 +0,0 @@
/*
* 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.allapps;
import android.content.ComponentName;
import java.util.ArrayList;
/**
* Interface for handling app search.
*/
public interface AppSearchManager {
/**
* Called when the search is about to be used. This method is optional for making a query but
* calling this appropriately can improve the initial response time.
*/
void connect();
/**
* Cancels all pending search requests.
*
* @param interruptActiveRequests if true, any active requests which are already executing will
* be invalidated, and the corresponding results will not be sent. The client should usually
* set this to true, before beginning a new search session.
*/
void cancel(boolean interruptActiveRequests);
/**
* Performs a search
*/
void doSearch(String query, AppSearchResultCallback callback);
/**
* Callback for getting search results.
*/
public interface AppSearchResultCallback {
/**
* Called when the search is complete.
*
* @param apps sorted list of matching components or null if in case of failure.
*/
void onSearchResult(ArrayList<ComponentName> apps);
}
}

View File

@ -17,7 +17,6 @@ package com.android.launcher3.allapps;
import android.content.ComponentName;
import android.os.Handler;
import com.android.launcher3.AppInfo;
import java.util.ArrayList;
@ -25,39 +24,33 @@ import java.util.List;
import java.util.regex.Pattern;
/**
* An {@link AppSearchManager} which does label matching on the UI thread.
* The default search implementation.
*/
public class SimpleAppSearchManagerImpl implements AppSearchManager {
public class DefaultAppSearchAlgorithm {
private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
private final List<AppInfo> mApps;
private final Handler mResultHandler;
public SimpleAppSearchManagerImpl(List<AppInfo> apps) {
public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
mApps = apps;
mResultHandler = new Handler();
}
@Override
public void connect() {
// No op
}
@Override
public void cancel(boolean interruptActiveRequests) {
if (interruptActiveRequests) {
mResultHandler.removeCallbacksAndMessages(null);
}
}
@Override
public void doSearch(String query, final AppSearchResultCallback callback) {
public void doSearch(final String query,
final AllAppsSearchBarController.Callbacks callback) {
// Do an intersection of the words in the query and each title, and filter out all the
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
final ArrayList<ComponentName> result = new ArrayList<ComponentName>();
final ArrayList<ComponentName> result = new ArrayList<>();
int total = mApps.size();
for (int i = 0; i < total; i++) {
@ -70,7 +63,7 @@ public class SimpleAppSearchManagerImpl implements AppSearchManager {
@Override
public void run() {
callback.onSearchResult(result);
callback.onSearchResult(query, result);
}
});
}

View File

@ -0,0 +1,270 @@
/*
* 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.allapps;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
import java.util.List;
/**
* The default search controller.
*/
final class DefaultAppSearchController extends AllAppsSearchBarController
implements TextWatcher, TextView.OnEditorActionListener, View.OnClickListener {
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
private static final int FADE_IN_DURATION = 175;
private static final int FADE_OUT_DURATION = 100;
private static final int SEARCH_TRANSLATION_X_DP = 18;
private final Context mContext;
@Thunk final InputMethodManager mInputMethodManager;
private DefaultAppSearchAlgorithm mSearchManager;
private ViewGroup mContainerView;
private View mSearchView;
@Thunk View mSearchBarContainerView;
private View mSearchButtonView;
private View mDismissSearchButtonView;
@Thunk AllAppsSearchEditView mSearchBarEditView;
@Thunk AllAppsRecyclerView mAppsRecyclerView;
private Runnable mFocusRecyclerViewRunnable = new Runnable() {
@Override
public void run() {
mAppsRecyclerView.requestFocus();
}
};
public DefaultAppSearchController(Context context, ViewGroup containerView,
AllAppsRecyclerView appsRecyclerView) {
mContext = context;
mInputMethodManager = (InputMethodManager)
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
mContainerView = containerView;
mAppsRecyclerView = appsRecyclerView;
}
@Override
public View getView(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
mSearchView = inflater.inflate(R.layout.all_apps_search_bar, parent, false);
mSearchView.setOnClickListener(this);
mSearchButtonView = mSearchView.findViewById(R.id.search_button);
mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
mDismissSearchButtonView.setOnClickListener(this);
mSearchBarEditView = (AllAppsSearchEditView)
mSearchBarContainerView.findViewById(R.id.search_box);
mSearchBarEditView.addTextChangedListener(this);
mSearchBarEditView.setOnEditorActionListener(this);
mSearchBarEditView.setOnBackKeyListener(
new AllAppsSearchEditView.OnBackKeyListener() {
@Override
public void onBackKey() {
// Only hide the search field if there is no query, or if there
// are no filtered results
String query = Utilities.trim(
mSearchBarEditView.getEditableText().toString());
if (query.isEmpty() || mApps.hasNoFilteredResults()) {
hideSearchField(true, mFocusRecyclerViewRunnable);
}
}
});
return mSearchView;
}
@Override
public void focusSearchField() {
mSearchBarEditView.requestFocus();
showSearchField();
}
@Override
public boolean isSearchFieldFocused() {
return mSearchBarEditView.isFocused();
}
@Override
protected void onInitialize() {
mSearchManager = new DefaultAppSearchAlgorithm(mApps.getApps());
}
@Override
public void reset() {
hideSearchField(false, null);
}
@Override
public boolean shouldShowPredictionBar() {
// Keep showing the prediction bar if the input query is empty
return mSearchBarEditView.getEditableText().toString().isEmpty();
}
@Override
public void onClick(View v) {
if (v == mSearchView) {
showSearchField();
} else if (v == mDismissSearchButtonView) {
hideSearchField(true, mFocusRecyclerViewRunnable);
}
}
@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) {
String query = s.toString();
if (query.isEmpty()) {
mSearchManager.cancel(true);
mCb.clearSearchResult();
} else {
mSearchManager.cancel(false);
mSearchManager.doSearch(query, mCb);
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// Skip if we disallow app-launch-on-enter
if (!ALLOW_SINGLE_APP_LAUNCH) {
return false;
}
// Skip if it's not the right action
if (actionId != EditorInfo.IME_ACTION_DONE) {
return false;
}
// Skip if there isn't exactly one item
if (mApps.getSize() != 1) {
return false;
}
// If there is exactly one icon, then quick-launch it
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
mAppsRecyclerView.getChildAt(i).performClick();
mInputMethodManager.hideSoftInputFromWindow(
mContainerView.getWindowToken(), 0);
return true;
}
}
return false;
}
/**
* Focuses the search field.
*/
private void showSearchField() {
// Show the search bar and focus the search
final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
mContext.getResources().getDisplayMetrics());
mSearchBarContainerView.setVisibility(View.VISIBLE);
mSearchBarContainerView.setAlpha(0f);
mSearchBarContainerView.setTranslationX(translationX);
mSearchBarContainerView.animate()
.alpha(1f)
.translationX(0)
.setDuration(FADE_IN_DURATION)
.withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
mSearchBarEditView.requestFocus();
mInputMethodManager.showSoftInput(mSearchBarEditView,
InputMethodManager.SHOW_IMPLICIT);
}
});
mSearchButtonView.animate()
.alpha(0f)
.translationX(-translationX)
.setDuration(FADE_OUT_DURATION)
.withLayer();
}
/**
* Unfocuses the search field.
*/
@Thunk void hideSearchField(boolean animated, final Runnable postAnimationRunnable) {
mSearchManager.cancel(true);
final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
mContext.getResources().getDisplayMetrics());
if (animated) {
// Hide the search bar and focus the recycler view
mSearchBarContainerView.animate()
.alpha(0f)
.translationX(0)
.setDuration(FADE_IN_DURATION)
.withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
if (resetTextField) {
mSearchBarEditView.setText("");
}
mCb.clearSearchResult();
if (postAnimationRunnable != null) {
postAnimationRunnable.run();
}
}
});
mSearchButtonView.setTranslationX(-translationX);
mSearchButtonView.animate()
.alpha(1f)
.translationX(0)
.setDuration(FADE_OUT_DURATION)
.withLayer();
} else {
mSearchBarContainerView.setVisibility(View.INVISIBLE);
if (resetTextField) {
mSearchBarEditView.setText("");
}
mCb.clearSearchResult();
mSearchButtonView.setAlpha(1f);
mSearchButtonView.setTranslationX(0f);
if (postAnimationRunnable != null) {
postAnimationRunnable.run();
}
}
mInputMethodManager.hideSoftInputFromWindow(mContainerView.getWindowToken(), 0);
}
}

View File

@ -66,6 +66,7 @@ public class WidgetsContainerView extends BaseContainerView
private IconCache mIconCache;
/* Recycler view related member variables */
private View mContent;
private WidgetsRecyclerView mView;
private WidgetsListAdapter mAdapter;
@ -98,6 +99,7 @@ public class WidgetsContainerView extends BaseContainerView
@Override
protected void onFinishInflate() {
mContent = findViewById(R.id.content);
mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view);
mView.setAdapter(mAdapter);
@ -112,7 +114,6 @@ public class WidgetsContainerView extends BaseContainerView
});
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom());
onUpdatePaddings();
}
//
@ -335,33 +336,18 @@ public class WidgetsContainerView extends BaseContainerView
//
@Override
protected void onUpdatePaddings() {
if (mFixedBounds.isEmpty()) {
// If there are no fixed bounds, then use the default padding and insets
setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top,
mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom);
} else {
// If there are fixed bounds, then we update the padding to reflect the fixed bounds.
setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
mFixedBounds.bottom);
}
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
// Apply the top-bottom padding to the content itself so that the launcher transition is
// clipped correctly
mContent.setPadding(0, padding.top, 0, padding.bottom);
int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset;
mView.setPadding(inset + mView.getScrollbarWidth(), inset,
inset, inset);
}
@Override
protected void onUpdateBackgrounds() {
InsetDrawable background;
// Update the background of the reveal view and list to be inset with the fixed bound
// insets instead of the default insets
// TODO: Use quantum_panel instead of quantum_panel_shape.
int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset;
background = new InsetDrawable(
getContext().getResources().getDrawable(R.drawable.quantum_panel_shape),
inset, 0, inset, 0);
mView.updateBackgroundPadding(background);
// TODO: Use quantum_panel_dark instead of quantum_panel_shape_dark.
InsetDrawable background = new InsetDrawable(
getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0,
padding.right, 0);
mView.setBackground(background);
getRevealView().setBackground(background.getConstantState().newDrawable());
mView.updateBackgroundPadding(padding);
}
/**

View File

@ -18,11 +18,9 @@ package com.android.launcher3.widget;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetsModel;
@ -35,7 +33,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
private static final String TAG = "WidgetsRecyclerView";
private WidgetsModel mWidgets;
private Rect mBackgroundPadding = new Rect();
public WidgetsRecyclerView(Context context) {
this(context, null);
@ -61,10 +58,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
addOnItemTouchListener(this);
}
public void updateBackgroundPadding(Drawable background) {
background.getPadding(mBackgroundPadding);
}
/**
* Sets the widget model in this view, used to determine the fast scroll position.
*/