diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-10-10 15:21:15 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-10-13 18:47:54 -0700 |
commit | f1fbc3fbe78997f141e2770221fe5ab1b1e68014 (patch) | |
tree | 466510cf18141f0a7a841c349096f6dfc23d5e8f /src/com/android/launcher3 | |
parent | 10a1bd0e652ec7ea3e3ee861fc0d72261a33a3fd (diff) | |
download | android_packages_apps_Trebuchet-f1fbc3fbe78997f141e2770221fe5ab1b1e68014.tar.gz android_packages_apps_Trebuchet-f1fbc3fbe78997f141e2770221fe5ab1b1e68014.tar.bz2 android_packages_apps_Trebuchet-f1fbc3fbe78997f141e2770221fe5ab1b1e68014.zip |
Converting widget panel into a floating view
> The widget panel is only inflated when needed
> Using the swipe up/down interaction for widgets tray
> Removing additional view wrappers from all-apps
> Widget tray is preserved across activity recreation
> Launcher no longer has WIDGET state, the actual code around
the states will be removed in a follow-up cl
Bug: 67678570
Bug: 67585158
Change-Id: Ia29a7c33ec81e6c53cc24e2906b7022b6f41755b
Diffstat (limited to 'src/com/android/launcher3')
25 files changed, 956 insertions, 1487 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 0fbad522e..62e0fb1bc 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -20,6 +20,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.IntDef; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; @@ -40,7 +41,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_FOLDER, TYPE_ACTION_POPUP, TYPE_WIDGETS_BOTTOM_SHEET, - TYPE_WIDGET_RESIZE_FRAME + TYPE_WIDGET_RESIZE_FRAME, + TYPE_WIDGETS_FULL_SHEET }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} @@ -48,6 +50,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_ACTION_POPUP = 1 << 1; public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2; public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3; + public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4; + + public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP + | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET; protected boolean mIsOpen; @@ -121,24 +127,31 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch } } - public static void closeAllOpenViews(Launcher launcher, boolean animate) { + public static void closeOpenViews(Launcher launcher, boolean animate, + @FloatingViewType int type) { DragLayer dragLayer = launcher.getDragLayer(); // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, // and will be one of the last views. for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { View child = dragLayer.getChildAt(i); if (child instanceof AbstractFloatingView) { - ((AbstractFloatingView) child).close(animate); + AbstractFloatingView abs = (AbstractFloatingView) child; + if (abs.isOfType(type)) { + abs.close(animate); + } } } } + public static void closeAllOpenViews(Launcher launcher, boolean animate) { + closeOpenViews(launcher, animate, TYPE_ALL); + } + public static void closeAllOpenViews(Launcher launcher) { closeAllOpenViews(launcher, true); } public static AbstractFloatingView getTopOpenView(Launcher launcher) { - return getOpenView(launcher, TYPE_FOLDER | TYPE_ACTION_POPUP - | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME); + return getOpenView(launcher, TYPE_ALL); } } diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java deleted file mode 100644 index 82175b721..000000000 --- a/src/com/android/launcher3/BaseContainerView.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.FrameLayout; - -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.TransformingTouchDelegate; - -/** - * A base container view, which supports resizing. - */ -public abstract class BaseContainerView extends FrameLayout - implements DeviceProfile.LauncherLayoutChangeListener { - - private static final Rect sBgPaddingRect = new Rect(); - - protected final Drawable mBaseDrawable; - - private View mRevealView; - private View mContent; - - private TransformingTouchDelegate mTouchDelegate; - - private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f); - - public BaseContainerView(Context context) { - this(context, null); - } - - public BaseContainerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - if (this instanceof AllAppsContainerView) { - mBaseDrawable = new ColorDrawable(); - } else { - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.BaseContainerView, defStyleAttr, 0); - mBaseDrawable = a.getDrawable(R.styleable.BaseContainerView_revealBackground); - a.recycle(); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - grid.addLauncherLayoutChangedListener(this); - - View touchDelegateTargetView = getTouchDelegateTargetView(); - if (touchDelegateTargetView != null) { - mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView); - ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - grid.removeLauncherLayoutChangedListener(this); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mContent = findViewById(R.id.main_content); - mRevealView = findViewById(R.id.reveal_view); - - updatePaddings(); - } - - @Override - public void onLauncherLayoutChanged() { - updatePaddings(); - } - - /** - * Calculate the background padding as it can change due to insets/content padding change. - */ - private void updatePaddings() { - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - int[] padding = grid.getContainerPadding(); - - int paddingLeft = padding[0]; - int paddingRight = padding[1]; - int paddingTop = 0; - int paddingBottom = 0; - - if (!grid.isVerticalBarLayout()) { - paddingLeft += grid.edgeMarginPx; - paddingRight += grid.edgeMarginPx; - paddingTop = paddingBottom = grid.edgeMarginPx; - } - updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom); - } - - /** - * Update the background for the reveal view and content view based on the background padding. - */ - protected void updateBackground(int paddingLeft, int paddingTop, - int paddingRight, int paddingBottom) { - mRevealView.setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - mContent.setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - View touchDelegateTargetView = getTouchDelegateTargetView(); - if (touchDelegateTargetView != null) { - getRevealView().getBackground().getPadding(sBgPaddingRect); - mTouchDelegate.setBounds( - touchDelegateTargetView.getLeft() - sBgPaddingRect.left, - touchDelegateTargetView.getTop() - sBgPaddingRect.top, - touchDelegateTargetView.getRight() + sBgPaddingRect.right, - touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return handleTouchEvent(ev); - } - - public void setRevealDrawableColor(int color) { - ((ColorDrawable) mBaseDrawable).setColor(color); - } - - public final View getContentView() { - return mContent; - } - - public final View getRevealView() { - return mRevealView; - } - - - /** - * Handles the touch events that shows the workspace when clicking outside the bounds of the - * touch delegate target view. - */ - private boolean handleTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - // Check if the touch is outside touch delegate target view - View touchDelegateTargetView = getTouchDelegateTargetView(); - float leftBoundPx = touchDelegateTargetView.getLeft(); - if (ev.getX() < leftBoundPx || - ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) { - mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY()); - } - break; - case MotionEvent.ACTION_UP: - if (mLastTouchDownPosPx.x > -1) { - ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); - float dx = ev.getX() - mLastTouchDownPosPx.x; - float dy = ev.getY() - mLastTouchDownPosPx.y; - float distance = PointF.length(dx, dy); - if (distance < viewConfig.getScaledTouchSlop()) { - // The background was clicked, so just go home - Launcher.getLauncher(getContext()).showWorkspace(true); - return true; - } - } - // Fall through - case MotionEvent.ACTION_CANCEL: - mLastTouchDownPosPx.set(-1, -1); - break; - } - return false; - } - - public abstract View getTouchDelegateTargetView(); -} diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 3ee6e51b8..afb83be58 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -21,6 +21,7 @@ import android.graphics.Canvas; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -99,11 +100,15 @@ public abstract class BaseRecyclerView extends RecyclerView // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS } + public int getScrollBarTop() { + return getPaddingTop(); + } + /** * Returns the height of the fast scroll bar */ public int getScrollbarTrackHeight() { - return getHeight() - getPaddingTop() - getPaddingBottom(); + return getHeight() - getScrollBarTop() - getPaddingBottom(); } /** @@ -121,13 +126,6 @@ public abstract class BaseRecyclerView extends RecyclerView return availableScrollBarHeight; } - /** - * Returns the scrollbar for this recycler view. - */ - public RecyclerViewFastScroller getScrollBar() { - return mScrollbar; - } - @Override protected void dispatchDraw(Canvas canvas) { onUpdateScrollbar(0); @@ -160,6 +158,28 @@ public abstract class BaseRecyclerView extends RecyclerView } /** + * Returns whether the view itself will handle the touch event or not. + * @param ev MotionEvent in {@param eventSource} + */ + public boolean shouldContainerScroll(MotionEvent ev, View eventSource) { + int[] point = new int[2]; + point[0] = (int) ev.getX(); + point[1] = (int) ev.getY(); + Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point); + // IF the MotionEvent is inside the thumb, container should not be pulled down. + if (mScrollbar.shouldBlockIntercept(point[0], point[1])) { + return false; + } + + // IF scroller is at the very top OR there is no scroll bar because there is probably not + // enough items to scroll, THEN it's okay for the container to be pulled down. + if (getCurrentScrollY() == 0) { + return true; + } + return false; + } + + /** * @return whether fast scrolling is supported in the current state. */ public boolean supportsFastScrolling() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1bb4807b5..5c635ca51 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -54,6 +54,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Parcelable; import android.os.Process; import android.os.StrictMode; import android.os.UserHandle; @@ -63,6 +64,7 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.Log; +import android.util.SparseArray; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -105,8 +107,6 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.ModelWriter; -import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.popup.BaseActionPopup; @@ -136,7 +136,8 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetAddFlowHandler; import com.android.launcher3.widget.WidgetHostViewLoader; -import com.android.launcher3.widget.WidgetsContainerView; +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsFullSheet; import com.android.launcher3.widget.custom.CustomWidgetParser; import java.io.FileDescriptor; @@ -198,6 +199,8 @@ public class Launcher extends BaseActivity private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; // Type: ActivityResultInfo private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; + // Type: SparseArray<Parcelable> + private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel"; static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown"; @@ -234,7 +237,6 @@ public class Launcher extends BaseActivity private ViewGroup mOverviewPanel; private View mAllAppsButton; - private View mWidgetsButton; private DropTargetBar mDropTargetBar; @@ -242,9 +244,6 @@ public class Launcher extends BaseActivity @Thunk AllAppsContainerView mAppsView; AllAppsTransitionController mAllAppsController; - // Main container view and the model for the widget tray screen. - @Thunk WidgetsContainerView mWidgetsView; - // We need to store the orientation Launcher was created with, due to a bug (b/64916689) // that results in widgets being inflated in the wrong orientation. private int mOrientation; @@ -1043,8 +1042,6 @@ public class Launcher extends BaseActivity ? stateValues[stateOrdinal] : State.WORKSPACE; if (state == State.APPS) { showAppsView(false /* animated */); - } else if (state == State.WIDGETS) { - showWidgetsView(false, false); } PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); @@ -1053,6 +1050,12 @@ public class Launcher extends BaseActivity } mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); + + SparseArray<Parcelable> widgetsState = + savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL); + if (widgetsState != null) { + WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState); + } } /** @@ -1093,9 +1096,8 @@ public class Launcher extends BaseActivity // Get the search/delete/uninstall bar mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar); - // Setup Apps and Widgets - mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); - mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view); + // Setup Apps + mAppsView = findViewById(R.id.apps_view); // Setup the drag controller (drop targets have to be added in reverse order in priority) mDragController.setMoveTarget(mWorkspace); @@ -1110,7 +1112,7 @@ public class Launcher extends BaseActivity } private void setupOverviewPanel() { - mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel); + mOverviewPanel = findViewById(R.id.overview_panel); // Bind wallpaper button actions View wallpaperButton = findViewById(R.id.wallpaper_button); @@ -1122,13 +1124,12 @@ public class Launcher extends BaseActivity }.attachTo(wallpaperButton); // Bind widget button actions - mWidgetsButton = findViewById(R.id.widget_button); new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) { @Override public void handleViewClick(View view) { onClickAddWidgetButton(view); } - }.attachTo(mWidgetsButton); + }.attachTo(findViewById(R.id.widget_button)); // Bind settings actions View settingsButton = findViewById(R.id.settings_button); @@ -1155,14 +1156,6 @@ public class Launcher extends BaseActivity mAllAppsButton = allAppsButton; } - public View getStartViewForAllAppsRevealAnimation() { - return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton; - } - - public View getWidgetsButton() { - return mWidgetsButton; - } - /** * Creates a view representing a shortcut. * @@ -1325,7 +1318,7 @@ public class Launcher extends BaseActivity if (Intent.ACTION_SCREEN_OFF.equals(action)) { // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop - if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) { + if (mAppsView != null && mPendingRequestArgs == null) { if (!showWorkspace(false)) { // If we are already on the workspace, then manually reset all apps mAppsView.reset(); @@ -1385,10 +1378,6 @@ public class Launcher extends BaseActivity return mAppsView; } - public WidgetsContainerView getWidgetsView() { - return mWidgetsView; - } - public Workspace getWorkspace() { return mWorkspace; } @@ -1468,11 +1457,6 @@ public class Launcher extends BaseActivity mAppsView.reset(); } - // Reset the widgets view - if (!alreadyOnHome && mWidgetsView != null) { - mWidgetsView.scrollToTop(); - } - if (mLauncherCallbacks != null) { mLauncherCallbacks.onHomeIntent(); } @@ -1517,9 +1501,19 @@ public class Launcher extends BaseActivity outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage()); } - super.onSaveInstanceState(outState); - outState.putInt(RUNTIME_STATE, mState.ordinal()); + + + AbstractFloatingView widgets = AbstractFloatingView + .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET); + if (widgets != null) { + SparseArray<Parcelable> widgetsState = new SparseArray<>(); + widgets.saveHierarchyState(widgetsState); + outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState); + } else { + outState.remove(RUNTIME_STATE_WIDGET_PANEL); + } + // We close any open folders and shortcut containers since they will not be re-opened, // and we need to make sure this state is reflected. AbstractFloatingView.closeAllOpenViews(this, false); @@ -1531,6 +1525,8 @@ public class Launcher extends BaseActivity outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult); } + super.onSaveInstanceState(outState); + if (mLauncherCallbacks != null) { mLauncherCallbacks.onSaveInstanceState(outState); } @@ -2159,7 +2155,7 @@ public class Launcher extends BaseActivity if (mIsSafeModeEnabled) { Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show(); } else { - showWidgetsView(true /* animated */, true /* resetPageToZero */); + WidgetsFullSheet.show(this, true /* animated */); } } @@ -2557,24 +2553,6 @@ public class Launcher extends BaseActivity } /** - * Shows the widgets view. - */ - void showWidgetsView(boolean animated, boolean resetPageToZero) { - if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero); - if (resetPageToZero) { - mWidgetsView.scrollToTop(); - } - showAppsOrWidgets(State.WIDGETS, animated); - - mWidgetsView.post(new Runnable() { - @Override - public void run() { - mWidgetsView.requestFocus(); - } - }); - } - - /** * Sets up the transition to show the apps/widgets view. * * @return whether the current from and to state allowed this operation @@ -2642,7 +2620,6 @@ public class Launcher extends BaseActivity // Before we show workspace, hide all apps again because // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should // clean up our state transition functions - mWidgetsView.setVisibility(View.GONE); showWorkspace(true, onCompleteRunnable); } else { exitSpringLoadedDragMode(); @@ -2661,8 +2638,6 @@ public class Launcher extends BaseActivity public void exitSpringLoadedDragMode() { if (mState == State.APPS_SPRING_LOADED) { showAppsView(true /* animated */); - } else if (mState == State.WIDGETS_SPRING_LOADED) { - showWidgetsView(true, false); } else if (mState == State.WORKSPACE_SPRING_LOADED) { showWorkspace(true); } @@ -2676,8 +2651,6 @@ public class Launcher extends BaseActivity // Populate event with a fake title based on the current state. if (mState == State.APPS) { text.add(getString(R.string.all_apps_button_label)); - } else if (mState == State.WIDGETS) { - text.add(getString(R.string.widget_button_text)); } else if (mWorkspace != null) { text.add(mWorkspace.getCurrentPageDescription()); } else { @@ -2777,7 +2750,11 @@ public class Launcher extends BaseActivity */ public void startBinding() { TraceHelper.beginSection("startBinding"); - AbstractFloatingView.closeAllOpenViews(this); + // Floating panels (except the full widget sheet) are associated with individual icons. If + // we are starting a fresh bind, close all such panels as all the icons are about + // to go away. + AbstractFloatingView.closeOpenViews(this, true, + AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET); setWorkspaceLoading(true); @@ -3388,7 +3365,8 @@ public class Launcher extends BaseActivity } @Override - public void bindAllWidgets(final MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) { + public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) { + mPopupDataProvider.setAllWidgets(allWidgets); Runnable r = new RunnableWithId(RUNNABLE_ID_BIND_WIDGETS) { @Override public void run() { @@ -3399,25 +3377,12 @@ public class Launcher extends BaseActivity return; } - if (mWidgetsView != null && allWidgets != null) { - Executor pendingExecutor = getPendingExecutor(); - if (pendingExecutor != null && mState != State.WIDGETS) { - pendingExecutor.execute(r); - return; - } - mWidgetsView.setWidgets(allWidgets); - } - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); if (topView != null) { topView.onWidgetsBound(); } } - public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { - return mWidgetsView.getWidgetsForPackageUser(packageUserKey); - } - @Override public void notifyWidgetProvidersChanged() { if (mWorkspace.getState().shouldUpdateWidget) { diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 3e2236682..618bd0f73 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -62,6 +62,7 @@ import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -153,7 +154,7 @@ public class LauncherModel extends BroadcastReceiver public void bindRestoreItemsChange(HashSet<ItemInfo> updates); public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); - public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets); + public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); public void onPageBoundSynchronously(int page); public void executeOnNextDraw(ViewOnDrawExecutor executor); public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index be0ed0cef..5823734e4 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -19,22 +19,15 @@ package com.android.launcher3; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; import android.content.res.Resources; import android.util.Log; import android.view.View; -import android.view.animation.AccelerateInterpolator; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimationLayerSet; -import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Thunk; -import com.android.launcher3.widget.WidgetsContainerView; /** * TODO: figure out what kind of tests we can write for this @@ -79,41 +72,8 @@ import com.android.launcher3.widget.WidgetsContainerView; */ public class LauncherStateTransitionAnimation { - /** - * animation used for the widget tray - */ - public static final int CIRCULAR_REVEAL = 0; - /** - * animation used for all apps tray - */ - public static final int PULLUP = 1; - - private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; - - /** - * Private callbacks made during transition setup. - */ - private static class PrivateTransitionCallbacks { - private final float materialRevealViewFinalAlpha; - - PrivateTransitionCallbacks(float revealAlpha) { - materialRevealViewFinalAlpha = revealAlpha; - } - - float getMaterialRevealViewStartFinalRadius() { - return 0; - } - AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, - View buttonView) { - return null; - } - void onTransitionComplete() {} - } - public static final String TAG = "LSTAnimation"; - public static final int SINGLE_FRAME_DELAY = 16; - @Thunk Launcher mLauncher; @Thunk AnimatorSet mCurrentAnimation; AllAppsTransitionController mAllAppsController; @@ -128,49 +88,63 @@ public class LauncherStateTransitionAnimation { */ public void startAnimationToAllApps(final boolean animated) { final AllAppsContainerView toView = mLauncher.getAppsView(); - final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation(); - PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { - @Override - public float getMaterialRevealViewStartFinalRadius() { - int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; - return allAppsButtonSize / 2; - } - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View allAppsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationStart(Animator animation) { - allAppsButtonView.setVisibility(View.INVISIBLE); - } - public void onAnimationEnd(Animator animation) { - allAppsButtonView.setVisibility(View.VISIBLE); - } - }; - } + + final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); + final Resources res = mLauncher.getResources(); + final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); + + final AnimationLayerSet layerViews = new AnimationLayerSet(); + + // If for some reason our views aren't initialized, don't animate + boolean initialized = toView != null; + + // Cancel the current animation + cancelAnimation(); + + playCommonTransitionAnimations(Workspace.State.NORMAL_HIDDEN, + animated, initialized, animation, layerViews); + if (!animated || !initialized) { + mAllAppsController.finishPullUp(); + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + toView.setAlpha(1.0f); + toView.setVisibility(View.VISIBLE); + + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); + return; + } + if (!FeatureFlags.LAUNCHER3_PHYSICS) { + // We are animating the content view alpha, so ensure we have a layer for it. + layerViews.addView(toView); + } + + animation.addListener(new AnimatorListenerAdapter() { @Override - void onTransitionComplete() { + public void onAnimationEnd(Animator animation) { + cleanupAnimation(); mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } - }; - // Only animate the search bar if animating from spring loaded mode back to all apps - startAnimationToOverlay( - Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, PULLUP, cb); + }); + boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); + + Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); + mCurrentAnimation = animation; + mCurrentAnimation.addListener(layerViews); + if (shouldPost) { + toView.post(startAnimRunnable); + } else { + startAnimRunnable.run(); + } } /** * Starts an animation to the widgets view. */ public void startAnimationToWidgets(final boolean animated) { - final WidgetsContainerView toView = mLauncher.getWidgetsView(); - final View buttonView = mLauncher.getWidgetsButton(); - startAnimationToOverlay( - Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL, - new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){ - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }); + // TODO: Remove this + throw new RuntimeException("This cannot happen"); } /** @@ -188,10 +162,6 @@ public class LauncherStateTransitionAnimation { if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED || mAllAppsController.isTransitioning()) { startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, - animated, PULLUP, onCompleteRunnable); - } else if (fromState == Launcher.State.WIDGETS || - fromState == Launcher.State.WIDGETS_SPRING_LOADED) { - startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, animated, onCompleteRunnable); } else { startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, @@ -200,159 +170,6 @@ public class LauncherStateTransitionAnimation { } /** - * Creates and starts a new animation to a particular overlay view. - */ - private void startAnimationToOverlay( - final Workspace.State toWorkspaceState, - final View buttonView, final BaseContainerView toView, - final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { - final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - final Resources res = mLauncher.getResources(); - final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); - final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); - - final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); - - final AnimationLayerSet layerViews = new AnimationLayerSet(); - - // If for some reason our views aren't initialized, don't animate - boolean initialized = buttonView != null; - - // Cancel the current animation - cancelAnimation(); - - final View contentView = toView.getContentView(); - playCommonTransitionAnimations(toWorkspaceState, - animated, initialized, animation, layerViews); - if (!animated || !initialized) { - if (toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { - mAllAppsController.finishPullUp(); - } - toView.setTranslationX(0.0f); - toView.setTranslationY(0.0f); - toView.setScaleX(1.0f); - toView.setScaleY(1.0f); - toView.setAlpha(1.0f); - toView.setVisibility(View.VISIBLE); - - // Show the content view - contentView.setVisibility(View.VISIBLE); - pCb.onTransitionComplete(); - return; - } - if (animType == CIRCULAR_REVEAL) { - // Setup the reveal view animation - final View revealView = toView.getRevealView(); - - int width = revealView.getMeasuredWidth(); - int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.hypot(width / 2, height / 2); - revealView.setVisibility(View.VISIBLE); - revealView.setAlpha(0f); - revealView.setTranslationY(0f); - revealView.setTranslationX(0f); - - // Calculate the final animation values - int[] buttonViewToPanelDelta = - Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); - final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha; - final float revealViewToXDrift = buttonViewToPanelDelta[0]; - final float revealViewToYDrift = buttonViewToPanelDelta[1]; - - // Create the animators - PropertyValuesHolder panelAlpha = - PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); - PropertyValuesHolder panelDriftY = - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); - PropertyValuesHolder panelDriftX = - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); - ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, - panelAlpha, panelDriftY, panelDriftX); - panelAlphaAndDrift.setDuration(revealDuration); - panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - // Play the animation - layerViews.addView(revealView); - animation.play(panelAlphaAndDrift); - - // Setup the animation for the content view - contentView.setVisibility(View.VISIBLE); - contentView.setAlpha(0f); - contentView.setTranslationY(revealViewToYDrift); - layerViews.addView(contentView); - - // Create the individual animators - ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", - revealViewToYDrift, 0); - pageDrift.setDuration(revealDuration); - pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - pageDrift.setStartDelay(itemsAlphaStagger); - animation.play(pageDrift); - - ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); - itemsAlpha.setDuration(revealDuration); - itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - itemsAlpha.setStartDelay(itemsAlphaStagger); - animation.play(itemsAlpha); - - float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); - AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( - revealView, buttonView); - Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, - startRadius, revealRadius).createRevealAnimator(revealView); - reveal.setDuration(revealDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - if (listener != null) { - reveal.addListener(listener); - } - animation.play(reveal); - - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - - // This can hold unnecessary references to views. - cleanupAnimation(); - pCb.onTransitionComplete(); - } - - }); - - toView.bringToFront(); - toView.setVisibility(View.VISIBLE); - - animation.addListener(layerViews); - toView.post(new StartAnimRunnable(animation, toView)); - mCurrentAnimation = animation; - } else if (animType == PULLUP) { - if (!FeatureFlags.LAUNCHER3_PHYSICS) { - // We are animating the content view alpha, so ensure we have a layer for it. - layerViews.addView(contentView); - } - - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - cleanupAnimation(); - pCb.onTransitionComplete(); - } - }); - boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); - - Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); - mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - if (shouldPost) { - toView.post(startAnimRunnable); - } else { - startAnimRunnable.run(); - } - } - } - - /** * Plays animations used by various transitions. */ private void playCommonTransitionAnimations( @@ -376,139 +193,20 @@ public class LauncherStateTransitionAnimation { * Starts an animation to the workspace from the apps view. */ private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final boolean animated, int type, - final Runnable onCompleteRunnable) { - // No alpha anim from all apps - PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { - @Override - float getMaterialRevealViewStartFinalRadius() { - int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; - return allAppsButtonSize / 2; - } - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View allAppsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationStart(Animator animation) { - // We set the alpha instead of visibility to ensure that the focus does not - // get taken from the all apps view - allAppsButtonView.setVisibility(View.VISIBLE); - allAppsButtonView.setAlpha(0f); - } - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - - // Show the all apps button, and focus it - allAppsButtonView.setAlpha(1f); - } - }; - } - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - // Only animate the search bar if animating to spring loaded mode from all apps - startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, - mLauncher.getStartViewForAllAppsRevealAnimation(), mLauncher.getAppsView(), - animated, type, onCompleteRunnable, cb); - } - - /** - * Starts an animation to the workspace from the widgets view. - */ - private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final boolean animated, final Runnable onCompleteRunnable) { - final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); - PrivateTransitionCallbacks cb = - new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { - @Override - public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( - final View revealView, final View widgetsButtonView) { - return new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - // Hide the reveal view - revealView.setVisibility(View.INVISIBLE); - } - }; - } - @Override - void onTransitionComplete() { - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - startAnimationToWorkspaceFromOverlay( - fromWorkspaceState, toWorkspaceState, - mLauncher.getWidgetsButton(), widgetsView, - animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); - } - - /** - * Starts an animation to the workspace from another workspace state, e.g. normal to overview. - */ - private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final boolean animated, - final Runnable onCompleteRunnable) { - final View fromWorkspace = mLauncher.getWorkspace(); - final AnimationLayerSet layerViews = new AnimationLayerSet(); - final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - - // Cancel the current animation - cancelAnimation(); - - playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews); - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - - if (animated) { - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - - // This can hold unnecessary references to views. - cleanupAnimation(); - } - }); - animation.addListener(layerViews); - fromWorkspace.post(new StartAnimRunnable(animation, null)); - mCurrentAnimation = animation; - } else /* if (!animated) */ { - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - - mCurrentAnimation = null; - } - } - /** - * Creates and starts a new animation to the workspace. - */ - private void startAnimationToWorkspaceFromOverlay( - final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final View buttonView, final BaseContainerView fromView, - final boolean animated, int animType, final Runnable onCompleteRunnable, - final PrivateTransitionCallbacks pCb) { + final AllAppsContainerView fromView = mLauncher.getAppsView(); final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); final Resources res = mLauncher.getResources(); - final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); - final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); final View toView = mLauncher.getWorkspace(); - final View revealView = fromView.getRevealView(); - final View contentView = fromView.getContentView(); final AnimationLayerSet layerViews = new AnimationLayerSet(); // If for some reason our views aren't initialized, don't animate - boolean initialized = buttonView != null; + boolean initialized = fromView != null; // Cancel the current animation cancelAnimation(); @@ -520,7 +218,7 @@ public class LauncherStateTransitionAnimation { mAllAppsController.finishPullDown(); } fromView.setVisibility(View.GONE); - pCb.onTransitionComplete(); + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); // Run any queued runnables if (onCompleteRunnable != null) { @@ -528,156 +226,82 @@ public class LauncherStateTransitionAnimation { } return; } - if (animType == CIRCULAR_REVEAL) { - // hideAppsCustomizeHelper is called in some cases when it is already hidden - // don't perform all these no-op animations. In particularly, this was causing - // the all-apps button to pop in and out. - if (fromView.getVisibility() == View.VISIBLE) { - int width = revealView.getMeasuredWidth(); - int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.hypot(width / 2, height / 2); - revealView.setVisibility(View.VISIBLE); - revealView.setAlpha(1f); - revealView.setTranslationY(0); - layerViews.addView(revealView); - - // Calculate the final animation values - int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView); - final float revealViewToXDrift = buttonViewToPanelDelta[0]; - final float revealViewToYDrift = buttonViewToPanelDelta[1]; - - // The vertical motion of the apps panel should be delayed by one frame - // from the conceal animation in order to give the right feel. We correspondingly - // shorten the duration so that the slide and conceal end at the same time. - TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0); - ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", - 0, revealViewToYDrift); - panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); - panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - panelDriftY.setInterpolator(decelerateInterpolator); - animation.play(panelDriftY); - - ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", - 0, revealViewToXDrift); - panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); - panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - panelDriftX.setInterpolator(decelerateInterpolator); - animation.play(panelDriftX); - - // Setup animation for the reveal panel alpha - if (pCb.materialRevealViewFinalAlpha != 1f) { - ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", - 1f, pCb.materialRevealViewFinalAlpha); - panelAlpha.setDuration(revealDuration); - panelAlpha.setInterpolator(decelerateInterpolator); - animation.play(panelAlpha); - } - // Setup the animation for the content view - layerViews.addView(contentView); - - // Create the individual animators - ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", - 0, revealViewToYDrift); - contentView.setTranslationY(0); - pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); - pageDrift.setInterpolator(decelerateInterpolator); - pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); - animation.play(pageDrift); - - contentView.setAlpha(1f); - ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); - itemsAlpha.setDuration(100); - itemsAlpha.setInterpolator(decelerateInterpolator); - animation.play(itemsAlpha); - - // Invalidate the scrim throughout the animation to ensure the highlight - // cutout is correct throughout. - ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); - invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mLauncher.getDragLayer().invalidateScrim(); - } - }); - animation.play(invalidateScrim); - - // Animate the all apps button - float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); - AnimatorListenerAdapter listener = - pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); - Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, - revealRadius, finalRadius).createRevealAnimator(revealView); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - reveal.setDuration(revealDuration); - reveal.setStartDelay(itemsAlphaStagger); - if (listener != null) { - reveal.addListener(listener); + // We are animating the content view alpha, so ensure we have a layer for it + layerViews.addView(toView); + + animation.addListener(new AnimatorListenerAdapter() { + boolean canceled = false; + @Override + public void onAnimationCancel(Animator animation) { + canceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (canceled) return; + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); } - animation.play(reveal); + + cleanupAnimation(); + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } - animation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - fromView.setVisibility(View.GONE); - // Run any queued runnables - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } + }); + boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); - // Reset page transforms - if (contentView != null) { - contentView.setTranslationX(0); - contentView.setTranslationY(0); - contentView.setAlpha(1); - } + Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); + mCurrentAnimation = animation; + mCurrentAnimation.addListener(layerViews); + if (shouldPost) { + fromView.post(startAnimRunnable); + } else { + startAnimRunnable.run(); + } + } - // This can hold unnecessary references to views. - cleanupAnimation(); - pCb.onTransitionComplete(); - } - }); + /** + * Starts an animation to the workspace from another workspace state, e.g. normal to overview. + */ + private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { + final View fromWorkspace = mLauncher.getWorkspace(); + final AnimationLayerSet layerViews = new AnimationLayerSet(); + final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - fromView.post(new StartAnimRunnable(animation, null)); - } else if (animType == PULLUP) { - // We are animating the content view alpha, so ensure we have a layer for it - layerViews.addView(contentView); + // Cancel the current animation + cancelAnimation(); - animation.addListener(new AnimatorListenerAdapter() { - boolean canceled = false; - @Override - public void onAnimationCancel(Animator animation) { - canceled = true; - } + playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews); + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); + if (animated) { + animation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - if (canceled) return; // Run any queued runnables if (onCompleteRunnable != null) { onCompleteRunnable.run(); } + // This can hold unnecessary references to views. cleanupAnimation(); - pCb.onTransitionComplete(); } - }); - boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); - - Runnable startAnimRunnable = new StartAnimRunnable(animation, toView); + animation.addListener(layerViews); + fromWorkspace.post(new StartAnimRunnable(animation, null)); mCurrentAnimation = animation; - mCurrentAnimation.addListener(layerViews); - if (shouldPost) { - fromView.post(startAnimRunnable); - } else { - startAnimRunnable.run(); + } else /* if (!animated) */ { + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); } + + mCurrentAnimation = null; } - return; } /** diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index f150c89c1..bdfeae162 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -92,12 +92,11 @@ public class WidgetPreviewLoader { * @return a request id which can be used to cancel the request. */ public CancellationSignal getPreview(WidgetItem item, int previewWidth, - int previewHeight, WidgetCell caller, boolean animate) { + int previewHeight, WidgetCell caller) { String size = previewWidth + "x" + previewHeight; WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); - PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller, - animate); + PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller); task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); CancellationSignal signal = new CancellationSignal(); @@ -527,19 +526,17 @@ public class WidgetPreviewLoader { private final int mPreviewHeight; private final int mPreviewWidth; private final WidgetCell mCaller; - private final boolean mAnimatePreviewIn; private final BaseActivity mActivity; @Thunk long[] mVersions; @Thunk Bitmap mBitmapToRecycle; PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth, - int previewHeight, WidgetCell caller, boolean animate) { + int previewHeight, WidgetCell caller) { mKey = key; mInfo = info; mPreviewHeight = previewHeight; mPreviewWidth = previewWidth; mCaller = caller; - mAnimatePreviewIn = animate; mActivity = BaseActivity.fromContext(mCaller.getContext()); if (DEBUG) { Log.d(TAG, String.format("%s, %s, %d, %d", @@ -595,7 +592,7 @@ public class WidgetPreviewLoader { @Override protected void onPostExecute(final Bitmap preview) { - mCaller.applyPreview(preview, mAnimatePreviewIn); + mCaller.applyPreview(preview); // Write the generated preview to the DB in the worker thread if (mVersions != null) { diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index d63ae4189..91b196eea 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -16,10 +16,8 @@ package com.android.launcher3.allapps; import android.content.Context; -import android.graphics.Color; +import android.graphics.Bitmap; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.InsetDrawable; import android.support.v7.widget.LinearLayoutManager; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -28,10 +26,11 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.RelativeLayout; import com.android.launcher3.AppInfo; -import com.android.launcher3.BaseContainerView; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.ClickShadowView; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -41,7 +40,6 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; @@ -49,9 +47,8 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.ComponentKeyMapper; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.TransformingTouchDelegate; import java.util.List; import java.util.Set; @@ -59,13 +56,17 @@ import java.util.Set; /** * The all apps view container. */ -public class AllAppsContainerView extends BaseContainerView implements DragSource, - View.OnLongClickListener, Insettable { +public class AllAppsContainerView extends RelativeLayout implements DragSource, + View.OnLongClickListener, Insettable, DeviceProfile.LauncherLayoutChangeListener, + BubbleTextView.BubbleTextShadowHandler { + + protected final Rect mBasePadding = new Rect(); private final Launcher mLauncher; private final AlphabeticalAppsList mApps; private final AllAppsGridAdapter mAdapter; private final LinearLayoutManager mLayoutManager; + private final ClickShadowView mTouchFeedbackView; private AllAppsRecyclerView mAppsRecyclerView; private SearchUiManager mSearchUiManager; @@ -78,6 +79,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private SpringAnimationHandler mSpringAnimationHandler; + private TransformingTouchDelegate mTouchDelegate; + public AllAppsContainerView(Context context) { this(context, null); } @@ -98,19 +101,69 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); + + mTouchFeedbackView = new ClickShadowView(context); + // Make the feedback view large enough to hold the blur bitmap. + int size = mLauncher.getDeviceProfile().allAppsIconSizePx + + mTouchFeedbackView.getExtraSize(); + addView(mTouchFeedbackView, size, size); } @Override - protected void updateBackground( - int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { - if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { - getRevealView().setBackground(new InsetDrawable(mBaseDrawable, - paddingLeft, paddingTop, paddingRight, paddingBottom)); - getContentView().setBackground( - new InsetDrawable(new ColorDrawable(Color.TRANSPARENT), - paddingLeft, paddingTop, paddingRight, paddingBottom)); - } else { - getRevealView().setBackground(mBaseDrawable); + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); + grid.addLauncherLayoutChangedListener(this); + + mTouchDelegate = new TransformingTouchDelegate(mAppsRecyclerView); + setTouchDelegate(mTouchDelegate); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); + grid.removeLauncherLayoutChangedListener(this); + } + + /** + * Calculate the background padding as it can change due to insets/content padding change. + */ + @Override + public void onLauncherLayoutChanged() { + DeviceProfile grid = mLauncher.getDeviceProfile(); + if (!grid.isVerticalBarLayout()) { + return; + } + + int[] padding = grid.getContainerPadding(); + int paddingLeft = padding[0]; + int paddingRight = padding[1]; + mBasePadding.set(paddingLeft, 0, paddingRight, 0); + setPadding(paddingLeft, 0, paddingRight, 0); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mTouchDelegate.setBounds( + mAppsRecyclerView.getLeft() - mBasePadding.left, + mAppsRecyclerView.getTop() - mBasePadding.top, + mAppsRecyclerView.getRight() + mBasePadding.right, + mAppsRecyclerView.getBottom() + mBasePadding.bottom); + } + + @Override + public void setPressedIcon(BubbleTextView icon, Bitmap background) { + if (icon == null || background == null) { + mTouchFeedbackView.setBitmap(null); + mTouchFeedbackView.animate().cancel(); + } else if (mTouchFeedbackView.setBitmap(background)) { + View rv = findViewById(R.id.apps_list_view); + mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv); + mTouchFeedbackView.animateShadow(); } } @@ -157,23 +210,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } - - int[] point = new int[2]; - point[0] = (int) ev.getX(); - point[1] = (int) ev.getY(); - Utilities.mapCoordInSelfToDescendant( - mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point); - // IF the MotionEvent is inside the thumb, container should not be pulled down. - if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) { - return false; - } - - // IF scroller is at the very top OR there is no scroll bar because there is probably not - // enough items to scroll, THEN it's okay for the container to be pulled down. - if (mAppsRecyclerView.getCurrentScrollY() == 0) { - return true; - } - return false; + return mAppsRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); } /** @@ -191,7 +228,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // 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. - getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() { + setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { @@ -222,9 +259,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.preMeasureViews(mAdapter); mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); - getRevealView().setVisibility(View.VISIBLE); - getContentView().setVisibility(View.VISIBLE); - getContentView().setBackground(null); + onLauncherLayoutChanged(); } public SearchUiManager getSearchUiManager() { @@ -232,11 +267,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @Override - public View getTouchDelegateTargetView() { - return mAppsRecyclerView; - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { DeviceProfile grid = mLauncher.getDeviceProfile(); // Update the number of items in the grid before we measure the view diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java deleted file mode 100644 index 517dc947e..000000000 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.allapps; - -import android.content.Context; -import android.graphics.Bitmap; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RelativeLayout; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; -import com.android.launcher3.ClickShadowView; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; - -/** - * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that - * is launching. - */ -public class AllAppsRecyclerViewContainerView extends RelativeLayout - implements BubbleTextShadowHandler { - - private final ClickShadowView mTouchFeedbackView; - - public AllAppsRecyclerViewContainerView(Context context) { - this(context, null); - } - - public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - Launcher launcher = Launcher.getLauncher(context); - DeviceProfile grid = launcher.getDeviceProfile(); - - mTouchFeedbackView = new ClickShadowView(context); - - // Make the feedback view large enough to hold the blur bitmap. - int size = grid.allAppsIconSizePx + mTouchFeedbackView.getExtraSize(); - addView(mTouchFeedbackView, size, size); - } - - @Override - public void setPressedIcon(BubbleTextView icon, Bitmap background) { - if (icon == null || background == null) { - mTouchFeedbackView.setBitmap(null); - mTouchFeedbackView.animate().cancel(); - } else if (mTouchFeedbackView.setBitmap(background)) { - View rv = findViewById(R.id.apps_list_view); - mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv); - mTouchFeedbackView.animateShadow(); - } - } -} diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index b844ba303..bb0822f95 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -294,7 +294,7 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha); updateAllAppsBg(alpha); - mAppsView.getContentView().setAlpha(alpha); + mAppsView.setAlpha(alpha); mAppsView.setTranslationY(shiftCurrent); if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { diff --git a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java deleted file mode 100644 index 9fb6b498b..000000000 --- a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 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.anim; - -public class CircleRevealOutlineProvider extends RevealOutlineAnimation { - - private int mCenterX; - private int mCenterY; - private float mRadius0; - private float mRadius1; - - /** - * @param x reveal center x - * @param y reveal center y - * @param r0 initial radius - * @param r1 final radius - */ - public CircleRevealOutlineProvider(int x, int y, float r0, float r1) { - mCenterX = x; - mCenterY = y; - mRadius0 = r0; - mRadius1 = r1; - } - - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return true; - } - - @Override - public void setProgress(float progress) { - mOutlineRadius = (1 - progress) * mRadius0 + progress * mRadius1; - - mOutline.left = (int) (mCenterX - mOutlineRadius); - mOutline.top = (int) (mCenterY - mOutlineRadius); - mOutline.right = (int) (mCenterX + mOutlineRadius); - mOutline.bottom = (int) (mCenterY + mOutlineRadius); - } -} diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index d5b5aa7cf..9aa30e7cc 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -28,6 +28,8 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsListAdapter; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -117,8 +119,8 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { } public void bindUpdatedWidgets(BgDataModel dataModel) { - final MultiHashMap<PackageItemInfo, WidgetItem> widgets - = dataModel.widgetsModel.getWidgetsMap(); + final ArrayList<WidgetListRowEntry> widgets = + dataModel.widgetsModel.getWidgetsList(mApp.getContext()); scheduleCallbackTask(new CallbackTask() { @Override public void execute(Callbacks callbacks) { diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java index b7a6b68e8..24e5b9c58 100644 --- a/src/com/android/launcher3/model/LoaderResults.java +++ b/src/com/android/launcher3/model/LoaderResults.java @@ -36,6 +36,8 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; +import com.android.launcher3.widget.WidgetsListAdapter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -362,8 +364,8 @@ public class LoaderResults { } public void bindWidgets() { - final MultiHashMap<PackageItemInfo, WidgetItem> widgets - = mBgDataModel.widgetsModel.getWidgetsMap(); + final ArrayList<WidgetListRowEntry> widgets = + mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext()); Runnable r = new Runnable() { public void run() { Callbacks callbacks = mCallbacks.get(); diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index ed900bf35..1ff0daca0 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -15,6 +15,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; @@ -22,10 +23,14 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.widget.WidgetItemComparator; +import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; /** * Widgets data model that is used by the adapters of the widget views and controllers. @@ -42,8 +47,26 @@ public class WidgetsModel { private AppFilter mAppFilter; - public synchronized MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() { - return mWidgetsList.clone(); + /** + * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row + * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s + * is not sorted. This list is sorted at the UI when using + * {@link com.android.launcher3.widget.WidgetsDiffReporter} + * + * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) + */ + public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) { + ArrayList<WidgetListRowEntry> result = new ArrayList<>(); + AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); + + WidgetItemComparator widgetComparator = new WidgetItemComparator(); + for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) { + WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); + row.titleSectionName = indexer.computeSectionName(row.pkgItem.title); + Collections.sort(row.widgets, widgetComparator); + result.add(row); + } + return result; } /** diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index aeb713479..070ac3971 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -25,6 +25,8 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.badge.BadgeInfo; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; @@ -32,6 +34,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.Collections; @@ -62,6 +65,8 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>(); /** Maps packages to their BadgeInfo's . */ private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>(); + /** Maps packages to their Widgets */ + private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>(); public PopupDataProvider(Launcher launcher) { mLauncher = launcher; @@ -265,4 +270,29 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } notificationListener.cancelNotification(notificationKey); } + + public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) { + mAllWidgets = allWidgets; + } + + public ArrayList<WidgetListRowEntry> getAllWidgets() { + return mAllWidgets; + } + + public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { + for (WidgetListRowEntry entry : mAllWidgets) { + if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { + ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); + // Remove widgets not associated with the correct user. + Iterator<WidgetItem> iterator = widgets.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().user.equals(packageUserKey.mUser)) { + iterator.remove(); + } + } + return widgets.isEmpty() ? null : widgets; + } + } + return null; + } } diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 3f7bf4214..e709b9368 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -59,8 +59,9 @@ public abstract class SystemShortcut extends ItemInfo { @Override public View.OnClickListener getOnClickListener(final Launcher launcher, final ItemInfo itemInfo) { - final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey( - itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); + final List<WidgetItem> widgets = + launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( + itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); if (widgets == null) { return null; } diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index 7b5bcdbd4..8c9a44186 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -285,7 +285,7 @@ public class RecyclerViewFastScroller extends View { return; } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(getWidth() / 2, mRv.getPaddingTop()); + canvas.translate(getWidth() / 2, mRv.getScrollBarTop()); // Draw the track float halfW = mWidth / 2; canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(), @@ -317,7 +317,7 @@ public class RecyclerViewFastScroller extends View { * Returns whether the specified point is inside the thumb bounds. */ private boolean isNearThumb(int x, int y) { - int offset = y - mRv.getPaddingTop() - mThumbOffsetY; + int offset = y - mRv.getScrollBarTop() - mThumbOffsetY; return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight; } @@ -348,7 +348,7 @@ public class RecyclerViewFastScroller extends View { private void updatePopupY(int lastTouchY) { int height = mPopupView.getHeight(); float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height) - + mRv.getPaddingTop(); + + mRv.getScrollBarTop(); top = Utilities.boundToRange(top, mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height); mPopupView.setTranslationY(top); diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java new file mode 100644 index 000000000..ee5dd66bd --- /dev/null +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2017 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.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Point; +import android.util.AttributeSet; +import android.util.Property; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.Toast; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.DeleteDropTarget; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.graphics.GradientView; +import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import com.android.launcher3.util.SystemUiController; +import com.android.launcher3.util.Themes; + +/** + * Base class for various widgets popup + */ +abstract class BaseWidgetSheet extends AbstractFloatingView + implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener { + + + protected static Property<BaseWidgetSheet, Float> TRANSLATION_SHIFT = + new Property<BaseWidgetSheet, Float>(Float.class, "translationShift") { + + @Override + public Float get(BaseWidgetSheet view) { + return view.mTranslationShift; + } + + @Override + public void set(BaseWidgetSheet view, Float value) { + view.setTranslationShift(value); + } + }; + protected static final float TRANSLATION_SHIFT_CLOSED = 1f; + protected static final float TRANSLATION_SHIFT_OPENED = 0f; + + /* Touch handling related member variables. */ + private Toast mWidgetInstructionToast; + + protected final Launcher mLauncher; + protected final SwipeDetector.ScrollInterpolator mScrollInterpolator; + protected final SwipeDetector mSwipeDetector; + protected final ObjectAnimator mOpenCloseAnimator; + + protected View mContent; + protected GradientView mGradientView; + + // range [0, 1], 0=> completely open, 1=> completely closed + protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED; + + protected boolean mNoIntercept; + + public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + + mScrollInterpolator = new SwipeDetector.ScrollInterpolator(); + mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); + + mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeDetector.finishedScrolling(); + } + }); + } + + @Override + public final void onClick(View v) { + // Let the user know that they have to long press to add a widget + if (mWidgetInstructionToast != null) { + mWidgetInstructionToast.cancel(); + } + + CharSequence msg = Utilities.wrapForTts( + getContext().getText(R.string.long_press_widget_to_add), + getContext().getString(R.string.long_accessible_way_to_add)); + mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT); + mWidgetInstructionToast.show(); + } + + @Override + public final boolean onLongClick(View v) { + if (!mLauncher.isDraggingEnabled()) return false; + + if (v instanceof WidgetCell) { + return beginDraggingWidget((WidgetCell) v); + } + return true; + } + + protected void setTranslationShift(float translationShift) { + mTranslationShift = translationShift; + mGradientView.setAlpha(1 - mTranslationShift); + mContent.setTranslationY(mTranslationShift * mContent.getHeight()); + } + + private boolean beginDraggingWidget(WidgetCell v) { + // Get the widget preview as the drag representation + WidgetImageView image = v.getWidgetView(); + + // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and + // we abort the drag. + if (image.getBitmap() == null) { + return false; + } + + int[] loc = new int[2]; + mLauncher.getDragLayer().getLocationInDragLayer(image, loc); + + new PendingItemDragHelper(v).startDrag( + image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(), + new Point(loc[0], loc[1]), this, new DragOptions()); + close(true); + return true; + } + + // + // Drag related handling methods that implement {@link DragSource} interface. + // + + @Override + public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, + boolean success) { + if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && + !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { + // Exit spring loaded mode if we have not successfully dropped or have not handled the + // drop in Workspace + mLauncher.exitSpringLoadedDragModeDelayed(true, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); + } + mLauncher.unlockScreenOrientation(false); + + if (!success) { + d.deferDragViewCleanupPostAnimation = false; + } + } + + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) { + // If we got ACTION_UP without ever returning true on intercept, + // the user never started dragging the bottom sheet. + if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) { + close(true); + return false; + } + } + + if (mNoIntercept) { + return false; + } + + int directionsToDetectScroll = mSwipeDetector.isIdleState() ? + SwipeDetector.DIRECTION_NEGATIVE : 0; + mSwipeDetector.setDetectableScrollConditions( + directionsToDetectScroll, false); + mSwipeDetector.onTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + /* SwipeDetector.Listener */ + + @Override + public void onDragStart(boolean start) { } + + @Override + public boolean onDrag(float displacement, float velocity) { + float range = mContent.getHeight(); + displacement = Utilities.boundToRange(displacement, 0, range); + setTranslationShift(displacement / range); + return true; + } + + @Override + public void onDragEnd(float velocity, boolean fling) { + if ((fling && velocity > 0) || mTranslationShift > 0.5f) { + mScrollInterpolator.setVelocityAtZero(velocity); + mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration( + velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); + close(true); + } else { + mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( + TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); + mOpenCloseAnimator.setDuration( + SwipeDetector.calculateDuration(velocity, mTranslationShift)) + .setInterpolator(new DecelerateInterpolator()); + mOpenCloseAnimator.start(); + } + } + + protected void handleClose(boolean animate, long defaultDuration) { + if (!mIsOpen || mOpenCloseAnimator.isRunning()) { + return; + } + if (animate) { + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED)); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onCloseComplete(); + } + }); + if (mSwipeDetector.isIdleState()) { + mOpenCloseAnimator + .setDuration(defaultDuration) + .setInterpolator(new AccelerateInterpolator()); + } else { + mOpenCloseAnimator.setInterpolator(mScrollInterpolator); + } + mOpenCloseAnimator.start(); + } else { + setTranslationShift(TRANSLATION_SHIFT_CLOSED); + onCloseComplete(); + } + } + + protected void onCloseComplete() { + mIsOpen = false; + mLauncher.getDragLayer().removeView(this); + mLauncher.getSystemUiController().updateUiState( + SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0); + } + + protected void setupNavBarColor() { + boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); + mLauncher.getSystemUiController().updateUiState( + SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, + isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); + } + + @Override + public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { + targetParent.containerType = ContainerType.WIDGETS; + } + + @Override + public final void logActionCommand(int command) { + // TODO: be more specific + mLauncher.getUserEventDispatcher().logActionCommand(command, ContainerType.WIDGETS); + } +} diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 40dbd523c..2ba55ab97 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -75,6 +75,9 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected CancellationSignal mActiveRequest; private boolean mAnimatePreview = true; + private boolean mApplyBitmapDeferred = false; + private Bitmap mDeferredBitmap; + protected final BaseActivity mActivity; public WidgetCell(Context context) { @@ -150,15 +153,31 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return mWidgetImage; } + /** + * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but + * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are + * ready. + * This prevents invalidates while the animation is running. + */ + public void setApplyBitmapDeferred(boolean isDeferred) { + if (mApplyBitmapDeferred != isDeferred) { + mApplyBitmapDeferred = isDeferred; + if (!mApplyBitmapDeferred && mDeferredBitmap != null) { + applyPreview(mDeferredBitmap); + mDeferredBitmap = null; + } + } + } + public void setAnimatePreview(boolean shouldAnimate) { mAnimatePreview = shouldAnimate; } public void applyPreview(Bitmap bitmap) { - applyPreview(bitmap, true); - } - - public void applyPreview(Bitmap bitmap, boolean animate) { + if (mApplyBitmapDeferred) { + mDeferredBitmap = bitmap; + return; + } if (bitmap != null) { mWidgetImage.setBitmap(bitmap, DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext())); @@ -173,15 +192,11 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } public void ensurePreview() { - ensurePreview(true); - } - - public void ensurePreview(boolean animate) { if (mActiveRequest != null) { return; } mActiveRequest = mWidgetPreviewLoader.getPreview( - mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate); + mItem, mPresetPreviewSize, mPresetPreviewSize, this); } @Override diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java index 7aa50a445..201bd1c9c 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -16,62 +16,38 @@ package com.android.launcher3.widget; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.TextView; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.DropTarget; import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.dragndrop.DragController; -import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.graphics.GradientView; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.touch.SwipeDetector; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SystemUiController; -import com.android.launcher3.util.Themes; import java.util.List; /** * Bottom sheet for the "Widgets" system shortcut in the long-press popup. */ -public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable, - SwipeDetector.Listener, View.OnClickListener, View.OnLongClickListener, - DragController.DragListener { +public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable { - private int mTranslationYOpen; - private int mTranslationYClosed; - private float mTranslationYRange; - - private Launcher mLauncher; + private static final int DEFAULT_CLOSE_DURATION = 200; private ItemInfo mOriginalItemInfo; - private ObjectAnimator mOpenCloseAnimator; private Interpolator mFastOutSlowInInterpolator; - private SwipeDetector.ScrollInterpolator mScrollInterpolator; private Rect mInsets; - private SwipeDetector mSwipeDetector; - private GradientView mGradientBackground; public WidgetsBottomSheet(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -80,23 +56,20 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setWillNotDraw(false); - mLauncher = Launcher.getLauncher(context); - mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); - mScrollInterpolator = new SwipeDetector.ScrollInterpolator(); mInsets = new Rect(); - mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); - mGradientBackground = (GradientView) mLauncher.getLayoutInflater().inflate( + + mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate( R.layout.gradient_bg, mLauncher.getDragLayer(), false); + mGradientView.setProgress(1, false); + mContent = this; } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - mTranslationYOpen = 0; - mTranslationYClosed = getMeasuredHeight(); - mTranslationYRange = mTranslationYClosed - mTranslationYOpen; + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setTranslationShift(mTranslationShift); } public void populateAndShow(ItemInfo itemInfo) { @@ -106,21 +79,21 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab onWidgetsBound(); - mLauncher.getDragLayer().addView(mGradientBackground); + mLauncher.getDragLayer().addView(mGradientView); mLauncher.getDragLayer().addView(this); - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - setTranslationY(mTranslationYClosed); mIsOpen = false; open(true); } @Override protected void onWidgetsBound() { - List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey( - mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user)); + List<WidgetItem> widgets = mLauncher.getPopupDataProvider().getWidgetsForPackageUser( + new PackageUserKey( + mOriginalItemInfo.getTargetComponent().getPackageName(), + mOriginalItemInfo.user)); - ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets); - ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list); + ViewGroup widgetRow = findViewById(R.id.widgets); + ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list); widgetCells.removeAllViews(); @@ -166,72 +139,31 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab return widget; } - @Override - public void onClick(View view) { - mLauncher.getWidgetsView().handleClick(); - } - - @Override - public boolean onLongClick(View view) { - mLauncher.getDragController().addDragListener(this); - return mLauncher.getWidgetsView().handleLongClick(view); - } - private void open(boolean animate) { if (mIsOpen || mOpenCloseAnimator.isRunning()) { return; } mIsOpen = true; - boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); - mLauncher.getSystemUiController().updateUiState( - SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, - isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); + setupNavBarColor(); if (animate) { - mOpenCloseAnimator.setValues(new PropertyListBuilder() - .translationY(mTranslationYOpen).build()); - mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSwipeDetector.finishedScrolling(); - } - }); + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator); mOpenCloseAnimator.start(); } else { - setTranslationY(mTranslationYOpen); + setTranslationShift(TRANSLATION_SHIFT_OPENED); } } @Override protected void handleClose(boolean animate) { - if (!mIsOpen || mOpenCloseAnimator.isRunning()) { - return; - } - if (animate) { - mOpenCloseAnimator.setValues(new PropertyListBuilder() - .translationY(mTranslationYClosed).build()); - mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSwipeDetector.finishedScrolling(); - onCloseComplete(); - } - }); - mOpenCloseAnimator.setInterpolator(mSwipeDetector.isIdleState() - ? mFastOutSlowInInterpolator : mScrollInterpolator); - mOpenCloseAnimator.start(); - } else { - setTranslationY(mTranslationYClosed); - onCloseComplete(); - } + handleClose(animate, DEFAULT_CLOSE_DURATION); } - private void onCloseComplete() { - mIsOpen = false; - mLauncher.getDragLayer().removeView(mGradientBackground); - mLauncher.getDragLayer().removeView(WidgetsBottomSheet.this); - mLauncher.getSystemUiController().updateUiState( - SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0); + @Override + protected void onCloseComplete() { + super.onCloseComplete(); + mLauncher.getDragLayer().removeView(mGradientView); } @Override @@ -249,83 +181,4 @@ public class WidgetsBottomSheet extends AbstractFloatingView implements Insettab setPadding(getPaddingLeft() + leftInset, getPaddingTop(), getPaddingRight() + rightInset, getPaddingBottom() + bottomInset); } - - /* SwipeDetector.Listener */ - - @Override - public void onDragStart(boolean start) { - } - - @Override - public boolean onDrag(float displacement, float velocity) { - setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen, - mTranslationYClosed)); - return true; - } - - @Override - public void setTranslationY(float translationY) { - super.setTranslationY(translationY); - if (mGradientBackground == null) return; - float p = (mTranslationYClosed - translationY) / mTranslationYRange; - boolean showScrim = p <= 0; - mGradientBackground.setProgress(p, showScrim); - } - - @Override - public void onDragEnd(float velocity, boolean fling) { - if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) { - mScrollInterpolator.setVelocityAtZero(velocity); - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity, - (mTranslationYClosed - getTranslationY()) / mTranslationYRange)); - close(true); - } else { - mIsOpen = false; - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity, - (getTranslationY() - mTranslationYOpen) / mTranslationYRange)); - open(true); - } - } - - @Override - public void logActionCommand(int command) { - // TODO: be more specific - mLauncher.getUserEventDispatcher().logActionCommand(command, ContainerType.WIDGETS); - } - - @Override - public boolean onControllerTouchEvent(MotionEvent ev) { - return mSwipeDetector.onTouchEvent(ev); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - // If we got ACTION_UP without ever returning true on intercept, - // the user never started dragging the bottom sheet. - if (!mLauncher.getDragLayer().isEventOverView(this, ev)) { - close(true); - return false; - } - } - - int directionsToDetectScroll = mSwipeDetector.isIdleState() ? - SwipeDetector.DIRECTION_NEGATIVE : 0; - mSwipeDetector.setDetectableScrollConditions( - directionsToDetectScroll, false); - mSwipeDetector.onTouchEvent(ev); - return mSwipeDetector.isDraggingOrSettling(); - } - - /* DragListener */ - - @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - // A widget or custom shortcut was dragged. - close(true); - } - - @Override - public void onDragEnd() { - } } diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java deleted file mode 100644 index 39dd0d498..000000000 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.widget; - -import android.content.Context; -import android.content.pm.LauncherApps; -import android.graphics.Point; -import android.support.v7.widget.LinearLayoutManager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.android.launcher3.BaseContainerView; -import com.android.launcher3.DeleteDropTarget; -import com.android.launcher3.DragSource; -import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.compat.AlphabeticIndexCompat; -import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.MultiHashMap; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Thunk; - -import java.util.List; - -/** - * The widgets list view container. - */ -public class WidgetsContainerView extends BaseContainerView - implements View.OnLongClickListener, View.OnClickListener, DragSource { - private static final String TAG = "WidgetsContainerView"; - private static final boolean LOGD = false; - - /* Global instances that are used inside this container. */ - @Thunk Launcher mLauncher; - - /* Recycler view related member variables */ - private WidgetsRecyclerView mRecyclerView; - private WidgetsListAdapter mAdapter; - - /* Touch handling related member variables. */ - private Toast mWidgetInstructionToast; - - public WidgetsContainerView(Context context) { - this(context, null); - } - - public WidgetsContainerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mLauncher = Launcher.getLauncher(context); - LauncherAppState apps = LauncherAppState.getInstance(context); - mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context), - apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this, - new WidgetsDiffReporter(apps.getIconCache())); - mAdapter.setNotifyListener(); - if (LOGD) { - Log.d(TAG, "WidgetsContainerView constructor"); - } - } - - @Override - public View getTouchDelegateTargetView() { - return mRecyclerView; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - } - - // - // Returns views used for launcher transitions. - // - - public void scrollToTop() { - mRecyclerView.scrollToPosition(0); - } - - // - // Touch related handling. - // - - @Override - public void onClick(View v) { - // When we have exited widget tray or are in transition, disregard clicks - if (!mLauncher.isWidgetsViewVisible() - || mLauncher.getWorkspace().isSwitchingState() - || !(v instanceof WidgetCell)) return; - - handleClick(); - } - - public void handleClick() { - // Let the user know that they have to long press to add a widget - if (mWidgetInstructionToast != null) { - mWidgetInstructionToast.cancel(); - } - - CharSequence msg = Utilities.wrapForTts( - getContext().getText(R.string.long_press_widget_to_add), - getContext().getString(R.string.long_accessible_way_to_add)); - mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT); - mWidgetInstructionToast.show(); - } - - @Override - public boolean onLongClick(View v) { - // When we have exited the widget tray, disregard long clicks - if (!mLauncher.isWidgetsViewVisible()) return false; - return handleLongClick(v); - } - - public boolean handleLongClick(View v) { - if (LOGD) { - Log.d(TAG, String.format("onLongClick [v=%s]", v)); - } - // When we are in transition, disregard long clicks - if (mLauncher.getWorkspace().isSwitchingState()) return false; - // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return false; - - return beginDragging(v); - } - - private boolean beginDragging(View v) { - if (v instanceof WidgetCell) { - if (!beginDraggingWidget((WidgetCell) v)) { - return false; - } - } else { - Log.e(TAG, "Unexpected dragging view: " + v); - } - - // We don't enter spring-loaded mode if the drag has been cancelled - if (mLauncher.getDragController().isDragging()) { - // Go into spring loaded mode (must happen before we startDrag()) - mLauncher.enterSpringLoadedDragMode(); - } - - return true; - } - - private boolean beginDraggingWidget(WidgetCell v) { - // Get the widget preview as the drag representation - WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview); - - // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and - // we abort the drag. - if (image.getBitmap() == null) { - return false; - } - - int[] loc = new int[2]; - mLauncher.getDragLayer().getLocationInDragLayer(image, loc); - - new PendingItemDragHelper(v).startDrag( - image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(), - new Point(loc[0], loc[1]), this, new DragOptions()); - return true; - } - - // - // Drag related handling methods that implement {@link DragSource} interface. - // - - @Override - public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, - boolean success) { - if (LOGD) { - Log.d(TAG, "onDropCompleted"); - } - if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { - // Exit spring loaded mode if we have not successfully dropped or have not handled the - // drop in Workspace - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); - } - mLauncher.unlockScreenOrientation(false); - - if (!success) { - d.deferDragViewCleanupPostAnimation = false; - } - } - - /** - * Initialize the widget data model. - */ - public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) { - mAdapter.setWidgets(model); - - View loader = getContentView().findViewById(R.id.loader); - if (loader != null) { - ((ViewGroup) getContentView()).removeView(loader); - } - } - - public boolean isEmpty() { - return mAdapter.getItemCount() == 0; - } - - public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { - return mAdapter.copyWidgetsForPackageUser(packageUserKey); - } - - @Override - public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { - targetParent.containerType = ContainerType.WIDGETS; - } -}
\ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java index 52deec32b..d67f40365 100644 --- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java +++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java @@ -16,6 +16,7 @@ package com.android.launcher3.widget; +import android.support.v7.widget.RecyclerView; import android.util.Log; import com.android.launcher3.IconCache; @@ -26,26 +27,18 @@ import java.util.ArrayList; import java.util.Iterator; /** - * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly. + * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter} + * methods accordingly. */ public class WidgetsDiffReporter { - private final boolean DEBUG = false; - private final String TAG = "WidgetsDiffReporter"; - private final IconCache mIconCache; - private NotifyListener mListener; + private static final boolean DEBUG = false; + private static final String TAG = "WidgetsDiffReporter"; - public interface NotifyListener { - void notifyDataSetChanged(); - void notifyItemChanged(int index); - void notifyItemInserted(int index); - void notifyItemRemoved(int index); - } + private final IconCache mIconCache; + private final RecyclerView.Adapter mListener; - public WidgetsDiffReporter(IconCache iconCache) { + public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) { mIconCache = iconCache; - } - - public void setListener(NotifyListener listener) { mListener = listener; } @@ -55,9 +48,17 @@ public class WidgetsDiffReporter { Log.d(TAG, "process oldEntries#=" + currentEntries.size() + " newEntries#=" + newEntries.size()); } - if (currentEntries.size() == 0 && newEntries.size() > 0) { - currentEntries.addAll(newEntries); - mListener.notifyDataSetChanged(); + // Early exit if either of the list is empty + if (currentEntries.isEmpty() || newEntries.isEmpty()) { + // Skip if both list are empty. + // On rotation, we open the widget tray with empty. Then try to fetch the list again + // when the animation completes (which still gives empty). And we get the final result + // when the bind actually completes. + if (currentEntries.size() != newEntries.size()) { + currentEntries.clear(); + currentEntries.addAll(newEntries); + mListener.notifyDataSetChanged(); + } return; } ArrayList<WidgetListRowEntry> orgEntries = diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java new file mode 100644 index 000000000..72277a253 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017 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.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AnimationUtils; + +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener; +import com.android.launcher3.R; + +/** + * Popup for showing the full list of available widgets + */ +public class WidgetsFullSheet extends BaseWidgetSheet + implements Insettable, ProviderChangedListener { + + private static final long DEFAULT_OPEN_DURATION = 267; + private static final long FADE_IN_DURATION = 150; + private static final float VERTICAL_START_POSITION = 0.3f; + + private static final Rect sTempRect = new Rect(); + + private final Rect mInsets = new Rect(); + + private final WidgetsListAdapter mAdapter; + + private View mNavBarScrim; + private WidgetsRecyclerView mRecyclerView; + + public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + LauncherAppState apps = LauncherAppState.getInstance(context); + mAdapter = new WidgetsListAdapter(context, + LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(), + this, this); + } + + public WidgetsFullSheet(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContent = findViewById(R.id.container); + mNavBarScrim = findViewById(R.id.nav_bar_bg); + + mRecyclerView = findViewById(R.id.widgets_list_view); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setApplyBitmapDeferred(true, mRecyclerView); + + mGradientView = findViewById(R.id.gradient_bg); + mGradientView.setProgress(1, false); + + onWidgetsBound(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mLauncher.getAppWidgetHost().addProviderChangeListener(this); + notifyWidgetProvidersChanged(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mLauncher.getAppWidgetHost().removeProviderChangeListener(this); + } + + @Override + public void setInsets(Rect insets) { + mInsets.set(insets); + + mNavBarScrim.getLayoutParams().height = insets.bottom; + mRecyclerView.setPadding( + mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), + mRecyclerView.getPaddingRight(), insets.bottom); + if (insets.bottom > 0) { + setupNavBarColor(); + } + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthUsed; + if (mInsets.bottom > 0) { + // If we have bottom insets, we do not show the scrim as it would overlap + // with the navbar scrim + mGradientView.setVisibility(View.INVISIBLE); + widthUsed = 0; + } else { + mLauncher.getDeviceProfile().getWorkspacePadding(sTempRect); + widthUsed = Math.max(sTempRect.left + sTempRect.right, + 2 * (mInsets.left + mInsets.right)); + } + + int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx; + measureChildWithMargins(mContent, widthMeasureSpec, + widthUsed, heightMeasureSpec, heightUsed); + measureChild(mGradientView, widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mGradientView.getMeasuredWidth(), mGradientView.getMeasuredHeight()); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int width = r - l; + int height = b - t; + mGradientView.layout(0, 0, width, height); + + // Content is laid out as center bottom aligned + int contentWidth = mContent.getMeasuredWidth(); + int contentLeft = (width - contentWidth) / 2; + mContent.layout(contentLeft, height - mContent.getMeasuredHeight(), + contentLeft + contentWidth, height); + + setTranslationShift(mTranslationShift); + } + + @Override + public void notifyWidgetProvidersChanged() { + mLauncher.refreshAndBindWidgetsForPackageUser(null); + } + + @Override + protected void onWidgetsBound() { + mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets()); + } + + private void open(boolean animate) { + if (mIsOpen) { + return; + } + mIsOpen = true; + if (animate) { + if (mLauncher.getDragLayer().getInsets().bottom > 0) { + mContent.setAlpha(0); + setTranslationShift(VERTICAL_START_POSITION); + } + mOpenCloseAnimator.setValues( + PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); + mOpenCloseAnimator + .setDuration(DEFAULT_OPEN_DURATION) + .setInterpolator(AnimationUtils.loadInterpolator( + getContext(), android.R.interpolator.linear_out_slow_in)); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRecyclerView.setLayoutFrozen(false); + mAdapter.setApplyBitmapDeferred(false, mRecyclerView); + mOpenCloseAnimator.removeListener(this); + } + }); + post(new Runnable() { + @Override + public void run() { + mRecyclerView.setLayoutFrozen(true); + mOpenCloseAnimator.start(); + mContent.animate().alpha(1).setDuration(FADE_IN_DURATION); + } + }); + } else { + setTranslationShift(TRANSLATION_SHIFT_OPENED); + mAdapter.setApplyBitmapDeferred(false, mRecyclerView); + } + } + + @Override + protected void handleClose(boolean animate) { + handleClose(animate, DEFAULT_OPEN_DURATION); + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_WIDGETS_FULL_SHEET) != 0; + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + // Disable swipe down when recycler view is scrolling + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = false; + if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) { + mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); + } + } + return super.onControllerInterceptTouchEvent(ev); + } + + public static WidgetsFullSheet show(Launcher launcher, boolean animate) { + WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater() + .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false); + launcher.getDragLayer().addView(sheet); + sheet.open(animate); + return sheet; + } +} diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 6b1800c67..0147ea427 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -25,13 +25,11 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; +import com.android.launcher3.IconCache; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.compat.AlphabeticIndexCompat; -import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.LabelComparator; -import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -39,7 +37,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.Map; /** * List view adapter for the widget tray. @@ -56,7 +53,6 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private final WidgetPreviewLoader mWidgetPreviewLoader; private final LayoutInflater mLayoutInflater; - private final AlphabeticIndexCompat mIndexer; private final OnClickListener mIconClickListener; private final OnLongClickListener mIconLongClickListener; @@ -64,56 +60,43 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>(); private final WidgetsDiffReporter mDiffReporter; + private boolean mApplyBitmapDeferred; + public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, - WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat, - OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - WidgetsDiffReporter diffReporter) { + WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, + OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { mLayoutInflater = layoutInflater; mWidgetPreviewLoader = widgetPreviewLoader; - mIndexer = indexCompat; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); - mDiffReporter = diffReporter; + mDiffReporter = new WidgetsDiffReporter(iconCache, this); } - public void setNotifyListener() { - mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() { - @Override - public void notifyDataSetChanged() { - WidgetsListAdapter.this.notifyDataSetChanged(); - } - - @Override - public void notifyItemChanged(int index) { - WidgetsListAdapter.this.notifyItemChanged(index); - } - - @Override - public void notifyItemInserted(int index) { - WidgetsListAdapter.this.notifyItemInserted(index); - } - - @Override - public void notifyItemRemoved(int index) { - WidgetsListAdapter.this.notifyItemRemoved(index); + /** + * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv} + * + * @see WidgetCell#setApplyBitmapDeferred(boolean) + */ + public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) { + mApplyBitmapDeferred = isDeferred; + + for (int i = rv.getChildCount() - 1; i >= 0; i--) { + WidgetsRowViewHolder holder = (WidgetsRowViewHolder) + rv.getChildViewHolder(rv.getChildAt(i)); + for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) { + View v = holder.cellContainer.getChildAt(j); + if (v instanceof WidgetCell) { + ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred); + } } - }); + } } /** * Update the widget list. */ - public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) { - ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>(); - - WidgetItemComparator widgetComparator = new WidgetItemComparator(); - for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) { - WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); - row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title); - Collections.sort(row.widgets, widgetComparator); - tempEntries.add(row); - } + public void setWidgets(ArrayList<WidgetListRowEntry> tempEntries) { WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator(); Collections.sort(tempEntries, rowComparator); mDiffReporter.process(mEntries, tempEntries, rowComparator); @@ -128,26 +111,6 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { return mEntries.get(pos).titleSectionName; } - /** - * Copies and returns the widgets associated with the package and user of the ComponentKey. - */ - public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) { - for (WidgetListRowEntry entry : mEntries) { - if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { - ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); - // Remove widgets not associated with the correct user. - Iterator<WidgetItem> iterator = widgets.iterator(); - while (iterator.hasNext()) { - if (!iterator.next().user.equals(packageUserKey.mUser)) { - iterator.remove(); - } - } - return widgets.isEmpty() ? null : widgets; - } - } - return null; - } - @Override public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) { WidgetListRowEntry entry = mEntries.get(pos); @@ -194,6 +157,7 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { for (int i=0; i < infoList.size(); i++) { WidgetCell widget = (WidgetCell) row.getChildAt(2*i); widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader); + widget.setApplyBitmapDeferred(mApplyBitmapDeferred); widget.ensurePreview(); widget.setVisibility(View.VISIBLE); @@ -253,5 +217,4 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString()); } } - } diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index 9730a82aa..89c88a4e7 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -17,12 +17,12 @@ package com.android.launcher3.widget; import android.content.Context; -import android.graphics.Color; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.view.View; import com.android.launcher3.BaseRecyclerView; +import com.android.launcher3.R; /** * The widgets recycler view. @@ -32,6 +32,8 @@ public class WidgetsRecyclerView extends BaseRecyclerView { private static final String TAG = "WidgetsRecyclerView"; private WidgetsListAdapter mAdapter; + private final int mScrollbarTop; + public WidgetsRecyclerView(Context context) { this(context, null); } @@ -43,6 +45,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView { public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { // API 21 and below only support 3 parameter ctor. super(context, attrs, defStyleAttr); + mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); } public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, @@ -130,13 +133,16 @@ public class WidgetsRecyclerView extends BaseRecyclerView { @Override protected int getAvailableScrollHeight() { View child = getChildAt(0); - int height = child.getMeasuredHeight() * mAdapter.getItemCount(); - int totalHeight = getPaddingTop() + height + getPaddingBottom(); - int availableScrollHeight = totalHeight - getScrollbarTrackHeight(); - return availableScrollHeight; + return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight() + - mScrollbarTop; } private boolean isModelNotReady() { return mAdapter.getItemCount() == 0; } + + @Override + public int getScrollBarTop() { + return mScrollbarTop; + } }
\ No newline at end of file |