Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3af00cf1 authored by Selim Cinek's avatar Selim Cinek
Browse files

Improved notification scroller animation logic

When an animation was already running, the calculation of the
new duration was wrong. We are now also starting the animation
instantly instead of waiting for the next frame.
Also improved the scrolling performance, which was lagging behind by one frame

Change-Id: I25d6e6eedf33d94f2f90bdc39d863955c707370c
parent eb973565
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -695,7 +695,7 @@ public class NotificationStackScrollLayout extends ViewGroup
//                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
//                    }
                }
                requestChildrenUpdate();
                updateChildren();
            }

            // Keep on drawing until the animation has finished.
@@ -705,7 +705,7 @@ public class NotificationStackScrollLayout extends ViewGroup

    private void customScrollTo(int y) {
        mOwnScrollY = y;
        requestChildrenUpdate();
        updateChildren();
    }

    @Override
@@ -721,7 +721,7 @@ public class NotificationStackScrollLayout extends ViewGroup
            if (clampedY) {
                mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange());
            }
            requestChildrenUpdate();
            updateChildren();
        } else {
            customScrollTo(scrollY);
            scrollTo(scrollX, mScrollY);
+76 −64
Original line number Diff line number Diff line
@@ -132,10 +132,10 @@ public class StackStateAnimator {
        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
        if (newDuration <= 0) {
            if (previousAnimator == null) {
                // no animation was running, but also no new animation should be performed,
                // lets just apply the value
            // no new animation needed, let's just apply the value
            child.setActualHeight(viewState.height);
            if (previousAnimator != null && !isRunning()) {
                onAnimationFinished();
            }
            return;
        }
@@ -158,7 +158,7 @@ public class StackStateAnimator {
                child.setTag(TAG_END_HEIGHT, null);
            }
        });
        animator.start();
        startInstantly(animator);
        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
        child.setTag(TAG_END_HEIGHT, viewState.height);
    }
@@ -173,13 +173,13 @@ public class StackStateAnimator {
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
        if (newDuration <= 0) {
            if (previousAnimator == null) {
                // no animation was running, but also no new animation should be performed,
                // lets just apply the value
            // no new animation needed, let's just apply the value
            child.setAlpha(endAlpha);
            if (endAlpha == 0) {
                child.setVisibility(View.INVISIBLE);
            }
            if (previousAnimator != null && !isRunning()) {
                onAnimationFinished();
            }
            return;
        }
@@ -213,6 +213,7 @@ public class StackStateAnimator {
                mWasCancelled = false;
            }
        });
        animator.setDuration(newDuration);
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
@@ -221,47 +222,11 @@ public class StackStateAnimator {

            }
        });
        animator.start();
        startInstantly(animator);
        child.setTag(TAG_ANIMATOR_ALPHA, animator);
        child.setTag(TAG_END_ALPHA, endAlpha);
    }

    /**
     * @return an adapter which ensures that onAnimationFinished is called once no animation is
     *         running anymore
     */
    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
        if (!mAnimationListenerPool.empty()) {
            return mAnimationListenerPool.pop();
        }

        // We need to create a new one, no reusable ones found
        return new AnimatorListenerAdapter() {
            private boolean mWasCancelled;

            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimatorSet.remove(animation);
                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
                    onAnimationFinished();
                }
                mAnimationListenerPool.push(this);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mWasCancelled = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                mAnimatorSet.add(animation);
                mWasCancelled = false;
            }
        };

    }

    private void startZTranslationAnimation(final ExpandableView child,
            final StackScrollState.ViewState viewState, boolean hasNewEvents) {
        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
@@ -271,10 +236,11 @@ public class StackStateAnimator {
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
        if (newDuration <= 0) {
            if (previousAnimator == null) {
                // no animation was running, but also no new animation should be performed,
                // lets just apply the value
            // no new animation needed, let's just apply the value
            child.setTranslationZ(viewState.zTranslation);

            if (previousAnimator != null && !isRunning()) {
                onAnimationFinished();
            }
            return;
        }
@@ -282,6 +248,7 @@ public class StackStateAnimator {
        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
                child.getTranslationZ(), viewState.zTranslation);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.setDuration(newDuration);
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
@@ -291,7 +258,7 @@ public class StackStateAnimator {
                child.setTag(TAG_END_TRANSLATION_Z, null);
            }
        });
        animator.start();
        startInstantly(animator);
        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
        child.setTag(TAG_END_TRANSLATION_Z, viewState.zTranslation);
    }
@@ -305,10 +272,10 @@ public class StackStateAnimator {
        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
        if (newDuration <= 0) {
            if (previousAnimator == null) {
                // no animation was running, but also no new animation should be performed,
                // lets just apply the value
            // no new animation needed, let's just apply the value
            child.setTranslationY(viewState.yTranslation);
            if (previousAnimator != null && !isRunning()) {
                onAnimationFinished();
            }
            return;
        }
@@ -316,6 +283,7 @@ public class StackStateAnimator {
        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
                child.getTranslationY(), viewState.yTranslation);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.setDuration(newDuration);
        animator.addListener(getGlobalAnimationFinishedListener());
        // remove the tag when the animation is finished
        animator.addListener(new AnimatorListenerAdapter() {
@@ -325,11 +293,54 @@ public class StackStateAnimator {
                child.setTag(TAG_END_TRANSLATION_Y, null);
            }
        });
        animator.start();
        startInstantly(animator);
        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
        child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation);
    }

    /**
     * Start an animator instantly instead of waiting on the next synchronization frame
     */
    private void startInstantly(ValueAnimator animator) {
        animator.start();
        animator.setCurrentPlayTime(0);
    }

    /**
     * @return an adapter which ensures that onAnimationFinished is called once no animation is
     *         running anymore
     */
    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
        if (!mAnimationListenerPool.empty()) {
            return mAnimationListenerPool.pop();
        }

        // We need to create a new one, no reusable ones found
        return new AnimatorListenerAdapter() {
            private boolean mWasCancelled;

            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimatorSet.remove(animation);
                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
                    onAnimationFinished();
                }
                mAnimationListenerPool.push(this);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mWasCancelled = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                mAnimatorSet.add(animation);
                mWasCancelled = false;
            }
        };
    }

    private <T> T getChildTag(View child, int tag) {
        return (T) child.getTag(tag);
    }
@@ -343,18 +354,19 @@ public class StackStateAnimator {
     */
    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator,
            boolean hasNewEvents) {
        long newDuration = ANIMATION_DURATION;
        if (previousAnimator != null) {
            previousAnimator.cancel();
            if (!hasNewEvents) {
                // This is only an update, no new event came in. lets just take the remaining
                // duration as the new duration
                return (long) ((1.0f - previousAnimator.getAnimatedFraction()) *
                        previousAnimator.getDuration());
                newDuration = previousAnimator.getDuration()
                        - previousAnimator.getCurrentPlayTime();
            }
            previousAnimator.cancel();
        } else if (!hasNewEvents){
            return 0;
            newDuration = 0;
        }
        return ANIMATION_DURATION;
        return newDuration;
    }

    private void onAnimationFinished() {