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>
\ No newline at end of file
+<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" />
\ No newline at end of file
+++ /dev/null
-<?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>
\ No newline at end of file
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>
\ No newline at end of file
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>
\ No newline at end of file
+++ /dev/null
-<?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" />
\ No newline at end of file
--- /dev/null
+<?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>
\ No newline at end of file
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>
\ No newline at end of file
<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>
<!-- 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>
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);
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);
- /**
- * Update the paddings in response to a change in the bounds or insets.
- */
- protected void onUpdatePaddings() {
- // Do nothing
+ // 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 backgrounds 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 onUpdateBackgrounds() {
- // Do nothing
- }
+ protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding);
}
\ No newline at end of file
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);
return false;
}
+ public void updateBackgroundPadding(Rect padding) {
+ mBackgroundPadding.set(padding);
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 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() -
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 */
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;
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;
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 =
}
}
+ /**
+ * 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) {
}
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);
* 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 {
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);
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) {
}
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) {
// 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();
- }
- }
}
}
}
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);
}
* 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;
}
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() {
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);
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;
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);
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();
/**
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.
*
* @param callbacks A set of callbacks to the Launcher, is actually a LauncherSearchCallback,
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;
}
@Override
- public void onAllAppsShown() {
- }
-
- @Override
- public void onAllAppsHidden() {
- }
-
- @Override
public void bindAllApplications(ArrayList<AppInfo> apps) {
}
}
@Override
- public boolean overrideAllAppsSearch() {
- return false;
+ public AllAppsSearchBarController getAllAppsSearchBarController() {
+ return null;
}
@Override
}
@Override
- public void setLauncherAppsCallback(Object callbacks) {
- // Do nothing
- }
-
- @Override
public void setLauncherSearchCallback(Object callbacks) {
// Do nothing
}
/**
* 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;
@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,
};
}
};
+ // 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);
}
/**
@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) {
}
};
startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView,
- toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */,
- cb);
+ toView.getContentView(), toView.getRevealView(), null, animated,
+ true /* hideSearchBar */, cb);
}
/**
}
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);
}
}
*/
@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);
// 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();
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);
/**
* 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;
};
}
};
+ // 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) {
}
};
startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView,
- widgetsView.getContentView(), widgetsView.getRevealView(), animated,
+ widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
onCompleteRunnable, cb);
}
*/
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);
// 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();
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();
contentView.setTranslationY(0);
contentView.setAlpha(1);
}
+ if (overlaySearchBarView != null) {
+ overlaySearchBarView.setAlpha(1f);
+ }
// This can hold unnecessary references to views.
mStateAnimation = null;
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);
* 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;
}
}
- @Override
- public View getContent() {
- return this;
- }
-
/**
* Returns the drawable for the given text view.
*/
}
/**
+ * 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.
*/
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;
}
}
/**
- * Starts a transition animation for the workspace.
+ * Reinitializes the arrays that we need for the animations on each page.
*/
- 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 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 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();
}
// 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();
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();
}
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;
// 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;
}
}
- final View searchBar = mLauncher.getOrCreateQsbBar();
final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
final View pageIndicator = mWorkspace.getPageIndicator();
}
}
}
- Animator pageIndicatorAlpha = null;
+ Animator pageIndicatorAlpha;
if (pageIndicator != null) {
pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
.alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
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));
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);
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);
}
}
- if (stateIsNormal) {
+ if (states.stateIsNormal) {
animateBackgroundGradient(0f, animated);
} else {
animateBackgroundGradient(mWorkspaceScrimAlpha, animated);
}
/**
- * 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);
+ }
+ }
}
/**
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;
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;
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;
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;
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;
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);
}
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));
-
- mLayoutInflater = LayoutInflater.from(context);
+ 2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
- 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);
}
/**
mApps.addApps(apps);
}
- public void setSearchManager(AppSearchManager searchManager) {
- mSearchManager.cancel(true);
- mSearchManager = searchManager;
- }
-
/**
* Updates existing apps in the list
*/
}
/**
- * 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();
}
/**
* 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
}
}
}
- });
-
- // 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);
+ };
+ 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);
+
+ // 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);
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
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;
}
@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
+ // TODO: Use quantum_panel instead of quantum_panel_shape.
+ InsetDrawable background = new InsetDrawable(
+ 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 ? mContentMarginStart : 0;
- int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+ int startMargin = grid.isPhone ? getResources().getDimensionPixelSize(
+ R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
if (isRtl) {
- mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0,
- inset + startMargin, 0);
+ mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
+ padding.right + startMargin, 0);
} else {
- mAppsRecyclerView.setPadding(inset + startMargin, 0,
- inset + mAppsRecyclerView.getScrollbarWidth(), 0);
+ mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
+ padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
}
- // Update the header bar
- if (hasSearchBar) {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
- lp.leftMargin = lp.rightMargin = inset;
- mHeaderView.requestLayout();
+ // 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 = inset + mAppsRecyclerView.getScrollbarWidth();
- lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
+ lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
+ lp.rightMargin = padding.right + 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());
+ public boolean onPreDraw() {
+ 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 onPreDraw() {
- synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
- return true;
+ 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
}
@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
if (!v.isInTouchMode()) return false;
}
@Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // Do nothing
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Do nothing
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- 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
// in this container
@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();
}
}
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());
}
}
- 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);
}
@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
// handle the prediction bar icons here
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;
}
}
/**
- * 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) {
}
return showPredictionBar;
}
-
- /**
- * Returns an input method manager.
- */
- @Thunk InputMethodManager getInputMethodManager() {
- return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- }
}
*/
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<>();
@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;
// 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;
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);
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(
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);
}
/**
* 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);
}
/**
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) {
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;
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
-
private int mPredictionBarHeight;
- private int mScrollbarMinHeight;
-
- private Rect mBackgroundPadding = new Rect();
private Launcher mLauncher;
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
}
- public void updateBackgroundPadding(Drawable background) {
- background.getPadding(mBackgroundPadding);
- }
-
/**
* Sets the prediction bar height.
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+
+ // Bind event handlers
addOnItemTouchListener(this);
}
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;
}
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())) {
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;
}
--- /dev/null
+/*
+ * 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();
+ }
+}
\ No newline at end of file
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);
}
/**
mAdapterChangedCallback = cb;
}
- public SimpleAppSearchManagerImpl newSimpleAppSearchManager() {
- return new SimpleAppSearchManagerImpl(mApps);
- }
-
/**
* Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
*/
mNumAppsPerRow = numAppsPerRow;
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
- onAppsUpdated();
+ updateAdapterItems();
}
/**
}
/**
+ * Returns all the apps.
+ */
+ public List<AppInfo> getApps() {
+ return mApps;
+ }
+
+ /**
* Returns sections of all the current filtered applications.
*/
public List<SectionInfo> getSections() {
* 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;
+++ /dev/null
-/*
- * 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);
- }
-}
import android.content.ComponentName;
import android.os.Handler;
-
import com.android.launcher3.AppInfo;
import java.util.ArrayList;
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++) {
@Override
public void run() {
- callback.onSearchResult(result);
+ callback.onSearchResult(query, result);
}
});
}
--- /dev/null
+/*
+ * 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);
+ }
+}
private IconCache mIconCache;
/* Recycler view related member variables */
+ private View mContent;
private WidgetsRecyclerView mView;
private WidgetsListAdapter mAdapter;
@Override
protected void onFinishInflate() {
+ mContent = findViewById(R.id.content);
mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view);
mView.setAdapter(mAdapter);
});
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom());
- onUpdatePaddings();
}
//
//
@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);
- }
-
- 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);
+ 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);
+
+ // 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);
}
/**
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;
private static final String TAG = "WidgetsRecyclerView";
private WidgetsModel mWidgets;
- private Rect mBackgroundPadding = new Rect();
public WidgetsRecyclerView(Context context) {
this(context, null);
addOnItemTouchListener(this);
}
- public void updateBackgroundPadding(Drawable background) {
- background.getPadding(mBackgroundPadding);
- }
-
/**
* Sets the widget model in this view, used to determine the fast scroll position.
*/