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:
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user