diff options
author | Tony Wickham <twickham@google.com> | 2017-03-20 17:12:24 -0700 |
---|---|---|
committer | Tony Wickham <twickham@google.com> | 2017-03-30 15:48:53 -0700 |
commit | 50e5165b78c75ccb022f0954699f49c579547115 (patch) | |
tree | 754a3dfe45fbed74cb3b122312304cf406226ebf /src | |
parent | 8eb0de133154666cd20d0244953ee755b626b44a (diff) | |
download | android_packages_apps_Trebuchet-50e5165b78c75ccb022f0954699f49c579547115.tar.gz android_packages_apps_Trebuchet-50e5165b78c75ccb022f0954699f49c579547115.tar.bz2 android_packages_apps_Trebuchet-50e5165b78c75ccb022f0954699f49c579547115.zip |
Add WidgetsAndMore bottom sheet
- Contains two rows, one for widgets, and one for "configurable
shortcuts" that have customization activities
- Extends AbstractFloatingView and uses VerticalPullDetector for
touch interactions
- No way to show this currently; will add options to popup in followup
Bug: 34940468
Change-Id: Iab62c2cb89428f91119c9c86f9db886496c321fd
Diffstat (limited to 'src')
8 files changed, 376 insertions, 34 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index bd1268651..dbef05411 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -16,9 +16,11 @@ package com.android.launcher3; +import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.IntDef; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; @@ -32,11 +34,16 @@ import java.lang.annotation.RetentionPolicy; */ public abstract class AbstractFloatingView extends LinearLayout { - @IntDef(flag = true, value = {TYPE_FOLDER, TYPE_POPUP_CONTAINER_WITH_ARROW}) + @IntDef(flag = true, value = { + TYPE_FOLDER, + TYPE_POPUP_CONTAINER_WITH_ARROW, + TYPE_WIDGETS_AND_MORE + }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} public static final int TYPE_FOLDER = 1 << 0; public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1; + public static final int TYPE_WIDGETS_AND_MORE = 1 << 2; protected boolean mIsOpen; @@ -48,6 +55,15 @@ public abstract class AbstractFloatingView extends LinearLayout { super(context, attrs, defStyleAttr); } + /** + * We need to handle touch events to prevent them from falling through to the workspace below. + */ + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + return true; + } + public final void close(boolean animate) { animate &= !Utilities.isPowerSaverOn(getContext()); handleClose(animate); @@ -119,7 +135,8 @@ public abstract class AbstractFloatingView extends LinearLayout { } public static AbstractFloatingView getTopOpenView(Launcher launcher) { - return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW); + return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW + | TYPE_WIDGETS_AND_MORE); } public abstract int getLogContainerType(); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 84a593058..1ad97cc01 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -89,7 +89,6 @@ import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PinItemRequestCompat; -import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 23a257767..b04d5b747 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -61,6 +61,7 @@ import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; +import com.android.launcher3.widget.WidgetsAndMore; import java.util.ArrayList; @@ -246,6 +247,12 @@ public class DragLayer extends InsettableFrameLayout { return true; } + WidgetsAndMore widgetsAndMore = WidgetsAndMore.getOpen(mLauncher); + if (widgetsAndMore != null && widgetsAndMore.onControllerInterceptTouchEvent(ev)) { + mActiveController = widgetsAndMore; + return true; + } + if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) { // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.) mActiveController = mPinchListener; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 3d2ffb4ff..93c9ea8e3 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -388,15 +388,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC return mFolderIcon; } - /** - * We need to handle touch events to prevent them from falling through to the workspace below. - */ - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return true; - } - public void setDragController(DragController dragController) { mDragController = dragController; } diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index b2018b92b..fffcb7107 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -531,15 +531,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } /** - * We need to handle touch events to prevent them from falling through to the workspace below. - */ - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return true; - } - - /** * Updates the notification header to reflect the badge info. Since this can be called * for any badge info (not necessarily the one associated with this app), we first * check that the ItemInfo matches the one of this popup. diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 3bf622e8f..72effd471 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -73,6 +73,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private StylusEventHelper mStylusEventHelper; protected CancellationSignal mActiveRequest; + private boolean mAnimatePreview = true; protected final BaseActivity mActivity; @@ -149,13 +150,21 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return mWidgetImage; } + public void setAnimatePreview(boolean shouldAnimate) { + mAnimatePreview = shouldAnimate; + } + public void applyPreview(Bitmap bitmap) { if (bitmap != null) { mWidgetImage.setBitmap(bitmap, DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext())); - mWidgetImage.setAlpha(0f); - ViewPropertyAnimator anim = mWidgetImage.animate(); - anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS); + if (mAnimatePreview) { + mWidgetImage.setAlpha(0f); + ViewPropertyAnimator anim = mWidgetImage.animate(); + anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS); + } else { + mWidgetImage.setAlpha(1f); + } } } diff --git a/src/com/android/launcher3/widget/WidgetsAndMore.java b/src/com/android/launcher3/widget/WidgetsAndMore.java new file mode 100644 index 000000000..3ed2530b3 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetsAndMore.java @@ -0,0 +1,326 @@ +/* + * 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.content.Context; +import android.graphics.Rect; +import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +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.allapps.VerticalPullDetector; +import com.android.launcher3.anim.PropertyListBuilder; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.util.TouchController; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Bottom sheet for the "Widgets & more" long-press option. + */ +public class WidgetsAndMore extends AbstractFloatingView implements Insettable, TouchController, + VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener, + DragController.DragListener { + + private int mTranslationYOpen; + private int mTranslationYClosed; + private float mTranslationYRange; + + private Launcher mLauncher; + private ObjectAnimator mOpenCloseAnimator; + private Interpolator mFastOutSlowInInterpolator; + private VerticalPullDetector.ScrollInterpolator mScrollInterpolator; + private Rect mInsets; + private boolean mWasNavBarLight; + private VerticalPullDetector mVerticalPullDetector; + + public WidgetsAndMore(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WidgetsAndMore(Context context, AttributeSet attrs, int defStyleAttr) { + super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr); + setWillNotDraw(false); + mLauncher = Launcher.getLauncher(context); + mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this); + mFastOutSlowInInterpolator = new FastOutSlowInInterpolator(); + mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator(); + mInsets = new Rect(); + mVerticalPullDetector = new VerticalPullDetector(context); + mVerticalPullDetector.setListener(this); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mTranslationYOpen = 0; + mTranslationYClosed = getMeasuredHeight(); + mTranslationYRange = mTranslationYClosed - mTranslationYOpen; + } + + public void populateAndShow(ItemInfo itemInfo, List<WidgetItem> widgets) { + ((TextView) findViewById(R.id.title)).setText(itemInfo.title); + + List<WidgetItem> shortcuts = new ArrayList<>(); + // Transfer configurable widgets to shortcuts + Iterator<WidgetItem> widgetsIter = widgets.iterator(); + WidgetItem nextWidget; + while (widgetsIter.hasNext()) { + nextWidget = widgetsIter.next(); + if (nextWidget.activityInfo != null) { + shortcuts.add(nextWidget); + widgetsIter.remove(); + } + } + + ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets); + ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list); + + ViewGroup shortcutRow = (ViewGroup) findViewById(R.id.shortcuts); + ViewGroup shortcutCells = (ViewGroup) shortcutRow.findViewById(R.id.widgets_cell_list); + + for (int i = 0; i < widgets.size(); i++) { + addItemCell(widgetCells); + if (i < widgets.size() - 1) { + addDivider(widgetCells); + } + } + for (int i = 0; i < shortcuts.size(); i++) { + addItemCell(shortcutCells); + if (i < shortcuts.size() - 1) { + addDivider(shortcutCells); + } + } + + // Bind the views in the horizontal tray regions. + if (widgetCells.getChildCount() > 0) { + for (int i = 0; i < widgets.size(); i++) { + WidgetCell widget = (WidgetCell) widgetCells.getChildAt(i*2); // skip dividers + widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher) + .getWidgetCache()); + widget.ensurePreview(); + widget.setVisibility(View.VISIBLE); + } + } else { + removeView(findViewById(R.id.widgets_header)); + } + if (shortcutCells.getChildCount() > 0) { + for (int i = 0; i < shortcuts.size(); i++) { + WidgetCell shortcut = (WidgetCell) shortcutCells.getChildAt(i*2); // skip dividers + shortcut.applyFromCellItem(shortcuts.get(i), LauncherAppState.getInstance(mLauncher) + .getWidgetCache()); + shortcut.ensurePreview(); + shortcut.setVisibility(View.VISIBLE); + } + } else { + removeView(findViewById(R.id.shortcuts_header)); + } + + mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility() + & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0; + mLauncher.getDragLayer().addView(this); + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + setTranslationY(mTranslationYClosed); + mIsOpen = false; + open(true); + } + + private void addDivider(ViewGroup parent) { + LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true); + } + + private void addItemCell(ViewGroup parent) { + WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate( + R.layout.widget_cell, parent, false); + + widget.setOnClickListener(this); + widget.setOnLongClickListener(this); + widget.setAnimatePreview(false); + + parent.addView(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; + setLightNavBar(true); + if (animate) { + mOpenCloseAnimator.setValues(new PropertyListBuilder() + .translationY(mTranslationYOpen).build()); + mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mVerticalPullDetector.finishedScrolling(); + } + }); + mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator); + mOpenCloseAnimator.start(); + } else { + setTranslationY(mTranslationYOpen); + } + } + + @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) { + mIsOpen = false; + mVerticalPullDetector.finishedScrolling(); + ((ViewGroup) getParent()).removeView(WidgetsAndMore.this); + setLightNavBar(mWasNavBarLight); + } + }); + mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState() + ? mFastOutSlowInInterpolator : mScrollInterpolator); + mOpenCloseAnimator.start(); + } else { + setTranslationY(mTranslationYClosed); + setLightNavBar(mWasNavBarLight); + mIsOpen = false; + } + } + + private void setLightNavBar(boolean lightNavBar) { + mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */); + } + + @Override + protected boolean isOfType(@FloatingViewType int type) { + return (type & TYPE_WIDGETS_AND_MORE) != 0; + } + + @Override + public int getLogContainerType() { + return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific + } + + /** + * Returns a WidgetsAndMore which is already open or null + */ + public static WidgetsAndMore getOpen(Launcher launcher) { + return getOpenView(launcher, TYPE_WIDGETS_AND_MORE); + } + + @Override + public void setInsets(Rect insets) { + // Extend behind left, right, and bottom insets. + int leftInset = insets.left - mInsets.left; + int rightInset = insets.right - mInsets.right; + int bottomInset = insets.bottom - mInsets.bottom; + mInsets.set(insets); + setPadding(getPaddingLeft() + leftInset, getPaddingTop(), + getPaddingRight() + rightInset, getPaddingBottom() + bottomInset); + } + + /* VerticalPullDetector.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 onDragEnd(float velocity, boolean fling) { + if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) { + mScrollInterpolator.setVelocityAtZero(velocity); + mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity, + (mTranslationYClosed - getTranslationY()) / mTranslationYRange)); + close(true); + } else { + mIsOpen = false; + mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity, + (getTranslationY() - mTranslationYOpen) / mTranslationYRange)); + open(true); + } + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mVerticalPullDetector.onTouchEvent(ev); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ? + VerticalPullDetector.DIRECTION_DOWN : 0; + mVerticalPullDetector.setDetectableScrollConditions( + directionsToDetectScroll, false); + mVerticalPullDetector.onTouchEvent(ev); + return mVerticalPullDetector.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 index b2321a7d3..ba6ed41f6 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -29,13 +29,10 @@ 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.IconCache; 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.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.model.PackageItemInfo; @@ -55,8 +52,6 @@ public class WidgetsContainerView extends BaseContainerView /* Global instances that are used inside this container. */ @Thunk Launcher mLauncher; - private DragController mDragController; - private IconCache mIconCache; /* Recycler view related member variables */ private WidgetsRecyclerView mRecyclerView; @@ -76,9 +71,7 @@ public class WidgetsContainerView extends BaseContainerView public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mLauncher = Launcher.getLauncher(context); - mDragController = mLauncher.getDragController(); mAdapter = new WidgetsListAdapter(this, this, context); - mIconCache = LauncherAppState.getInstance(context).getIconCache(); if (LOGD) { Log.d(TAG, "WidgetsContainerView constructor"); } @@ -116,6 +109,10 @@ public class WidgetsContainerView extends BaseContainerView || 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(); @@ -130,14 +127,19 @@ public class WidgetsContainerView extends BaseContainerView @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)); } // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; - // When we have exited all apps or are in transition, disregard long clicks - if (!mLauncher.isWidgetsViewVisible() || - mLauncher.getWorkspace().isSwitchingState()) return false; + // 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; |