Introduce AmbientState for StackScroller.

AmbientState is a global state for the algorithm of the
StackScroller. Dimmed and which child is activated was moved
into this state. Further, scale and dimmed is also a
StackScrollState and animated in StackStateAnimator.

Change-Id: Ia68131cee62b2e0005f55ea6dc400d149ec15278
This commit is contained in:
Jorim Jaggi
2014-05-07 19:41:13 +02:00
parent 7359b0b885
commit d552d9d8e9
18 changed files with 471 additions and 338 deletions

View File

@@ -18,11 +18,13 @@
<resources>
<item type="id" name="translation_y_animator_tag"/>
<item type="id" name="translation_z_animator_tag"/>
<item type="id" name="scale_animator_tag"/>
<item type="id" name="alpha_animator_tag"/>
<item type="id" name="top_inset_animator_tag"/>
<item type="id" name="height_animator_tag"/>
<item type="id" name="translation_y_animator_end_value_tag"/>
<item type="id" name="translation_z_animator_end_value_tag"/>
<item type="id" name="scale_animator_end_value_tag"/>
<item type="id" name="alpha_animator_end_value_tag"/>
<item type="id" name="top_inset_animator_end_value_tag"/>
<item type="id" name="height_animator_end_value_tag"/>

View File

@@ -38,6 +38,7 @@ import com.android.internal.R;
public abstract class ActivatableNotificationView extends ExpandableOutlineView {
private static final long DOUBLETAP_TIMEOUT_MS = 1000;
private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
private boolean mDimmed;
@@ -179,7 +180,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mActivated = false;
}
if (mOnActivatedListener != null) {
mOnActivatedListener.onReset(this);
mOnActivatedListener.onActivationReset(this);
}
removeCallbacks(mTapTimeoutRunnable);
}
@@ -189,12 +190,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
&& Math.abs(event.getY() - mDownY) < mTouchSlop;
}
/**
* Sets the notification as dimmed, meaning that it will appear in a more gray variant.
*
* @param dimmed Whether the notification should be dimmed.
* @param fade Whether an animation should be played to change the state.
*/
public void setDimmed(boolean dimmed, boolean fade) {
if (mDimmed != dimmed) {
mDimmed = dimmed;
@@ -226,7 +221,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
int startAlpha = mDimmed ? 255 : 0;
int endAlpha = mDimmed ? 0 : 255;
int duration = NotificationActivator.ANIMATION_LENGTH_MS;
int duration = BACKGROUND_ANIMATION_LENGTH_MS;
// Check whether there is already a background animation running.
if (mBackgroundAnimator != null) {
startAlpha = (Integer) mBackgroundAnimator.getAnimatedValue();
@@ -313,8 +308,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
@Override
public void setActualHeight(int actualHeight) {
super.setActualHeight(actualHeight);
public void setActualHeight(int actualHeight, boolean notifyListeners) {
super.setActualHeight(actualHeight, notifyListeners);
invalidate();
setPivotY(actualHeight / 2);
}
@@ -331,6 +326,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
public interface OnActivatedListener {
void onActivated(View view);
void onReset(View view);
void onActivationReset(View view);
}
}

View File

@@ -1067,7 +1067,6 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setSystemExpanded(top);
}
}
entry.row.setDimmed(onKeyguard, false /* fade */);
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
|| !showOnKeyguard)) {
@@ -1087,48 +1086,11 @@ public abstract class BaseStatusBar extends SystemUI implements
if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, false /* fade */);
} else {
mKeyguardIconOverflowContainer.setVisibility(View.GONE);
}
}
@Override
public void onActivated(View view) {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
if (view == entry.row) {
entry.row.getActivator().activate();
} else {
entry.row.getActivator().activateInverse();
}
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
if (view == mKeyguardIconOverflowContainer) {
mKeyguardIconOverflowContainer.getActivator().activate();
} else {
mKeyguardIconOverflowContainer.getActivator().activateInverse();
}
}
}
@Override
public void onReset(View view) {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
entry.row.getActivator().reset();
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
mKeyguardIconOverflowContainer.getActivator().reset();
}
}
private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
}

View File

@@ -117,7 +117,7 @@ public class DragDownHelper implements Gefingerpoken {
} else {
if (mDraggedFarEnough) {
mDraggedFarEnough = false;
mOnDragDownListener.onReset();
mOnDragDownListener.onDragDownReset();
}
}
return true;
@@ -188,7 +188,7 @@ public class DragDownHelper implements Gefingerpoken {
cancelExpansion(mStartingChild);
}
mDraggingDown = false;
mOnDragDownListener.onReset();
mOnDragDownListener.onDragDownReset();
}
private ExpandableView findView(float x, float y) {
@@ -200,7 +200,7 @@ public class DragDownHelper implements Gefingerpoken {
public interface OnDragDownListener {
void onDraggedDown(View startingChild);
void onReset();
void onDragDownReset();
void onThresholdReached();
}
}

View File

@@ -52,7 +52,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private int mMaxExpandHeight;
private NotificationActivator mActivator;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -63,8 +62,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
mActivator = new NotificationActivator(this, this);
}
@Override
@@ -208,23 +205,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE);
}
/**
* Sets the notification as dimmed, meaning that it will appear in a more gray variant.
*/
@Override
public void setDimmed(boolean dimmed, boolean fade) {
super.setDimmed(dimmed, fade);
mActivator.setDimmed(dimmed, fade);
}
public int getMaxExpandHeight() {
return mMaxExpandHeight;
}
public NotificationActivator getActivator() {
return mActivator;
}
/**
* @return the potential height this view could expand in addition.
*/
@@ -238,10 +222,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
@Override
public void setActualHeight(int height) {
mPrivateLayout.setActualHeight(height);
public void setActualHeight(int height, boolean notifyListeners) {
mPrivateLayout.setActualHeight(height, notifyListeners);
invalidate();
super.setActualHeight(height);
super.setActualHeight(height, notifyListeners);
}
@Override

View File

@@ -33,8 +33,8 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
@Override
public void setActualHeight(int actualHeight) {
super.setActualHeight(actualHeight);
public void setActualHeight(int actualHeight, boolean notifyListeners) {
super.setActualHeight(actualHeight, notifyListeners);
updateOutline();
}

View File

@@ -61,10 +61,19 @@ public abstract class ExpandableView extends FrameLayout {
/**
* Sets the actual height of this notification. This is different than the laid out
* {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
*
* @param actualHeight The height of this notification.
* @param notifyListeners Whether the listener should be informed about the change.
*/
public void setActualHeight(int actualHeight) {
public void setActualHeight(int actualHeight, boolean notifyListeners) {
mActualHeight = actualHeight;
notifyHeightChanged();
if (notifyListeners) {
notifyHeightChanged();
}
}
public void setActualHeight(int actualHeight) {
setActualHeight(actualHeight, true);
}
/**
@@ -90,6 +99,15 @@ public abstract class ExpandableView extends FrameLayout {
return getHeight();
}
/**
* Sets the notification as dimmed. The default implementation does nothing.
*
* @param dimmed Whether the notification should be dimmed.
* @param fade Whether an animation should be played to change the state.
*/
public void setDimmed(boolean dimmed, boolean fade) {
}
/**
* @return The desired notification height.
*/

View File

@@ -1,152 +0,0 @@
/*
* Copyright (C) 2014 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.systemui.statusbar;
import android.content.Context;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
/**
* A helper class used by both {@link com.android.systemui.statusbar.ExpandableNotificationRow} and
* {@link com.android.systemui.statusbar.NotificationOverflowIconsView} to make a notification look
* active after tapping it once on the Keyguard.
*/
public class NotificationActivator {
public static final int ANIMATION_LENGTH_MS = 220;
private static final float INVERSE_ALPHA = 0.9f;
private static final float DIMMED_SCALE = 0.95f;
/**
* Normal state. Notification is fully interactable.
*/
private static final int STATE_NORMAL = 0;
/**
* Dimmed state. Neutral state when on the lockscreen, with slight transparency and scaled down
* a bit.
*/
private static final int STATE_DIMMED = 1;
/**
* Activated state. Used after tapping a notification on the lockscreen. Normal transparency and
* normal scale.
*/
private static final int STATE_ACTIVATED = 2;
/**
* Inverse activated state. Used for the other notifications on the lockscreen when tapping on
* one.
*/
private static final int STATE_ACTIVATED_INVERSE = 3;
private final View mTargetView;
private final View mHotspotView;
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
private final int mTranslationZ;
private int mState;
public NotificationActivator(View targetView, View hotspotView) {
mTargetView = targetView;
mHotspotView = hotspotView;
Context ctx = targetView.getContext();
mFastOutSlowInInterpolator =
AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
mLinearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in);
mTranslationZ =
ctx.getResources().getDimensionPixelSize(R.dimen.z_distance_between_notifications);
mTargetView.animate().setDuration(ANIMATION_LENGTH_MS);
}
public void activateInverse() {
if (mState == STATE_ACTIVATED_INVERSE) {
return;
}
mTargetView.animate().cancel();
mTargetView.animate().withLayer().alpha(INVERSE_ALPHA);
mState = STATE_ACTIVATED_INVERSE;
}
public void addHotspot() {
mHotspotView.getBackground().setHotspot(
0, mHotspotView.getWidth()/2, mHotspotView.getHeight()/2);
}
public void activate() {
if (mState == STATE_ACTIVATED) {
return;
}
mTargetView.animate().cancel();
mTargetView.animate()
.setInterpolator(mLinearOutSlowInInterpolator)
.scaleX(1)
.scaleY(1)
.translationZBy(mTranslationZ);
mState = STATE_ACTIVATED;
}
public void reset() {
if (mState == STATE_DIMMED) {
return;
}
mTargetView.animate().cancel();
mTargetView.animate()
.setInterpolator(mFastOutSlowInInterpolator)
.scaleX(DIMMED_SCALE)
.scaleY(DIMMED_SCALE)
.translationZBy(-mTranslationZ);
if (mTargetView.getAlpha() != 1.0f) {
mTargetView.animate().withLayer().alpha(1);
}
mState = STATE_DIMMED;
}
public void setDimmed(boolean dimmed, boolean fade) {
if (dimmed) {
mTargetView.animate().cancel();
if (fade) {
mTargetView.animate()
.setInterpolator(mFastOutSlowInInterpolator)
.scaleX(DIMMED_SCALE)
.scaleY(DIMMED_SCALE);
} else {
mTargetView.setScaleX(DIMMED_SCALE);
mTargetView.setScaleY(DIMMED_SCALE);
}
mState = STATE_DIMMED;
} else {
mTargetView.animate().cancel();
if (fade) {
mTargetView.animate()
.setInterpolator(mFastOutSlowInInterpolator)
.scaleX(1)
.scaleY(1);
} else {
mTargetView.animate().cancel();
mTargetView.setScaleX(1);
mTargetView.setScaleY(1);
}
mState = STATE_NORMAL;
}
}
}

View File

@@ -70,8 +70,8 @@ public class NotificationContentView extends ExpandableView {
}
@Override
public void setActualHeight(int actualHeight) {
super.setActualHeight(actualHeight);
public void setActualHeight(int actualHeight, boolean notifyListeners) {
super.setActualHeight(actualHeight, notifyListeners);
selectLayout();
updateClipping();
}

View File

@@ -28,14 +28,13 @@ import com.android.systemui.R;
public class NotificationOverflowContainer extends ActivatableNotificationView {
private NotificationOverflowIconsView mIconsView;
private NotificationActivator mActivator;
public NotificationOverflowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setActualHeight(int currentHeight) {
public void setActualHeight(int currentHeight, boolean notifyListeners) {
// noop
}
@@ -54,22 +53,9 @@ public class NotificationOverflowContainer extends ActivatableNotificationView {
super.onFinishInflate();
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
mActivator = new NotificationActivator(this, this);
setDimmed(true, false);
}
@Override
public void setDimmed(boolean dimmed, boolean fade) {
super.setDimmed(dimmed, fade);
mActivator.setDimmed(dimmed, fade);
}
public NotificationOverflowIconsView getIconsView() {
return mIconsView;
}
public NotificationActivator getActivator() {
return mActivator;
}
}

View File

@@ -2790,6 +2790,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mSettingsContainer.setKeyguardShowing(false);
}
updateStackScrollerState();
updatePublicMode();
updateRowStates();
checkBarModes();
@@ -2797,6 +2798,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateCarrierLabelVisibility(false);
}
public void updateStackScrollerState() {
mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */);
}
public void userActivity() {
if (mState == StatusBarState.KEYGUARD) {
mKeyguardViewMediatorCallback.userActivity();
@@ -2850,7 +2855,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void onActivated(View view) {
userActivity();
mKeyguardIndicationTextView.switchIndication(R.string.notification_tap_again);
super.onActivated(view);
mStackScroller.setActivatedChild(view);
}
/**
@@ -2862,9 +2867,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
public void onReset(View view) {
super.onReset(view);
mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
public void onActivationReset(View view) {
if (view == mStackScroller.getActivatedChild()) {
mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
mStackScroller.setActivatedChild(null);
}
}
public void onTrackingStarted() {
@@ -2896,30 +2903,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
public void onReset() {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
entry.row.setDimmed(true /* dimmed */, true /* fade */);
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, true /* fade */);
}
public void onDragDownReset() {
mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
}
public void onThresholdReached() {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
entry.row.setDimmed(false /* dimmed */, true /* fade */);
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
mKeyguardIconOverflowContainer.setDimmed(false /* dimmed */, true /* fade */);
}
mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
}
/**

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2014 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.systemui.statusbar.stack;
import android.view.View;
import java.util.ArrayList;
/**
* A global state to track all input states for the algorithm.
*/
public class AmbientState {
private ArrayList<View> mDraggedViews = new ArrayList<View>();
private int mScrollY;
private boolean mDimmed;
private View mActivatedChild;
public int getScrollY() {
return mScrollY;
}
public void setScrollY(int scrollY) {
this.mScrollY = scrollY;
}
public void onBeginDrag(View view) {
mDraggedViews.add(view);
}
public void onDragFinished(View view) {
mDraggedViews.remove(view);
}
public ArrayList<View> getDraggedViews() {
return mDraggedViews;
}
/**
* @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
* translucent and everything is scaled back a bit.
*/
public void setDimmed(boolean dimmed) {
mDimmed = dimmed;
}
/**
* In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
* interaction. This child is then scaled normally and its background is fully opaque.
*/
public void setActivatedChild(View activatedChild) {
mActivatedChild = activatedChild;
}
public boolean isDimmed() {
return mDimmed;
}
public View getActivatedChild() {
return mActivatedChild;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2014 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.systemui.statusbar.stack;
import java.util.ArrayList;
/**
* Filters the animations for only a certain type of properties.
*/
public class AnimationFilter {
boolean animateAlpha;
boolean animateY;
boolean animateZ;
boolean animateScale;
boolean animateHeight;
boolean animateDimmed;
public AnimationFilter animateAlpha() {
animateAlpha = true;
return this;
}
public AnimationFilter animateY() {
animateY = true;
return this;
}
public AnimationFilter animateZ() {
animateZ = true;
return this;
}
public AnimationFilter animateScale() {
animateScale = true;
return this;
}
public AnimationFilter animateHeight() {
animateHeight = true;
return this;
}
public AnimationFilter animateDimmed() {
animateDimmed = true;
return this;
}
/**
* Combines multiple filters into {@code this} filter, using or as the operand .
*
* @param events The animation events from the filters to combine.
*/
public void applyCombination(ArrayList<NotificationStackScrollLayout.AnimationEvent> events) {
reset();
int size = events.size();
for (int i = 0; i < size; i++) {
combineFilter(events.get(i).filter);
}
}
private void combineFilter(AnimationFilter filter) {
animateAlpha |= filter.animateAlpha;
animateY |= filter.animateY;
animateZ |= filter.animateZ;
animateScale |= filter.animateScale;
animateHeight |= filter.animateHeight;
animateDimmed |= filter.animateDimmed;
}
private void reset() {
animateAlpha = false;
animateY = false;
animateZ = false;
animateScale = false;
animateHeight = false;
animateDimmed = false;
}
}

View File

@@ -84,7 +84,6 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mEmptyMarginBottom;
private int mPaddingBetweenElements;
private int mTopPadding;
private boolean mListenForHeightChanges = true;
/**
* The algorithm which calculates the properties for our children
@@ -95,6 +94,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* The current State this Layout is in
*/
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
@@ -108,6 +108,8 @@ public class NotificationStackScrollLayout extends ViewGroup
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
private boolean mDimmedNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mIsExpanded = true;
private boolean mChildrenUpdateRequested;
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
@@ -267,8 +269,8 @@ public class NotificationStackScrollLayout extends ViewGroup
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
*/
private void updateChildren() {
mCurrentStackScrollState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
mAmbientState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
if (!isCurrentlyAnimating() && !mNeedsAnimation) {
applyCurrentState();
} else {
@@ -385,12 +387,12 @@ public class NotificationStackScrollLayout extends ViewGroup
mDragAnimPendingChildren.remove(v);
}
mSwipedOutViews.add(v);
mStackScrollAlgorithm.onDragFinished(v);
mAmbientState.onDragFinished(v);
}
@Override
public void onChildSnappedBack(View animView) {
mStackScrollAlgorithm.onDragFinished(animView);
mAmbientState.onDragFinished(animView);
if (!mDragAnimPendingChildren.contains(animView)) {
mSnappedBackChildren.add(animView);
requestChildrenUpdate();
@@ -404,7 +406,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mDragAnimPendingChildren.add(v);
mStackScrollAlgorithm.onBeginDrag(v);
mAmbientState.onBeginDrag(v);
requestChildrenUpdate();
mNeedsAnimation = true;
}
@@ -965,6 +967,8 @@ public class NotificationStackScrollLayout extends ViewGroup
generateSnapBackEvents();
generateDragEvents();
generateTopPaddingEvent();
generateActivateEvent();
generateDimmedEvent();
mNeedsAnimation = false;
}
@@ -1012,6 +1016,22 @@ public class NotificationStackScrollLayout extends ViewGroup
mTopPaddingNeedsAnimation = false;
}
private void generateActivateEvent() {
if (mActivateNeedsAnimation) {
mAnimationEvents.add(
new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
}
mActivateNeedsAnimation = false;
}
private void generateDimmedEvent() {
if (mDimmedNeedsAnimation) {
mAnimationEvents.add(
new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
}
mDimmedNeedsAnimation = false;
}
private boolean onInterceptTouchEventScroll(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
@@ -1177,14 +1197,12 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
public void onHeightChanged(ExpandableView view) {
if (mListenForHeightChanges && !isCurrentlyAnimating()) {
updateContentHeight();
updateScrollPositionIfNecessary();
if (mOnHeightChangedListener != null) {
mOnHeightChangedListener.onHeightChanged(view);
}
requestChildrenUpdate();
updateContentHeight();
updateScrollPositionIfNecessary();
if (mOnHeightChangedListener != null) {
mOnHeightChangedListener.onHeightChanged(view);
}
requestChildrenUpdate();
}
public void setOnHeightChangedListener(
@@ -1197,10 +1215,34 @@ public class NotificationStackScrollLayout extends ViewGroup
mAnimationEvents.clear();
}
/**
* See {@link AmbientState#setDimmed}.
*/
public void setDimmed(boolean dimmed, boolean animate) {
mAmbientState.setDimmed(dimmed);
if (animate) {
mDimmedNeedsAnimation = true;
mNeedsAnimation = true;
}
requestChildrenUpdate();
}
/**
* See {@link AmbientState#setActivatedChild}.
*/
public void setActivatedChild(View activatedChild) {
mAmbientState.setActivatedChild(activatedChild);
mActivateNeedsAnimation = true;
mNeedsAnimation = true;
requestChildrenUpdate();
}
public View getActivatedChild() {
return mAmbientState.getActivatedChild();
}
private void applyCurrentState() {
mListenForHeightChanges = false;
mCurrentStackScrollState.apply();
mListenForHeightChanges = true;
if (mListener != null) {
mListener.onChildLocationsChanged(this);
}
@@ -1215,21 +1257,76 @@ public class NotificationStackScrollLayout extends ViewGroup
static class AnimationEvent {
static int ANIMATION_TYPE_ADD = 1;
static int ANIMATION_TYPE_REMOVE = 2;
static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3;
static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 4;
static int ANIMATION_TYPE_START_DRAG = 5;
static int ANIMATION_TYPE_SNAP_BACK = 6;
static AnimationFilter[] FILTERS = new AnimationFilter[] {
// ANIMATION_TYPE_ADD
new AnimationFilter()
.animateAlpha()
.animateHeight()
.animateY()
.animateZ(),
// ANIMATION_TYPE_REMOVE
new AnimationFilter()
.animateAlpha()
.animateHeight()
.animateY()
.animateZ(),
// ANIMATION_TYPE_REMOVE_SWIPED_OUT
new AnimationFilter()
.animateAlpha()
.animateHeight()
.animateY()
.animateZ(),
// ANIMATION_TYPE_TOP_PADDING_CHANGED
new AnimationFilter()
.animateAlpha()
.animateHeight()
.animateY()
.animateDimmed()
.animateScale()
.animateZ(),
// ANIMATION_TYPE_START_DRAG
new AnimationFilter()
.animateAlpha(),
// ANIMATION_TYPE_SNAP_BACK
new AnimationFilter()
.animateAlpha(),
// ANIMATION_TYPE_ACTIVATED_CHILD
new AnimationFilter()
.animateScale()
.animateAlpha(),
// ANIMATION_TYPE_DIMMED
new AnimationFilter()
.animateScale()
.animateDimmed()
};
static int ANIMATION_TYPE_ADD = 0;
static int ANIMATION_TYPE_REMOVE = 1;
static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
static int ANIMATION_TYPE_START_DRAG = 4;
static int ANIMATION_TYPE_SNAP_BACK = 5;
static int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
static int ANIMATION_TYPE_DIMMED = 7;
final long eventStartTime;
final View changingView;
final int animationType;
final AnimationFilter filter;
AnimationEvent(View view, int type) {
eventStartTime = AnimationUtils.currentAnimationTimeMillis();
changingView = view;
animationType = type;
filter = FILTERS[type];
}
}

View File

@@ -39,6 +39,10 @@ public class StackScrollAlgorithm {
private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
private static final int MAX_ITEMS_IN_TOP_STACK = 3;
/** When a child is activated, the other cards' alpha fade to this value. */
private static final float ACTIVATED_INVERSE_ALPHA = 0.9f;
private static final float DIMMED_SCALE = 0.95f;
private int mPaddingBetweenElements;
private int mCollapsedSize;
private int mTopStackPeekSize;
@@ -61,7 +65,6 @@ public class StackScrollAlgorithm {
private ExpandableView mFirstChildWhileExpanding;
private boolean mExpandedOnStart;
private int mTopStackTotalSize;
private ArrayList<View> mDraggedViews = new ArrayList<View>();
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -93,7 +96,7 @@ public class StackScrollAlgorithm {
}
public void getStackScrollState(StackScrollState resultState) {
public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
// The state of the local variables are saved in an algorithmState to easily subdivide it
// into multiple phases.
StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
@@ -107,7 +110,7 @@ public class StackScrollAlgorithm {
algorithmState.scrolledPixelsTop = 0;
algorithmState.itemsInBottomStack = 0.0f;
algorithmState.partialInBottom = 0.0f;
algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize;
algorithmState.scrollY = ambientState.getScrollY() + mCollapsedSize;
updateVisibleChildren(resultState, algorithmState);
@@ -120,19 +123,42 @@ public class StackScrollAlgorithm {
// Phase 3:
updateZValuesForState(resultState, algorithmState);
handleDraggedViews(resultState, algorithmState);
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivated(ambientState, resultState, algorithmState);
}
/**
* Updates the dimmed and activated states of the children.
*/
private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
boolean dimmed = ambientState.isDimmed();
View activatedChild = ambientState.getActivatedChild();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
childViewState.dimmed = dimmed;
childViewState.scale = !dimmed || activatedChild == child
? 1.0f
: DIMMED_SCALE;
if (dimmed && activatedChild != null && child != activatedChild) {
childViewState.alpha *= ACTIVATED_INVERSE_ALPHA;
}
}
}
/**
* Handle the special state when views are being dragged
*/
private void handleDraggedViews(StackScrollState resultState,
private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
for (View draggedView : mDraggedViews) {
ArrayList<View> draggedViews = ambientState.getDraggedViews();
for (View draggedView : draggedViews) {
int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
if (!mDraggedViews.contains(nextChild)) {
if (!draggedViews.contains(nextChild)) {
// only if the view is not dragged itself we modify its state to be fully
// visible
StackScrollState.ViewState viewState = resultState.getViewStateForView(
@@ -595,14 +621,6 @@ public class StackScrollAlgorithm {
}
}
public void onBeginDrag(View view) {
mDraggedViews.add(view);
}
public void onDragFinished(View view) {
mDraggedViews.remove(view);
}
class StackScrollAlgorithmState {
/**

View File

@@ -37,19 +37,10 @@ public class StackScrollState {
private final ViewGroup mHostView;
private Map<ExpandableView, ViewState> mStateMap;
private int mScrollY;
private final Rect mClipRect = new Rect();
private int mBackgroundRoundedRectCornerRadius;
private final Outline mChildOutline = new Outline();
public int getScrollY() {
return mScrollY;
}
public void setScrollY(int scrollY) {
this.mScrollY = scrollY;
}
public StackScrollState(ViewGroup hostView) {
mHostView = hostView;
mStateMap = new HashMap<ExpandableView, ViewState>();
@@ -106,10 +97,12 @@ public class StackScrollState {
float alpha = child.getAlpha();
float yTranslation = child.getTranslationY();
float zTranslation = child.getTranslationZ();
float scale = child.getScaleX();
int height = child.getActualHeight();
float newAlpha = state.alpha;
float newYTranslation = state.yTranslation;
float newZTranslation = state.zTranslation;
float newScale = state.scale;
int newHeight = state.height;
boolean becomesInvisible = newAlpha == 0.0f;
if (alpha != newAlpha) {
@@ -147,11 +140,20 @@ public class StackScrollState {
child.setTranslationZ(newZTranslation);
}
// apply scale
if (scale != newScale) {
child.setScaleX(newScale);
child.setScaleY(newScale);
}
// apply height
if (height != newHeight) {
child.setActualHeight(newHeight);
child.setActualHeight(newHeight, false /* notifyListeners */);
}
// apply dimming
child.setDimmed(state.dimmed, false /* animate */);
// apply clipping and shadow
float newNotificationEnd = newYTranslation + newHeight;
@@ -228,6 +230,8 @@ public class StackScrollState {
float zTranslation;
int height;
boolean gone;
float scale;
boolean dimmed;
/**
* The location this view is currently rendered at.

View File

@@ -18,7 +18,9 @@ package com.android.systemui.statusbar.stack;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -40,11 +42,13 @@ public class StackStateAnimator {
private static final int ANIMATION_DURATION = 360;
private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
@@ -58,6 +62,7 @@ public class StackStateAnimator {
private Set<Animator> mAnimatorSet = new HashSet<Animator>();
private Stack<AnimatorListenerAdapter> mAnimationListenerPool
= new Stack<AnimatorListenerAdapter>();
private AnimationFilter mAnimationFilter = new AnimationFilter();
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -75,8 +80,8 @@ public class StackStateAnimator {
processAnimationEvents(mAnimationEvents, finalState);
boolean hasNewEvents = !mNewEvents.isEmpty();
int childCount = mHostLayout.getChildCount();
mAnimationFilter.applyCombination(mNewEvents);
for (int i = 0; i < childCount; i++) {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
@@ -84,7 +89,7 @@ public class StackStateAnimator {
continue;
}
startAnimations(child, viewState, hasNewEvents);
startAnimations(child, viewState);
child.setClipBounds(null);
}
@@ -97,8 +102,7 @@ public class StackStateAnimator {
/**
* Start an animation to the given viewState
*/
private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
boolean hasNewEvents) {
private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) {
int childVisibility = child.getVisibility();
boolean wasVisible = childVisibility == View.VISIBLE;
final float alpha = viewState.alpha;
@@ -107,33 +111,40 @@ public class StackStateAnimator {
}
// start translationY animation
if (child.getTranslationY() != viewState.yTranslation) {
startYTranslationAnimation(child, viewState, hasNewEvents);
startYTranslationAnimation(child, viewState);
}
// start translationZ animation
if (child.getTranslationZ() != viewState.zTranslation) {
startZTranslationAnimation(child, viewState, hasNewEvents);
startZTranslationAnimation(child, viewState);
}
// start scale animation
if (child.getScaleX() != viewState.scale) {
startScaleAnimation(child, viewState);
}
// start alpha animation
if (alpha != child.getAlpha()) {
startAlphaAnimation(child, viewState, hasNewEvents);
startAlphaAnimation(child, viewState);
}
// start height animation
if (viewState.height != child.getActualHeight()) {
startHeightAnimation(child, viewState, hasNewEvents);
startHeightAnimation(child, viewState);
}
// start dimmed animation
child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
}
private void startHeightAnimation(final ExpandableView child,
StackScrollState.ViewState viewState, boolean hasNewEvents) {
Integer previousEndValue = getChildTag(child,TAG_END_HEIGHT);
StackScrollState.ViewState viewState) {
Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
if (previousEndValue != null && previousEndValue == viewState.height) {
return;
}
ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
mAnimationFilter.animateHeight);
if (newDuration <= 0) {
// no new animation needed, let's just apply the value
child.setActualHeight(viewState.height);
child.setActualHeight(viewState.height, false /* notifyListeners */);
if (previousAnimator != null && !isRunning()) {
onAnimationFinished();
}
@@ -144,7 +155,8 @@ public class StackStateAnimator {
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
child.setActualHeight((int) animation.getAnimatedValue());
child.setActualHeight((int) animation.getAnimatedValue(),
false /* notifyListeners */);
}
});
animator.setInterpolator(mFastOutSlowInInterpolator);
@@ -164,14 +176,15 @@ public class StackStateAnimator {
}
private void startAlphaAnimation(final ExpandableView child,
final StackScrollState.ViewState viewState, boolean hasNewEvents) {
final StackScrollState.ViewState viewState) {
final float endAlpha = viewState.alpha;
Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
if (previousEndValue != null && previousEndValue == endAlpha) {
return;
}
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
mAnimationFilter.animateAlpha);
if (newDuration <= 0) {
// no new animation needed, let's just apply the value
child.setAlpha(endAlpha);
@@ -228,13 +241,14 @@ public class StackStateAnimator {
}
private void startZTranslationAnimation(final ExpandableView child,
final StackScrollState.ViewState viewState, boolean hasNewEvents) {
final StackScrollState.ViewState viewState) {
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
if (previousEndValue != null && previousEndValue == viewState.zTranslation) {
return;
}
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
mAnimationFilter.animateZ);
if (newDuration <= 0) {
// no new animation needed, let's just apply the value
child.setTranslationZ(viewState.zTranslation);
@@ -264,13 +278,14 @@ public class StackStateAnimator {
}
private void startYTranslationAnimation(final ExpandableView child,
StackScrollState.ViewState viewState, boolean hasNewEvents) {
StackScrollState.ViewState viewState) {
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
if (previousEndValue != null && previousEndValue == viewState.yTranslation) {
return;
}
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
mAnimationFilter.animateY);
if (newDuration <= 0) {
// no new animation needed, let's just apply the value
child.setTranslationY(viewState.yTranslation);
@@ -298,6 +313,46 @@ public class StackStateAnimator {
child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation);
}
private void startScaleAnimation(final ExpandableView child,
StackScrollState.ViewState viewState) {
Float previousEndValue = getChildTag(child, TAG_END_SCALE);
if (previousEndValue != null && previousEndValue == viewState.scale) {
return;
}
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
mAnimationFilter.animateScale);
if (newDuration <= 0) {
// no new animation needed, let's just apply the value
child.setScaleX(viewState.scale);
child.setScaleY(viewState.scale);
if (previousAnimator != null && !isRunning()) {
onAnimationFinished();
}
return;
}
PropertyValuesHolder holderX =
PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), viewState.scale);
PropertyValuesHolder holderY =
PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), viewState.scale);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
animator.setInterpolator(mFastOutSlowInInterpolator);
animator.setDuration(newDuration);
animator.addListener(getGlobalAnimationFinishedListener());
// remove the tag when the animation is finished
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
child.setTag(TAG_ANIMATOR_SCALE, null);
child.setTag(TAG_END_SCALE, null);
}
});
startInstantly(animator);
child.setTag(TAG_ANIMATOR_SCALE, animator);
child.setTag(TAG_END_SCALE, viewState.scale);
}
/**
* Start an animator instantly instead of waiting on the next synchronization frame
*/
@@ -349,21 +404,22 @@ public class StackStateAnimator {
* Cancel the previous animator and get the duration of the new animation.
*
* @param previousAnimator the animator which was running before
* @param hasNewEvents indicating whether new events came in in this animation
* @param newAnimationNeeded indicating whether a new animation should be started for this
* property
* @return the new duration
*/
private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator,
boolean hasNewEvents) {
boolean newAnimationNeeded) {
long newDuration = ANIMATION_DURATION;
if (previousAnimator != null) {
if (!hasNewEvents) {
if (!newAnimationNeeded) {
// This is only an update, no new event came in. lets just take the remaining
// duration as the new duration
newDuration = previousAnimator.getDuration()
- previousAnimator.getCurrentPlayTime();
}
previousAnimator.cancel();
} else if (!hasNewEvents){
} else if (!newAnimationNeeded){
newDuration = 0;
}
return newDuration;

View File

@@ -150,4 +150,11 @@ public class TvStatusBar extends BaseStatusBar {
protected void refreshLayout(int layoutDirection) {
}
@Override
public void onActivated(View view) {
}
@Override
public void onActivationReset(View view) {
}
}