Remove gap between popup items

- Unround interior corners
- Update colors (shortcuts are gray when next to
  notifications, notifications always white)
- Clean up animation to animate entire popup with
  simple reveal instead of individual items
  animating with reveal and icon scale

Bug: 35766387
Bug: 36110804
Change-Id: I33685d53e2db3904731676123dc230be4dabb5d4
This commit is contained in:
Tony 2017-05-10 13:05:32 -05:00
parent d5645d406b
commit acaf5b3a37
14 changed files with 223 additions and 481 deletions

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="@dimen/bg_round_rect_radius" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/popup_item_divider_height"
android:background="?android:attr/listDivider"/>

View File

@ -19,9 +19,7 @@
android:id="@+id/notification_view"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
android:elevation="@dimen/deep_shortcuts_elevation"
android:background="@drawable/bg_white_round_rect"
android:backgroundTint="@color/notification_color_beneath">
android:elevation="@dimen/deep_shortcuts_elevation">
<RelativeLayout
android:layout_width="match_parent"
@ -35,7 +33,7 @@
android:layout_height="@dimen/notification_header_height"
android:paddingStart="@dimen/notification_padding_start"
android:paddingEnd="@dimen/notification_padding_end"
android:background="@color/popup_header_background_color"
android:background="@color/popup_background_color"
android:elevation="@dimen/notification_elevation">
<TextView
android:id="@+id/notification_text"

View File

@ -19,8 +19,7 @@
android:id="@+id/shortcuts_view"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
android:elevation="@dimen/deep_shortcuts_elevation"
android:background="@drawable/bg_white_round_rect">
android:elevation="@dimen/deep_shortcuts_elevation">
<LinearLayout
android:id="@+id/deep_shortcuts"

View File

@ -31,7 +31,7 @@
<color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
<!-- Popup container -->
<color name="popup_header_background_color">#EEEEEE</color> <!-- Gray 200 -->
<color name="popup_header_background_color">#F5F5F5</color> <!-- Gray 100 -->
<color name="popup_background_color">#FFF</color>
<color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
<color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 -->

View File

@ -124,11 +124,8 @@
<item type="id" name="preview_image_id" />
<!-- Popup items -->
<integer name="config_deepShortcutOpenDuration">220</integer>
<integer name="config_deepShortcutArrowOpenDuration">80</integer>
<integer name="config_deepShortcutOpenStagger">40</integer>
<integer name="config_deepShortcutCloseDuration">150</integer>
<integer name="config_deepShortcutCloseStagger">20</integer>
<integer name="config_popupOpenCloseDuration">220</integer>
<integer name="config_popupArrowOpenDuration">80</integer>
<integer name="config_removeNotificationViewDuration">300</integer>
<!-- Accessibility actions -->

View File

@ -148,7 +148,6 @@
<dimen name="deep_shortcuts_elevation">9dp</dimen>
<dimen name="bg_popup_item_width">220dp</dimen>
<dimen name="bg_popup_item_height">56dp</dimen>
<dimen name="popup_items_spacing">4dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>

View File

@ -1,41 +0,0 @@
/*
* 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.anim;
import android.graphics.Rect;
/**
* Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
* For now, we assume the height is added/removed from the bottom.
*/
public class PillHeightRevealOutlineProvider extends PillRevealOutlineProvider {
private final int mNewHeight;
public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) {
super(0, 0, pillRect, radius);
mOutline.set(pillRect);
mNewHeight = newHeight;
}
@Override
public void setProgress(float progress) {
mOutline.top = 0;
int heightDifference = mPillRect.height() - mNewHeight;
mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress));
}
}

View File

@ -1,68 +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;
import android.graphics.Rect;
import android.view.ViewOutlineProvider;
/**
* A {@link ViewOutlineProvider} that animates a reveal in a "pill" shape.
* A pill is simply a round rect, but we assume the width is greater than
* the height and that the radius is equal to half the height.
*/
public class PillRevealOutlineProvider extends RevealOutlineAnimation {
private int mCenterX;
private int mCenterY;
private float mFinalRadius;
protected Rect mPillRect;
/**
* @param x reveal center x
* @param y reveal center y
* @param pillRect round rect that represents the final pill shape
*/
public PillRevealOutlineProvider(int x, int y, Rect pillRect) {
this(x, y, pillRect, pillRect.height() / 2f);
}
public PillRevealOutlineProvider(int x, int y, Rect pillRect, float radius) {
mCenterX = x;
mCenterY = y;
mPillRect = pillRect;
mOutlineRadius = mFinalRadius = radius;
}
@Override
public boolean shouldRemoveElevationDuringAnimation() {
return false;
}
@Override
public void setProgress(float progress) {
// Assumes width is greater than height.
int centerToEdge = Math.max(mCenterX, mPillRect.width() - mCenterX);
int currentSize = (int) (progress * centerToEdge);
// Bound the outline to the final pill shape defined by mPillRect.
mOutline.left = Math.max(mPillRect.left, mCenterX - currentSize);
mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize);
mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize);
mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize);
mOutlineRadius = Math.min(mFinalRadius, mOutline.height() / 2);
}
}

View File

@ -18,6 +18,11 @@ package com.android.launcher3.anim;
import android.graphics.Rect;
import com.android.launcher3.popup.PopupContainerWithArrow;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
/**
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
* and two {@link Rect}s.
@ -32,12 +37,21 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
private final Rect mStartRect;
private final Rect mEndRect;
private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect) {
this(startRadius, endRadius, startRect, endRect,
ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
}
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect, int roundedCorners) {
mStartRadius = startRadius;
mEndRadius = endRadius;
mStartRect = startRect;
mEndRect = endRect;
mRoundedCorners = roundedCorners;
}
@Override
@ -51,7 +65,13 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
mOutline.top -= mOutlineRadius;
}
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
mOutline.bottom += mOutlineRadius;
}
}
}

View File

@ -21,7 +21,6 @@ import android.app.Notification;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@ -30,7 +29,7 @@ import android.widget.TextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
import com.android.launcher3.popup.PopupItemView;
@ -87,9 +86,10 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
}
public Animator animateHeightRemoval(int heightToRemove) {
final int newHeight = getHeight() - heightToRemove;
return new PillHeightRevealOutlineProvider(mPillRect,
getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */);
Rect endRect = new Rect(mPillRect);
endRect.bottom -= heightToRemove;
return new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
mPillRect, endRect, mRoundedCorners).createRevealAnimator(this, false);
}
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
@ -162,13 +162,6 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
}
}
@Override
public int getArrowColor(boolean isArrowAttachedToBottom) {
return ContextCompat.getColor(getContext(), isArrowAttachedToBottom
? R.color.popup_background_color
: R.color.popup_header_background_color);
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {

View File

@ -20,19 +20,22 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.CornerPathEffect;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -40,7 +43,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import com.android.launcher3.AbstractFloatingView;
@ -53,13 +56,13 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LogAccelerateInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@ -73,6 +76,8 @@ import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutsItemView;
import com.android.launcher3.util.PackageUserKey;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -89,6 +94,16 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
DragController.DragListener {
public static final int ROUNDED_TOP_CORNERS = 1 << 0;
public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
@IntDef(flag = true, value = {
ROUNDED_TOP_CORNERS,
ROUNDED_BOTTOM_CORNERS
})
@Retention(RetentionPolicy.SOURCE)
public @interface RoundedCornerFlags {}
protected final Launcher mLauncher;
private final int mStartDragThreshold;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
@ -107,6 +122,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
protected Animator mOpenCloseAnimator;
private boolean mDeferContainerRemoval;
private AnimatorSet mReduceHeightAnimatorSet;
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@ -222,6 +239,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
mArrow.setPivotX(arrowWidth / 2);
mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
animateOpen();
mLauncher.getDragController().addDragListener(this);
@ -238,46 +256,66 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate,
boolean notificationFooterHasIcons) {
final Resources res = getResources();
final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
final LayoutInflater inflater = mLauncher.getLayoutInflater();
int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
int numItems = itemTypesToPopulate.length;
for (int i = 0; i < numItems; i++) {
PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
PopupPopulator.Item prevItemTypeToPopulate =
i > 0 ? itemTypesToPopulate[i - 1] : null;
PopupPopulator.Item nextItemTypeToPopulate =
i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
&& itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
&& itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
mNotificationItemView = (NotificationItemView) item;
int footerHeight = notificationFooterHasIcons ?
res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
if (shouldUnroundTopCorners) {
roundedCorners &= ~ROUNDED_TOP_CORNERS;
}
if (shouldUnroundBottomCorners) {
roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
}
int backgroundColor = ContextCompat.getColor(getContext(),
R.color.notification_color_beneath);
mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
} else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
item.setAccessibilityDelegate(mAccessibilityDelegate);
}
boolean shouldAddBottomMargin = nextItemTypeToPopulate != null
&& itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
if (itemTypeToPopulate.isShortcut) {
if (mShortcutsItemView == null) {
mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
R.layout.shortcuts_item, this, false);
addView(mShortcutsItemView);
if (shouldUnroundTopCorners) {
shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
}
}
mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
if (shouldAddBottomMargin) {
((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
if (shouldUnroundBottomCorners) {
shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
}
} else {
addView(item);
if (shouldAddBottomMargin) {
((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
}
}
}
int backgroundColor = ContextCompat.getColor(getContext(), mNotificationItemView == null
? R.color.popup_background_color
: R.color.popup_header_background_color);
mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
}
protected PopupItemView getItemViewAt(int index) {
@ -297,45 +335,31 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
setVisibility(View.VISIBLE);
mIsOpen = true;
final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
final int itemCount = getItemCount();
final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
final long duration = getResources().getInteger(
R.integer.config_deepShortcutOpenDuration);
final long arrowScaleDuration = getResources().getInteger(
R.integer.config_deepShortcutArrowOpenDuration);
final long arrowScaleDelay = duration - arrowScaleDuration;
final long stagger = getResources().getInteger(
R.integer.config_deepShortcutOpenStagger);
final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
// Animate shortcuts
DecelerateInterpolator interpolator = new DecelerateInterpolator();
for (int i = 0; i < itemCount; i++) {
final PopupItemView popupItemView = getItemViewAt(i);
popupItemView.setVisibility(INVISIBLE);
popupItemView.setAlpha(0);
Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
popupItemView.setVisibility(VISIBLE);
}
});
anim.setDuration(duration);
int animationIndex = mIsAboveIcon ? itemCount - i - 1 : i;
anim.setStartDelay(stagger * animationIndex);
anim.setInterpolator(interpolator);
shortcutAnims.play(anim);
Animator fadeAnim = ObjectAnimator.ofFloat(popupItemView, View.ALPHA, 1);
fadeAnim.setInterpolator(fadeInterpolator);
// We want the shortcut to be fully opaque before the arrow starts animating.
fadeAnim.setDuration(arrowScaleDelay);
shortcutAnims.play(fadeAnim);
// Rectangular reveal.
int itemsTotalHeight = 0;
for (int i = 0; i < getItemCount(); i++) {
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
}
shortcutAnims.addListener(new AnimatorListenerAdapter() {
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
float radius = getItemViewAt(0).getBackgroundRadius();
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
(radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
// Animate the arrow.
mArrow.setScaleX(0);
mArrow.setScaleY(0);
Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
R.integer.config_popupArrowOpenDuration));
openAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator = null;
@ -346,15 +370,26 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
}
});
// Animate the arrow
mArrow.setScaleX(0);
mArrow.setScaleY(0);
Animator arrowScale = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
arrowScale.setStartDelay(arrowScaleDelay);
shortcutAnims.play(arrowScale);
mOpenCloseAnimator = openAnim;
openAnim.playSequentially(revealAnim, arrowScale);
openAnim.start();
}
mOpenCloseAnimator = shortcutAnims;
shortcutAnims.start();
/**
* Returns the point at which the center of the arrow merges with the first popup item.
*/
private Point computeAnimStartPoint(int itemsTotalHeight) {
int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
R.dimen.popup_arrow_horizontal_center_start:
R.dimen.popup_arrow_horizontal_center_end);
if (!mIsLeftAligned) {
arrowCenterX = getMeasuredWidth() - arrowCenterX;
}
int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
- itemsTotalHeight;
// The y-coordinate of edge between the arrow and the first popup item.
int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
return new Point(arrowCenterX, arrowEdge);
}
/**
@ -506,7 +541,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
// since the latter expects the arrow which hasn't been added yet.
PopupItemView itemAttachedToArrow = (PopupItemView)
(getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0));
arrowPaint.setColor(itemAttachedToArrow.getArrowColor(mIsAboveIcon));
arrowPaint.setColor(ContextCompat.getColor(mLauncher, R.color.popup_background_color));
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
arrowPaint.setPathEffect(new CornerPathEffect(radius));
@ -609,22 +644,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
final int duration = getResources().getInteger(
R.integer.config_removeNotificationViewDuration);
final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing);
removeNotification.play(reduceNotificationViewHeight(
mNotificationItemView.getHeightMinusFooter() + spacing, duration));
final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
: mNotificationItemView;
if (removeMarginView != null) {
ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin
= (int) (spacing * (float) valueAnimator.getAnimatedValue());
}
});
removeNotification.play(removeMargin);
}
mNotificationItemView.getHeightMinusFooter(), duration));
Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
.setDuration(duration);
fade.addListener(new AnimatorListenerAdapter() {
@ -634,19 +655,25 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
mNotificationItemView = null;
if (getItemCount() == 0) {
close(false);
return;
}
}
});
removeNotification.play(fade);
final long arrowScaleDuration = getResources().getInteger(
R.integer.config_deepShortcutArrowOpenDuration);
R.integer.config_popupArrowOpenDuration);
Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
hideArrow.setStartDelay(0);
Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
removeNotification.playSequentially(hideArrow, showArrow);
removeNotification.start();
if (mShortcutsItemView != null) {
int backgroundColor = ContextCompat.getColor(getContext(),
R.color.popup_background_color);
// With notifications gone, all corners of shortcuts item should be rounded.
mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
}
return;
}
mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
@ -770,55 +797,40 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
if (!mIsOpen) {
return;
}
mEndRect.setEmpty();
if (mOpenCloseAnimator != null) {
Outline outline = new Outline();
getOutlineProvider().getOutline(this, outline);
outline.getRect(mEndRect);
mOpenCloseAnimator.cancel();
}
mIsOpen = false;
final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
final int itemCount = getItemCount();
int numOpenShortcuts = 0;
for (int i = 0; i < itemCount; i++) {
if (getItemViewAt(i).isOpenOrOpening()) {
numOpenShortcuts++;
}
final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
// Animate the arrow.
Animator arrowScale = createArrowScaleAnim(0).setDuration(res.getInteger(
R.integer.config_popupArrowOpenDuration));
// Rectangular reveal (reversed).
int itemsTotalHeight = 0;
for (int i = 0; i < getItemCount(); i++) {
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
}
final long duration = getResources().getInteger(
R.integer.config_deepShortcutCloseDuration);
final long arrowScaleDuration = getResources().getInteger(
R.integer.config_deepShortcutArrowOpenDuration);
final long stagger = getResources().getInteger(
R.integer.config_deepShortcutCloseStagger);
final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0;
for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
final PopupItemView view = getItemViewAt(i);
Animator anim;
anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
: numOpenShortcuts - i - 1;
anim.setStartDelay(stagger * animationIndex);
Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
// Don't start fading until the arrow is gone.
fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
fadeAnim.setDuration(duration - arrowScaleDuration);
fadeAnim.setInterpolator(fadeInterpolator);
shortcutAnims.play(fadeAnim);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(INVISIBLE);
}
});
shortcutAnims.play(anim);
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
float radius = getItemViewAt(0).getBackgroundRadius();
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
if (mEndRect.isEmpty()) {
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
}
Animator arrowAnim = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
arrowAnim.setStartDelay(0);
shortcutAnims.play(arrowAnim);
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
revealAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
revealAnim.setInterpolator(new AccelerateDecelerateInterpolator());
shortcutAnims.addListener(new AnimatorListenerAdapter() {
closeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator = null;
@ -829,8 +841,9 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
}
}
});
mOpenCloseAnimator = shortcutAnims;
shortcutAnims.start();
mOpenCloseAnimator = closeAnim;
closeAnim.playSequentially(arrowScale, revealAnim);
closeAnim.start();
mOriginalIcon.forceHideBadge(false);
}

View File

@ -16,38 +16,34 @@
package com.android.launcher3.popup;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.launcher3.LogAccelerateInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PillRevealOutlineProvider;
import com.android.launcher3.popup.PopupContainerWithArrow.RoundedCornerFlags;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
/**
* An abstract {@link FrameLayout} that supports animating an item's content
* (e.g. icon and text) separate from the item's background.
* An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
*/
public abstract class PopupItemView extends FrameLayout
implements ValueAnimator.AnimatorUpdateListener {
protected static final Point sTempPoint = new Point();
public abstract class PopupItemView extends FrameLayout {
protected final Rect mPillRect;
private float mOpenAnimationProgress;
protected @RoundedCornerFlags int mRoundedCorners;
protected final boolean mIsRtl;
protected View mIconView;
@ -93,164 +89,55 @@ public abstract class PopupItemView extends FrameLayout
@Override
protected void dispatchDraw(Canvas canvas) {
if (mRoundedCorners == 0) {
super.dispatchDraw(canvas);
return;
}
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
super.dispatchDraw(canvas);
// Clip children to this item's rounded corners.
int cornerWidth = mRoundedCornerBitmap.getWidth();
int cornerHeight = mRoundedCornerBitmap.getHeight();
// Clip top left corner.
mMatrix.reset();
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip top right corner.
mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip bottom right corner.
mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip bottom left corner.
mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
// Clip top left corner.
mMatrix.reset();
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip top right corner.
mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
}
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
// Clip bottom right corner.
mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip bottom left corner.
mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2);
mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
}
canvas.restoreToCount(saveCount);
}
/**
* Creates an animator to play when the shortcut container is being opened.
* Creates a round rect drawable (with the specified corners unrounded)
* and sets it as this View's background.
*/
public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
Point center = getIconCenter();
int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
R.dimen.popup_arrow_horizontal_center_start:
R.dimen.popup_arrow_horizontal_center_end);
ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
.createRevealAnimator(this, false);
mOpenAnimationProgress = 0f;
openAnimator.addUpdateListener(this);
return openAnimator;
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
}
public boolean isOpenOrOpening() {
return mOpenAnimationProgress > 0;
}
/**
* Creates an animator to play when the shortcut container is being closed.
*/
public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
long duration) {
Point center = getIconCenter();
int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
R.dimen.popup_arrow_horizontal_center_start :
R.dimen.popup_arrow_horizontal_center_end);
ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
.createRevealAnimator(this, true);
// Scale down the duration and interpolator according to the progress
// that the open animation was at when the close started.
closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
closeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenAnimationProgress = 0;
}
});
return closeAnimator;
}
/**
* Returns the position of the center of the icon relative to the container.
*/
public Point getIconCenter() {
sTempPoint.y = getMeasuredHeight() / 2;
sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2;
if (Utilities.isRtl(getResources())) {
sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
}
return sTempPoint;
public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
mRoundedCorners = roundedCorners;
float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
roundRectBackground.getPaint().setColor(color);
setBackground(roundRectBackground);
}
protected float getBackgroundRadius() {
return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
}
public abstract int getArrowColor(boolean isArrowAttachedToBottom);
/**
* Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
*/
private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
private final View mTranslateView;
private final View mZoomView;
private final float mFullHeight;
private final float mTranslateYMultiplier;
private final boolean mPivotLeft;
private final float mTranslateX;
private final float mArrowCenter;
public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter) {
super(x, y, pillRect, translateView.getBackgroundRadius());
mTranslateView = translateView;
mZoomView = zoomView;
mFullHeight = pillRect.height();
mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
mPivotLeft = pivotLeft;
mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter;
mArrowCenter = arrowCenter;
}
@Override
public void setProgress(float progress) {
super.setProgress(progress);
if (mZoomView != null) {
mZoomView.setScaleX(progress);
mZoomView.setScaleY(progress);
}
float height = mOutline.height();
mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
float offsetX = Math.min(mOutline.width(), mArrowCenter);
float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX);
mTranslateView.setTranslationX(mTranslateX - pivotX);
}
}
/**
* An interpolator that reverses the current open animation progress.
*/
private static class CloseInterpolator extends LogAccelerateInterpolator {
private float mStartProgress;
private float mRemainingProgress;
/**
* @param openAnimationProgress The progress that the open interpolator ended at.
*/
public CloseInterpolator(float openAnimationProgress) {
super(100, 0);
mStartProgress = 1f - openAnimationProgress;
mRemainingProgress = openAnimationProgress;
}
@Override
public float getInterpolation(float v) {
return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
}
}
}

View File

@ -16,12 +16,10 @@
package com.android.launcher3.shortcuts;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Point;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
@ -30,9 +28,7 @@ import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
@ -135,7 +131,17 @@ public class ShortcutsItemView extends PopupItemView implements View.OnLongClick
if (mSystemShortcutIcons == null) {
mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
R.layout.system_shortcut_icons, mShortcutsLayout, false);
mShortcutsLayout.addView(mSystemShortcutIcons, 0);
View divider = LayoutInflater.from(getContext()).inflate(
R.layout.horizontal_divider, this, false);
if (mShortcutsLayout.getChildCount() > 0) {
mShortcutsLayout.addView(divider);
}
mShortcutsLayout.addView(mSystemShortcutIcons);
if (mShortcutsLayout.getChildCount() == 1) {
mShortcutsLayout.addView(divider);
}
}
mSystemShortcutIcons.addView(shortcutView, index);
} else {
@ -209,51 +215,6 @@ public class ShortcutsItemView extends PopupItemView implements View.OnLongClick
}
}
@Override
public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
continue;
}
DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
View deepShortcutIcon = shortcutView.getIconView();
deepShortcutIcon.setScaleX(0);
deepShortcutIcon.setScaleY(0);
openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
}
return openAnimation;
}
@Override
public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
long duration) {
AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
continue;
}
DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
View deepShortcutIcon = shortcutView.getIconView();
deepShortcutIcon.setScaleX(1);
deepShortcutIcon.setScaleY(1);
closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
}
return closeAnimation;
}
@Override
public int getArrowColor(boolean isArrowAttachedToBottom) {
return ContextCompat.getColor(getContext(),
isArrowAttachedToBottom || mSystemShortcutIcons == null
? R.color.popup_background_color
: R.color.popup_header_background_color);
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {