Loading packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +4 −0 Original line number Diff line number Diff line Loading @@ -1078,6 +1078,10 @@ public abstract class BaseStatusBar extends SystemUI implements mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); } } else { if (entry.row.getVisibility() == View.GONE) { // notify the scroller of a child addition mStackScroller.generateAddAnimation(entry.row); } entry.row.setVisibility(View.VISIBLE); visibleNotifications++; } Loading packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +162 −20 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.AnimationUtils; import android.widget.OverScroller; import com.android.systemui.ExpandHelper; Loading @@ -41,6 +42,8 @@ import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.statusbar.policy.ScrollAdapter; import java.util.ArrayList; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ Loading Loading @@ -90,10 +93,28 @@ public class NotificationStackScrollLayout extends ViewGroup /** * The current State this Layout is in */ private final StackScrollState mCurrentStackScrollState = new StackScrollState(this); private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<ChildHierarchyChangeEvent> mAnimationEvents = new ArrayList<ChildHierarchyChangeEvent>(); private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); private OnChildLocationsChangedListener mListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private boolean mChildHierarchyDirty; private boolean mIsExpanded = true; private ViewTreeObserver.OnPreDrawListener mAfterLayoutPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { updateScrollPositionIfNecessary(); updateChildren(); getViewTreeObserver().removeOnPreDrawListener(this); return true; } }; public NotificationStackScrollLayout(Context context) { this(context, null); Loading Loading @@ -184,16 +205,7 @@ public class NotificationStackScrollLayout extends ViewGroup } setMaxLayoutHeight(getHeight() - mEmptyMarginBottom); updateContentHeight(); getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { updateScrollPositionIfNecessary(); updateChildren(); getViewTreeObserver().removeOnPreDrawListener(this); return true; } }); getViewTreeObserver().addOnPreDrawListener(mAfterLayoutPreDrawListener); } public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { Loading Loading @@ -228,22 +240,20 @@ public class NotificationStackScrollLayout extends ViewGroup * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. */ private void updateChildren() { if (!isCurrentlyAnimating()) { mCurrentStackScrollState.setScrollY(mOwnScrollY); mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState); mListenForHeightChanges = false; mCurrentStackScrollState.apply(); mListenForHeightChanges = true; if (!isCurrentlyAnimating() && !mChildHierarchyDirty) { applyCurrentState(); if (mListener != null) { mListener.onChildLocationsChanged(this); } } else { // TODO: handle animation startAnimationToState(mCurrentStackScrollState); } } private boolean isCurrentlyAnimating() { return false; return mStateAnimator.isRunning(); } private void updateScrollPositionIfNecessary() { Loading Loading @@ -288,6 +298,7 @@ public class NotificationStackScrollLayout extends ViewGroup veto.performClick(); } setSwipingInProgress(false); mSwipedOutViews.add(v); } public void onBeginDrag(View v) { Loading Loading @@ -734,6 +745,50 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); mStackScrollAlgorithm.notifyChildrenChanged(this); updateScrollStateForRemovedChild(child); if (mIsExpanded) { // Generate Animations mChildrenToRemoveAnimated.add(child); mChildHierarchyDirty = true; } } /** * Updates the scroll position when a child was removed * * @param removedChild the removed child */ private void updateScrollStateForRemovedChild(View removedChild) { int startingPosition = getPositionInLinearLayout(removedChild); int childHeight = removedChild.getHeight() + mPaddingBetweenElements; int endPosition = startingPosition + childHeight; if (endPosition <= mOwnScrollY) { // This child is fully scrolled of the top, so we have to deduct its height from the // scrollPosition mOwnScrollY -= childHeight; } else if (startingPosition < mOwnScrollY) { // This child is currently being scrolled into, set the scroll position to the start of // this child mOwnScrollY = startingPosition; } } private int getPositionInLinearLayout(View requestedChild) { int position = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child == requestedChild) { return position; } if (child.getVisibility() != View.GONE) { position += child.getHeight(); if (i < getChildCount()-1) { position += mPaddingBetweenElements; } } } return 0; } @Override Loading @@ -741,6 +796,64 @@ public class NotificationStackScrollLayout extends ViewGroup super.onViewAdded(child); mStackScrollAlgorithm.notifyChildrenChanged(this); ((ExpandableView) child).setOnHeightChangedListener(this); if (child.getVisibility() != View.GONE) { generateAddAnimation(child); } } public void generateAddAnimation(View child) { if (mIsExpanded) { // Generate Animations mChildrenToAddAnimated.add(child); mChildHierarchyDirty = true; } } /** * Change the position of child to a new location * * @param child the view to change the position for * @param newIndex the new index */ public void changeViewPosition(View child, int newIndex) { if (child != null && child.getParent() == this) { // TODO: handle this } } private void startAnimationToState(StackScrollState finalState) { if (mChildHierarchyDirty) { generateChildHierarchyEvents(); mChildHierarchyDirty = false; } mStateAnimator.startAnimationForEvents(mAnimationEvents, finalState); } private void generateChildHierarchyEvents() { generateChildAdditionEvents(); generateChildRemovalEvents(); mChildHierarchyDirty = false; } private void generateChildRemovalEvents() { for (View child : mChildrenToRemoveAnimated) { boolean childWasSwipedOut = mSwipedOutViews.contains(child); int animationType = childWasSwipedOut ? ChildHierarchyChangeEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT : ChildHierarchyChangeEvent.ANIMATION_TYPE_REMOVE; mAnimationEvents.add(new ChildHierarchyChangeEvent(child, animationType)); } mSwipedOutViews.clear(); mChildrenToRemoveAnimated.clear(); } private void generateChildAdditionEvents() { for (View child : mChildrenToAddAnimated) { mAnimationEvents.add(new ChildHierarchyChangeEvent(child, ChildHierarchyChangeEvent.ANIMATION_TYPE_ADD)); } mChildrenToAddAnimated.clear(); } private boolean onInterceptTouchEventScroll(MotionEvent ev) { Loading Loading @@ -895,6 +1008,7 @@ public class NotificationStackScrollLayout extends ViewGroup } public void setIsExpanded(boolean isExpanded) { mIsExpanded = isExpanded; mStackScrollAlgorithm.setIsExpanded(isExpanded); if (!isExpanded) { mOwnScrollY = 0; Loading @@ -903,7 +1017,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onHeightChanged(ExpandableView view) { if (mListenForHeightChanges) { if (mListenForHeightChanges && !isCurrentlyAnimating()) { updateContentHeight(); updateScrollPositionIfNecessary(); if (mOnHeightChangedListener != null) { Loading @@ -918,10 +1032,38 @@ public class NotificationStackScrollLayout extends ViewGroup this.mOnHeightChangedListener = mOnHeightChangedListener; } public void onChildAnimationFinished() { applyCurrentState(); mAnimationEvents.clear(); } private void applyCurrentState() { mListenForHeightChanges = false; mCurrentStackScrollState.apply(); mListenForHeightChanges = true; } /** * A listener that is notified when some child locations might have changed. */ public interface OnChildLocationsChangedListener { public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); } static class ChildHierarchyChangeEvent { static int ANIMATION_TYPE_ADD = 1; static int ANIMATION_TYPE_REMOVE = 2; static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3; final long eventStartTime; final View changingView; final int animationType; ChildHierarchyChangeEvent(View view, int type) { eventStartTime = AnimationUtils.currentAnimationTimeMillis(); changingView = view; animationType = type; } } } packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +1 −2 Original line number Diff line number Diff line Loading @@ -72,12 +72,11 @@ public class StackScrollState { } // initialize with the default values of the view viewState.height = child.getActualHeight(); viewState.alpha = 1; viewState.gone = child.getVisibility() == View.GONE; viewState.alpha = 1; } } public ViewState getViewStateForView(View requestedView) { return mStateMap.get(requestedView); } Loading packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java 0 → 100644 +148 −0 Original line number Diff line number Diff line /* * 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.animation.Animator; import android.animation.ValueAnimator; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; /** * An stack state animator which handles animations to new StackScrollStates */ public class StackStateAnimator { private static final int ANIMATION_DURATION = 360; private final Interpolator mFastOutSlowInInterpolator; public NotificationStackScrollLayout mHostLayout; private boolean mAnimationIsRunning; private ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mHandledEvents = new ArrayList<>(); public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), android.R.interpolator.fast_out_slow_in); } public boolean isRunning() { return mAnimationIsRunning; } public void startAnimationForEvents( ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mAnimationEvents, StackScrollState finalState) { int numEvents = mAnimationEvents.size(); if (numEvents == 0) { // No events, so we don't perform any animation return; } long lastEventStartTime = mAnimationEvents.get(numEvents - 1).eventStartTime; long eventEnd = lastEventStartTime + ANIMATION_DURATION; long currentTime = AnimationUtils.currentAnimationTimeMillis(); long newDuration = eventEnd - currentTime; if (newDuration <= 0) { // last event is long before this, so we don't do anything return; } initializeAddedViewStates(mAnimationEvents, finalState); int childCount = mHostLayout.getChildCount(); for (int i = 0; i < childCount; i++) { final boolean isFirstView = i == 0; final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); StackScrollState.ViewState viewState = finalState.getViewStateForView(child); if (viewState == null) { continue; } int childVisibility = child.getVisibility(); boolean wasVisible = childVisibility == View.VISIBLE; final float alpha = viewState.alpha; if (!wasVisible && alpha != 0 && !viewState.gone) { child.setVisibility(View.VISIBLE); } startPropertyAnimation(newDuration, isFirstView, child, viewState, alpha); // TODO: animate clipBounds child.setClipBounds(null); int currentHeigth = child.getActualHeight(); if (viewState.height != currentHeigth) { startHeightAnimation(newDuration, child, viewState, currentHeigth); } } mAnimationIsRunning = true; } private void startPropertyAnimation(long newDuration, final boolean isFirstView, final ExpandableView child, StackScrollState.ViewState viewState, final float alpha) { child.animate().setInterpolator(mFastOutSlowInInterpolator) .alpha(alpha) .translationY(viewState.yTranslation) .translationZ(viewState.zTranslation) .setDuration(newDuration) .withEndAction(new Runnable() { @Override public void run() { mAnimationIsRunning = false; if (isFirstView) { mHandledEvents.clear(); mHostLayout.onChildAnimationFinished(); } if (alpha == 0) { child.setVisibility(View.INVISIBLE); } } }); } private void startHeightAnimation(long newDuration, final ExpandableView child, StackScrollState.ViewState viewState, int currentHeigth) { ValueAnimator heightAnimator = ValueAnimator.ofInt(currentHeigth, viewState.height); heightAnimator.setInterpolator(mFastOutSlowInInterpolator); heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { child.setActualHeight((int) animation.getAnimatedValue()); } }); heightAnimator.setDuration(newDuration); heightAnimator.start(); } private void initializeAddedViewStates( ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mAnimationEvents, StackScrollState finalState) { for (NotificationStackScrollLayout.ChildHierarchyChangeEvent event: mAnimationEvents) { View changingView = event.changingView; if (event.animationType == NotificationStackScrollLayout.ChildHierarchyChangeEvent .ANIMATION_TYPE_ADD && !mHandledEvents.contains(event)) { // This item is added, initialize it's properties. StackScrollState.ViewState viewState = finalState.getViewStateForView(changingView); changingView.setAlpha(0); changingView.setTranslationY(viewState.yTranslation); changingView.setTranslationZ(viewState.zTranslation); mHandledEvents.add(event); } } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +4 −0 Original line number Diff line number Diff line Loading @@ -1078,6 +1078,10 @@ public abstract class BaseStatusBar extends SystemUI implements mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); } } else { if (entry.row.getVisibility() == View.GONE) { // notify the scroller of a child addition mStackScroller.generateAddAnimation(entry.row); } entry.row.setVisibility(View.VISIBLE); visibleNotifications++; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +162 −20 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.AnimationUtils; import android.widget.OverScroller; import com.android.systemui.ExpandHelper; Loading @@ -41,6 +42,8 @@ import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.statusbar.policy.ScrollAdapter; import java.util.ArrayList; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ Loading Loading @@ -90,10 +93,28 @@ public class NotificationStackScrollLayout extends ViewGroup /** * The current State this Layout is in */ private final StackScrollState mCurrentStackScrollState = new StackScrollState(this); private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<ChildHierarchyChangeEvent> mAnimationEvents = new ArrayList<ChildHierarchyChangeEvent>(); private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); private OnChildLocationsChangedListener mListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private boolean mChildHierarchyDirty; private boolean mIsExpanded = true; private ViewTreeObserver.OnPreDrawListener mAfterLayoutPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { updateScrollPositionIfNecessary(); updateChildren(); getViewTreeObserver().removeOnPreDrawListener(this); return true; } }; public NotificationStackScrollLayout(Context context) { this(context, null); Loading Loading @@ -184,16 +205,7 @@ public class NotificationStackScrollLayout extends ViewGroup } setMaxLayoutHeight(getHeight() - mEmptyMarginBottom); updateContentHeight(); getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { updateScrollPositionIfNecessary(); updateChildren(); getViewTreeObserver().removeOnPreDrawListener(this); return true; } }); getViewTreeObserver().addOnPreDrawListener(mAfterLayoutPreDrawListener); } public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { Loading Loading @@ -228,22 +240,20 @@ public class NotificationStackScrollLayout extends ViewGroup * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. */ private void updateChildren() { if (!isCurrentlyAnimating()) { mCurrentStackScrollState.setScrollY(mOwnScrollY); mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState); mListenForHeightChanges = false; mCurrentStackScrollState.apply(); mListenForHeightChanges = true; if (!isCurrentlyAnimating() && !mChildHierarchyDirty) { applyCurrentState(); if (mListener != null) { mListener.onChildLocationsChanged(this); } } else { // TODO: handle animation startAnimationToState(mCurrentStackScrollState); } } private boolean isCurrentlyAnimating() { return false; return mStateAnimator.isRunning(); } private void updateScrollPositionIfNecessary() { Loading Loading @@ -288,6 +298,7 @@ public class NotificationStackScrollLayout extends ViewGroup veto.performClick(); } setSwipingInProgress(false); mSwipedOutViews.add(v); } public void onBeginDrag(View v) { Loading Loading @@ -734,6 +745,50 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); mStackScrollAlgorithm.notifyChildrenChanged(this); updateScrollStateForRemovedChild(child); if (mIsExpanded) { // Generate Animations mChildrenToRemoveAnimated.add(child); mChildHierarchyDirty = true; } } /** * Updates the scroll position when a child was removed * * @param removedChild the removed child */ private void updateScrollStateForRemovedChild(View removedChild) { int startingPosition = getPositionInLinearLayout(removedChild); int childHeight = removedChild.getHeight() + mPaddingBetweenElements; int endPosition = startingPosition + childHeight; if (endPosition <= mOwnScrollY) { // This child is fully scrolled of the top, so we have to deduct its height from the // scrollPosition mOwnScrollY -= childHeight; } else if (startingPosition < mOwnScrollY) { // This child is currently being scrolled into, set the scroll position to the start of // this child mOwnScrollY = startingPosition; } } private int getPositionInLinearLayout(View requestedChild) { int position = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child == requestedChild) { return position; } if (child.getVisibility() != View.GONE) { position += child.getHeight(); if (i < getChildCount()-1) { position += mPaddingBetweenElements; } } } return 0; } @Override Loading @@ -741,6 +796,64 @@ public class NotificationStackScrollLayout extends ViewGroup super.onViewAdded(child); mStackScrollAlgorithm.notifyChildrenChanged(this); ((ExpandableView) child).setOnHeightChangedListener(this); if (child.getVisibility() != View.GONE) { generateAddAnimation(child); } } public void generateAddAnimation(View child) { if (mIsExpanded) { // Generate Animations mChildrenToAddAnimated.add(child); mChildHierarchyDirty = true; } } /** * Change the position of child to a new location * * @param child the view to change the position for * @param newIndex the new index */ public void changeViewPosition(View child, int newIndex) { if (child != null && child.getParent() == this) { // TODO: handle this } } private void startAnimationToState(StackScrollState finalState) { if (mChildHierarchyDirty) { generateChildHierarchyEvents(); mChildHierarchyDirty = false; } mStateAnimator.startAnimationForEvents(mAnimationEvents, finalState); } private void generateChildHierarchyEvents() { generateChildAdditionEvents(); generateChildRemovalEvents(); mChildHierarchyDirty = false; } private void generateChildRemovalEvents() { for (View child : mChildrenToRemoveAnimated) { boolean childWasSwipedOut = mSwipedOutViews.contains(child); int animationType = childWasSwipedOut ? ChildHierarchyChangeEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT : ChildHierarchyChangeEvent.ANIMATION_TYPE_REMOVE; mAnimationEvents.add(new ChildHierarchyChangeEvent(child, animationType)); } mSwipedOutViews.clear(); mChildrenToRemoveAnimated.clear(); } private void generateChildAdditionEvents() { for (View child : mChildrenToAddAnimated) { mAnimationEvents.add(new ChildHierarchyChangeEvent(child, ChildHierarchyChangeEvent.ANIMATION_TYPE_ADD)); } mChildrenToAddAnimated.clear(); } private boolean onInterceptTouchEventScroll(MotionEvent ev) { Loading Loading @@ -895,6 +1008,7 @@ public class NotificationStackScrollLayout extends ViewGroup } public void setIsExpanded(boolean isExpanded) { mIsExpanded = isExpanded; mStackScrollAlgorithm.setIsExpanded(isExpanded); if (!isExpanded) { mOwnScrollY = 0; Loading @@ -903,7 +1017,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onHeightChanged(ExpandableView view) { if (mListenForHeightChanges) { if (mListenForHeightChanges && !isCurrentlyAnimating()) { updateContentHeight(); updateScrollPositionIfNecessary(); if (mOnHeightChangedListener != null) { Loading @@ -918,10 +1032,38 @@ public class NotificationStackScrollLayout extends ViewGroup this.mOnHeightChangedListener = mOnHeightChangedListener; } public void onChildAnimationFinished() { applyCurrentState(); mAnimationEvents.clear(); } private void applyCurrentState() { mListenForHeightChanges = false; mCurrentStackScrollState.apply(); mListenForHeightChanges = true; } /** * A listener that is notified when some child locations might have changed. */ public interface OnChildLocationsChangedListener { public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); } static class ChildHierarchyChangeEvent { static int ANIMATION_TYPE_ADD = 1; static int ANIMATION_TYPE_REMOVE = 2; static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3; final long eventStartTime; final View changingView; final int animationType; ChildHierarchyChangeEvent(View view, int type) { eventStartTime = AnimationUtils.currentAnimationTimeMillis(); changingView = view; animationType = type; } } }
packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +1 −2 Original line number Diff line number Diff line Loading @@ -72,12 +72,11 @@ public class StackScrollState { } // initialize with the default values of the view viewState.height = child.getActualHeight(); viewState.alpha = 1; viewState.gone = child.getVisibility() == View.GONE; viewState.alpha = 1; } } public ViewState getViewStateForView(View requestedView) { return mStateMap.get(requestedView); } Loading
packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java 0 → 100644 +148 −0 Original line number Diff line number Diff line /* * 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.animation.Animator; import android.animation.ValueAnimator; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; /** * An stack state animator which handles animations to new StackScrollStates */ public class StackStateAnimator { private static final int ANIMATION_DURATION = 360; private final Interpolator mFastOutSlowInInterpolator; public NotificationStackScrollLayout mHostLayout; private boolean mAnimationIsRunning; private ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mHandledEvents = new ArrayList<>(); public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), android.R.interpolator.fast_out_slow_in); } public boolean isRunning() { return mAnimationIsRunning; } public void startAnimationForEvents( ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mAnimationEvents, StackScrollState finalState) { int numEvents = mAnimationEvents.size(); if (numEvents == 0) { // No events, so we don't perform any animation return; } long lastEventStartTime = mAnimationEvents.get(numEvents - 1).eventStartTime; long eventEnd = lastEventStartTime + ANIMATION_DURATION; long currentTime = AnimationUtils.currentAnimationTimeMillis(); long newDuration = eventEnd - currentTime; if (newDuration <= 0) { // last event is long before this, so we don't do anything return; } initializeAddedViewStates(mAnimationEvents, finalState); int childCount = mHostLayout.getChildCount(); for (int i = 0; i < childCount; i++) { final boolean isFirstView = i == 0; final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); StackScrollState.ViewState viewState = finalState.getViewStateForView(child); if (viewState == null) { continue; } int childVisibility = child.getVisibility(); boolean wasVisible = childVisibility == View.VISIBLE; final float alpha = viewState.alpha; if (!wasVisible && alpha != 0 && !viewState.gone) { child.setVisibility(View.VISIBLE); } startPropertyAnimation(newDuration, isFirstView, child, viewState, alpha); // TODO: animate clipBounds child.setClipBounds(null); int currentHeigth = child.getActualHeight(); if (viewState.height != currentHeigth) { startHeightAnimation(newDuration, child, viewState, currentHeigth); } } mAnimationIsRunning = true; } private void startPropertyAnimation(long newDuration, final boolean isFirstView, final ExpandableView child, StackScrollState.ViewState viewState, final float alpha) { child.animate().setInterpolator(mFastOutSlowInInterpolator) .alpha(alpha) .translationY(viewState.yTranslation) .translationZ(viewState.zTranslation) .setDuration(newDuration) .withEndAction(new Runnable() { @Override public void run() { mAnimationIsRunning = false; if (isFirstView) { mHandledEvents.clear(); mHostLayout.onChildAnimationFinished(); } if (alpha == 0) { child.setVisibility(View.INVISIBLE); } } }); } private void startHeightAnimation(long newDuration, final ExpandableView child, StackScrollState.ViewState viewState, int currentHeigth) { ValueAnimator heightAnimator = ValueAnimator.ofInt(currentHeigth, viewState.height); heightAnimator.setInterpolator(mFastOutSlowInInterpolator); heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { child.setActualHeight((int) animation.getAnimatedValue()); } }); heightAnimator.setDuration(newDuration); heightAnimator.start(); } private void initializeAddedViewStates( ArrayList<NotificationStackScrollLayout.ChildHierarchyChangeEvent> mAnimationEvents, StackScrollState finalState) { for (NotificationStackScrollLayout.ChildHierarchyChangeEvent event: mAnimationEvents) { View changingView = event.changingView; if (event.animationType == NotificationStackScrollLayout.ChildHierarchyChangeEvent .ANIMATION_TYPE_ADD && !mHandledEvents.contains(event)) { // This item is added, initialize it's properties. StackScrollState.ViewState viewState = finalState.getViewStateForView(changingView); changingView.setAlpha(0); changingView.setTranslationY(viewState.yTranslation); changingView.setTranslationZ(viewState.zTranslation); mHandledEvents.add(event); } } } }