Added appear and disappear animations for the shelf icons

The icons now animate in and out of the shelf nicer.
Also fixed that the regular animation was played when in the shelf.

Test: Add notifications, observe animation in statusbar
Bug: 32437839
Change-Id: Id003fee1508b8c18a933d38faf93541be21baffd
This commit is contained in:
Selim Cinek
2016-11-08 18:11:58 -08:00
parent 49014f8522
commit 5b5beb01dc
7 changed files with 161 additions and 63 deletions

View File

@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
@@ -157,6 +155,7 @@ public class NotificationShelf extends ActivatableNotificationView {
mShelfState.shadowAlpha = 1.0f;
mShelfState.isBottomClipped = false;
mShelfState.hideSensitive = false;
mShelfState.xTranslation = getTranslationX();
if (mNotGoneIndex != -1) {
mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
}
@@ -173,7 +172,7 @@ public class NotificationShelf extends ActivatableNotificationView {
* the icons from the notification area into the shelf.
*/
public void updateAppearance() {
WeakHashMap<View, IconState> iconStates =
WeakHashMap<View, NotificationIconContainer.IconState> iconStates =
mShelfIcons.resetViewStates();
float numIconsInShelf = 0.0f;
int shelfIndex = mAmbientState.getShelfIndex();
@@ -196,7 +195,7 @@ public class NotificationShelf extends ActivatableNotificationView {
}
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
StatusBarIconView icon = row.getEntry().expandedIcon;
IconState iconState = iconStates.get(icon);
NotificationIconContainer.IconState iconState = iconStates.get(icon);
float notificationClipEnd;
float shelfStart = getTranslationY();
boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
@@ -255,8 +254,9 @@ public class NotificationShelf extends ActivatableNotificationView {
}
}
private void updateIconAppearance(ExpandableNotificationRow row, IconState iconState,
StatusBarIconView icon, float expandAmount) {
private void updateIconAppearance(ExpandableNotificationRow row,
NotificationIconContainer.IconState iconState, StatusBarIconView icon,
float expandAmount) {
// Let calculate how much the view is in the shelf
float viewStart = row.getTranslationY();
int transformHeight = row.getActualHeight() + mPaddingBetweenElements;

View File

@@ -19,7 +19,6 @@ package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -68,7 +67,7 @@ public class StatusBarIconView extends AnimatedImageView {
return object.getIconAppearAmount();
}
};
private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNG
private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("dot_appear_amount") {
@Override
@@ -440,54 +439,75 @@ public class StatusBarIconView extends AnimatedImageView {
mDotPaint.setColor(iconTint);
}
public void setVisibleState(int visibleState) {
public void setVisibleState(int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
public void setVisibleState(int state, boolean animate) {
setVisibleState(state, animate, null);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
if (visibleState != mVisibleState) {
mVisibleState = visibleState;
if (mIconAppearAnimator != null) {
mIconAppearAnimator.cancel();
}
float targetAmount = 0.0f;
Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_ICON) {
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
targetAmount);
mIconAppearAnimator.setInterpolator(interpolator);
mIconAppearAnimator.setDuration(100);
mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIconAppearAnimator = null;
if (animate) {
if (mIconAppearAnimator != null) {
mIconAppearAnimator.cancel();
}
});
mIconAppearAnimator.start();
float targetAmount = 0.0f;
Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_ICON) {
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
targetAmount);
mIconAppearAnimator.setInterpolator(interpolator);
mIconAppearAnimator.setDuration(100);
mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIconAppearAnimator = null;
if (endRunnable != null) {
endRunnable.run();
}
}
});
mIconAppearAnimator.start();
if (mDotAnimator != null) {
mDotAnimator.cancel();
}
targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_DOT) {
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNG,
targetAmount);
mDotAnimator.setInterpolator(interpolator);
mDotAnimator.setDuration(100);
mDotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDotAnimator = null;
if (mDotAnimator != null) {
mDotAnimator.cancel();
}
});
mDotAnimator.start();
targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_DOT) {
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
targetAmount);
mDotAnimator.setInterpolator(interpolator);
mDotAnimator.setDuration(100);
mDotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDotAnimator = null;
}
});
mDotAnimator.start();
} else {
setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f);
}
}
}
public void setIconAppearAmount(Float iconAppearAmount) {
public void setIconAppearAmount(float iconAppearAmount) {
mIconAppearAmount = iconAppearAmount;
invalidate();
}

View File

@@ -36,7 +36,7 @@ public class NotificationIconAreaController {
private PhoneStatusBar mPhoneStatusBar;
protected View mNotificationIconArea;
private NotificationIconContainer mNotificationIcons;
private NotificationIconContainer mNotificationIconsScroller;
private NotificationIconContainer mShelfIcons;
private final Rect mTintArea = new Rect();
private NotificationStackScrollLayout mNotificationScrollLayout;
private Context mContext;
@@ -65,7 +65,7 @@ public class NotificationIconAreaController {
R.id.notificationIcons);
NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf();
mNotificationIconsScroller = shelf.getShelfIcons();
mShelfIcons = shelf.getShelfIcons();
shelf.setCollapsedIcons(mNotificationIcons);
mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
@@ -147,8 +147,7 @@ public class NotificationIconAreaController {
public void updateNotificationIcons(NotificationData notificationData) {
updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
updateIconsForLayout(notificationData, entry -> entry.expandedIcon,
mNotificationIconsScroller);
updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons);
applyNotificationIconsTint();
ArrayList<NotificationData.Entry> activeNotifications
@@ -207,11 +206,14 @@ public class NotificationIconAreaController {
final LinearLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
hostLayout.addView(v, i, params);
}
}
hostLayout.setChangingViewPositions(true);
// Re-sort notification icons
final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
@@ -223,6 +225,7 @@ public class NotificationIconAreaController {
hostLayout.removeView(expected);
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
}
/**

View File

@@ -40,13 +40,23 @@ import java.util.WeakHashMap;
public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private static final String TAG = "NotificationIconContainer";
private static final boolean DEBUG = false;
private static final AnimationFilter DOT_ANIMATION_FILTER = new AnimationFilter().animateX();
private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
@Override
public AnimationFilter getAnimationFilter() {
return DOT_ANIMATION_FILTER;
return mAnimationFilter;
}
}.setDuration(200);
private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@Override
public AnimationFilter getAnimationFilter() {
return mAnimationFilter;
}
}.setDuration(200).setDelay(50);
private boolean mShowAllIcons = true;
private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();
@@ -55,6 +65,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private int mActualLayoutWidth = -1;
private float mActualPaddingEnd = -1;
private float mActualPaddingStart = -1;
private boolean mChangingViewPositions;
private int mAnimationStartIndex = -1;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -109,18 +121,60 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
childState.applyToView(child);
}
}
mAnimationStartIndex = -1;
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
mIconStates.put(child, new IconState());
if (!mChangingViewPositions) {
mIconStates.put(child, new IconState());
}
int childIndex = indexOfChild(child);
if (childIndex < getChildCount() - 1
&& mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
if (mAnimationStartIndex < 0) {
mAnimationStartIndex = childIndex;
} else {
mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex);
}
}
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
mIconStates.remove(child);
if (child instanceof StatusBarIconView) {
final StatusBarIconView icon = (StatusBarIconView) child;
if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
&& child.getVisibility() == VISIBLE) {
int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
if (mAnimationStartIndex < 0) {
mAnimationStartIndex = animationStartIndex;
} else {
mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex);
}
}
if (!mChangingViewPositions) {
mIconStates.remove(child);
addTransientView(icon, 0);
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
() -> removeTransientView(icon));
}
}
}
/**
* Finds the first view with a translation bigger then a given value
*/
private int findFirstViewIndexAfter(float translationX) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getTranslationX() > translationX) {
return i;
}
}
return getChildCount();
}
public WeakHashMap<View, IconState> resetViewStates() {
@@ -128,6 +182,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
View view = getChildAt(i);
ViewState iconState = mIconStates.get(view);
iconState.initFrom(view);
iconState.alpha = 1.0f;
}
return mIconStates;
}
@@ -246,21 +301,41 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
return mActualLayoutWidth;
}
public static class IconState extends ViewState {
public void setChangingViewPositions(boolean changingViewPositions) {
mChangingViewPositions = changingViewPositions;
}
public class IconState extends ViewState {
public float iconAppearAmount = 1.0f;
public int visibleState;
public boolean justAdded = true;
@Override
public void applyToView(View view) {
if (view instanceof StatusBarIconView) {
StatusBarIconView icon = (StatusBarIconView) view;
if (visibleState != icon.getVisibleState()) {
icon.setVisibleState(visibleState);
animateTo(icon, DOT_ANIMATION_PROPERTIES);
AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES;
if (justAdded) {
super.applyToView(icon);
icon.setAlpha(0.0f);
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
animationProperties = ADD_ICON_PROPERTIES;
}
boolean animate = visibleState != icon.getVisibleState() || justAdded;
if (!animate && mAnimationStartIndex >= 0
&& (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
|| visibleState != StatusBarIconView.STATE_HIDDEN)) {
int viewIndex = indexOfChild(view);
animate = viewIndex >= mAnimationStartIndex;
}
icon.setVisibleState(visibleState);
if (animate) {
animateTo(icon, animationProperties);
} else {
super.applyToView(view);
}
}
justAdded = false;
}
}
}

View File

@@ -221,7 +221,7 @@ public class ExpandableViewState extends ViewState {
// start dark animation
expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
if (properties.wasAdded(child)) {
if (properties.wasAdded(child) && !hidden) {
expandableView.performAddAnimation(properties.delay, properties.duration);
}

View File

@@ -372,7 +372,7 @@ public class StackStateAnimator {
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
if (changingView.getVisibility() == View.GONE) {
if (changingView.getVisibility() != View.VISIBLE) {
removeFromOverlay(changingView);
continue;
}

View File

@@ -210,7 +210,7 @@ public class ViewState {
}
// start alpha animation
if (alphaChanging && child.getTranslationX() == 0) {
if (alphaChanging) {
startAlphaAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_ALPHA);