Merge "Add WidgetsAndMore bottom sheet" into ub-launcher3-dorval

This commit is contained in:
Tony Wickham
2017-03-30 23:21:39 +00:00
committed by Android (Google) Code Review
11 changed files with 492 additions and 34 deletions
@@ -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();
-1
View File
@@ -88,7 +88,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;
@@ -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;
@@ -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;
}
@@ -530,15 +530,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
> ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
/**
* 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
@@ -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);
}
}
}
@@ -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() {
}
}
@@ -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;