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

Commit 40f9d02a authored by Josh Tsuji's avatar Josh Tsuji Committed by Android (Google) Code Review
Browse files

Merge "Adds PhysicsPropertyAnimator, which simplifies animation controllers."

parents 67a72e81 c1108434
Loading
Loading
Loading
Loading
−68.9 KiB
Loading image diff...
+58 −6
Original line number Diff line number Diff line
@@ -25,22 +25,74 @@ Value to add every time chained animations update the subsequent animation in th
Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed.

### Animation Control Methods
![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png)
Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded```, ```onChildRemoved```, and ```setChildVisibility``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out/visible/gone. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.

In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation.
In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`. 

For example, moving the first child view to *(100, 200)*:
#### PhysicsPropertyAnimator

Like `ViewPropertyAnimator`, `PhysicsPropertyAnimator` provides the following methods for animating properties:
- `alpha(float)`
- `translationX/Y/Z(float)`
- `scaleX/Y(float)`

It also provides the following configuration methods:
- `withStartDelay(int)`, for starting the animation after a given delay.
- `withStartVelocity(float)`, for starting the animation with the given start velocity.
- `withPositionStartVelocities(float, float)`, for setting specific start velocities for TRANSLATION_X and TRANSLATION_Y, since these typically differ.
- `start(Runnable)`, to start the animation, with an optional end action to call when the animations for every property (including chained animations) have completed.

For example, moving the first child view:

```
animateValueForChildAtIndex(TRANSLATION_X, 0, 100);
animateValueForChildAtIndex(TRANSLATION_Y, 0, 200);
animationForChild(getChildAt(0))
    .translationX(100)
    .translationY(200)
    .setStartDelay(500)
    .start();
```

This would use the physics animations constructed by the layout to spring the view to *(100, 200)*.
This would use the physics animations constructed by the layout to spring the view to *(100, 200)* after 500ms.

If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```.

##### Advanced Usage
The animator has additional functionality to reduce the amount of boilerplate required for typical physics animation use cases.

- Often, animations will set starting values for properties before the animation begins. Property methods like `translationX` have an overloaded variant: `translationX(from, to)`. When `start()` is called, the animation will set the view's translationX property to `from` before beginning the animation to `to`.
- We may want to use different end actions for each property. For example, if we're animating a view to the bottom of the screen, and also fading it out, we might want to perform an action as soon as the fade out is complete. We can use `alpha(to, endAction)`, which will call endAction as soon as the alpha animation is finished. A special case is `position(x, y, endAction)`, where the endAction is called when both translationX and translationY animations have completed.

`PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.

##### Examples
Spring the stack of bubbles (whose animations are chained) to the bottom of the screen, shrinking them to 50% size. Once the first bubble is done shrinking, begin fading them out, and then remove them all from the parent once all bubbles have faded out:

```
animationForChild(leadBubble)
    .position(screenCenter, screenBottom)
    .scaleX(0.5f)
    .scaleY(0.5f, () -> animationForChild(leadBubble).alpha(0).start(removeAllFromParent))
    .start();
```

'Drop in' a child view that was just added to the layout:

```
animationForChild(newView)
    .scaleX(1.15f /* from */, 1f /* to */)
    .scaleY(1.15f /* from */, 1f /* to */)
    .alpha(0f /* from */, 1f /* to */)
    .position(posX, posY)
    .start();
```

Move every view except for the first to x = (index - 1) * 50, then remove the first view.

```
animationsForChildrenFromIndex(1, (index, anim) -> anim.translationX((index - 1) * 50))
    .startAll(removeFirstView);
```

## PhysicsAnimationLayout
The layout itself is a FrameLayout descendant with a few extra methods:

+2 −1
Original line number Diff line number Diff line
@@ -111,13 +111,14 @@
    <!-- Optional cancel button on Keyguard -->
    <item type="id" name="cancel_button"/>

    <!-- For saving DynamicAnimation physics animations as view tags. -->
    <!-- For saving PhysicsAnimationLayout animations/animators as view tags. -->
    <item type="id" name="translation_x_dynamicanimation_tag"/>
    <item type="id" name="translation_y_dynamicanimation_tag"/>
    <item type="id" name="translation_z_dynamicanimation_tag"/>
    <item type="id" name="alpha_dynamicanimation_tag"/>
    <item type="id" name="scale_x_dynamicanimation_tag"/>
    <item type="id" name="scale_y_dynamicanimation_tag"/>
    <item type="id" name="physics_animator_tag"/>

    <!-- Global Actions Menu -->
    <item type="id" name="global_actions_view" />
+57 −87
Original line number Diff line number Diff line
@@ -104,16 +104,23 @@ public class ExpandedAnimationController
     * @return The y-value to which the bubbles were expanded, in case that's useful.
     */
    public float expandFromStack(PointF collapseTo, Runnable after) {
        mCollapseToPoint = collapseTo;
        animationsForChildrenFromIndex(
                0, /* startIndex */
                new ChildAnimationConfigurator() {
                    // How much to translate the next bubble, so that it is not overlapping the
                    // previous one.
                    float mTranslateNextBubbleXBy = mBubblePaddingPx;

        // How much to translate the next bubble, so that it is not overlapping the previous one.
        float translateNextBubbleXBy = mBubblePaddingPx;
        for (int i = 0; i < mLayout.getChildCount(); i++) {
            mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY());
            translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
                    @Override
                    public void configureAnimationForChildAtIndex(
                            int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) {
                        animation.position(mTranslateNextBubbleXBy, getExpandedY());
                        mTranslateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
                    }
            })
            .startAll(after);

        runAfterTranslationsEnd(after);
        mCollapseToPoint = collapseTo;
        return getExpandedY();
    }

@@ -121,13 +128,14 @@ public class ExpandedAnimationController
    public void collapseBackToStack(Runnable after) {
        // Stack to the left if we're going to the left, or right if not.
        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1;
        for (int i = 0; i < mLayout.getChildCount(); i++) {
            mLayout.animatePositionForChildAtIndex(
                    i,
                    mCollapseToPoint.x + (sideMultiplier * i * mStackOffsetPx), mCollapseToPoint.y);
        }

        runAfterTranslationsEnd(after);
        animationsForChildrenFromIndex(
                0, /* startIndex */
                (index, animation) ->
                    animation.position(
                            mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx),
                            mCollapseToPoint.y))
            .startAll(after /* endAction */);
    }

    /** Prepares the given bubble to be dragged out. */
@@ -164,20 +172,10 @@ public class ExpandedAnimationController
    public void snapBubbleBack(View bubbleView, float velX, float velY) {
        final int index = mLayout.indexOfChild(bubbleView);

        // Snap the bubble back, respecting its current velocity.
        mLayout.animateValueForChildAtIndex(
                DynamicAnimation.TRANSLATION_X, index, getXForChildAtIndex(index), velX);
        mLayout.animateValueForChildAtIndex(
                DynamicAnimation.TRANSLATION_Y, index, getExpandedY(), velY);
        mLayout.setEndListenerForProperties(
                mLayout.new OneTimeMultiplePropertyEndListener() {
                    @Override
                    void onAllAnimationsForPropertiesEnd() {
                        // Reset Z translation once the bubble is done snapping back.
                        bubbleView.setTranslationZ(0f);
                    }
                },
                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
        animationForChildAtIndex(index)
                .position(getXForChildAtIndex(index), getExpandedY())
                .withPositionStartVelocities(velX, velY)
                .start(() -> bubbleView.setTranslationZ(0f) /* after */);

        animateStackByBubbleWidthsStartingFrom(
                /* numBubbleWidths */ 0, /* startIndex */ index + 1);
@@ -202,12 +200,8 @@ public class ExpandedAnimationController
     */
    public void updateYPosition(Runnable after) {
        if (mLayout == null) return;

        for (int i = 0; i < mLayout.getChildCount(); i++) {
            boolean isLast = i == mLayout.getChildCount() - 1;
            mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, i,
                    getExpandedY(), isLast ? after : null);
        }
        animationsForChildrenFromIndex(
                0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
    }

    /**
@@ -216,12 +210,11 @@ public class ExpandedAnimationController
     * positions.
     */
    private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
        for (int i = startIndex; i < mLayout.getChildCount(); i++) {
            mLayout.animateValueForChildAtIndex(
                    DynamicAnimation.TRANSLATION_X,
                    i,
                    getXForChildAtIndex(i + numBubbleWidths));
        }
        animationsForChildrenFromIndex(
                startIndex,
                (index, animation) ->
                        animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
            .startAll();
    }

    /** The Y value of the row of expanded bubbles. */
@@ -248,21 +241,6 @@ public class ExpandedAnimationController
        }
    }

    /** Runs the given Runnable after all translation-related animations have ended. */
    private void runAfterTranslationsEnd(Runnable after) {
        DynamicAnimation.OnAnimationEndListener allEndedListener =
                (animation, canceled, value, velocity) -> {
                    if (!mLayout.arePropertiesAnimating(
                            DynamicAnimation.TRANSLATION_X,
                            DynamicAnimation.TRANSLATION_Y)) {
                        after.run();
                    }
                };

        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X);
        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y);
    }

    @Override
    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
        return Sets.newHashSet(
@@ -295,8 +273,12 @@ public class ExpandedAnimationController
        // Pop in from the top.
        // TODO: Reverse this when bubbles are at the bottom.
        child.setTranslationX(getXForChildAtIndex(index));
        child.setTranslationY(getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR);
        mLayout.animateValueForChild(DynamicAnimation.TRANSLATION_Y, child, getExpandedY());

        animationForChild(child)
                .translationY(
                        getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
                        getExpandedY() /* to */)
                .start();
        animateBubblesAfterIndexToCorrectX(index);
    }

@@ -304,36 +286,26 @@ public class ExpandedAnimationController
    void onChildRemoved(View child, int index, Runnable finishRemoval) {
        // Bubble pops out to the top.
        // TODO: Reverse this when bubbles are at the bottom.
        mLayout.animateValueForChild(
                DynamicAnimation.ALPHA, child, 0f, finishRemoval);

        final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
        animator.alpha(0f, finishRemoval /* endAction */);

        // If we're removing the dragged-out bubble, that means it got dismissed.
        if (child.equals(mBubbleDraggingOut)) {
            // Throw it to the bottom of the screen, towards the center horizontally.
            mLayout.animateValueForChild(
                    DynamicAnimation.TRANSLATION_X,
                    child,
            animator.position(
                            mLayout.getWidth() / 2f - mBubbleSizePx / 2f,
                    mBubbleDraggingOutVelX);
            mLayout.animateValueForChild(
                    DynamicAnimation.TRANSLATION_Y,
                    child,
                    mLayout.getHeight() + mBubbleSizePx,
                    mBubbleDraggingOutVelY);

            // Scale it down a bit so it looks like it's disappearing.
            mLayout.animateValueForChild(DynamicAnimation.SCALE_X, child, ANIMATE_SCALE_PERCENT);
            mLayout.animateValueForChild(DynamicAnimation.SCALE_Y, child, ANIMATE_SCALE_PERCENT);
                            mLayout.getHeight() + mBubbleSizePx)
                    .withPositionStartVelocities(mBubbleDraggingOutVelX, mBubbleDraggingOutVelY)
                    .scaleX(ANIMATE_SCALE_PERCENT)
                    .scaleY(ANIMATE_SCALE_PERCENT);

            mBubbleDraggingOut = null;
        } else {
            // If we're removing some random bubble just throw it off the top.
            mLayout.animateValueForChild(
                    DynamicAnimation.TRANSLATION_Y,
                    child,
                    getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR);
            animator.translationY(getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR);
        }

        animator.start();

        // Animate all the other bubbles to their new positions sans this bubble.
        animateBubblesAfterIndexToCorrectX(index);
    }
@@ -346,12 +318,9 @@ public class ExpandedAnimationController
            child.setVisibility(View.VISIBLE);
        }

        // Fade in.
        mLayout.animateValueForChild(
                DynamicAnimation.ALPHA,
                child,
                /* value */ visibility == View.GONE ? 0f : 1f,
                () -> super.setChildVisibility(child, index, visibility));
        animationForChild(child)
                .alpha(visibility == View.GONE ? 0f : 1f)
                .start(() -> super.setChildVisibility(child, index, visibility) /* after */);
    }

    /**
@@ -365,8 +334,9 @@ public class ExpandedAnimationController
            // Don't animate the dragging out bubble, or it'll jump around while being dragged. It
            // will be snapped to the correct X value after the drag (if it's not dismissed).
            if (!bubble.equals(mBubbleDraggingOut)) {
                mLayout.animateValueForChild(
                        DynamicAnimation.TRANSLATION_X, bubble, getXForChildAtIndex(i));
                animationForChild(bubble)
                        .translationX(getXForChildAtIndex(i))
                        .start();
            }
        }
    }
+359 −143

File changed.

Preview size limit exceeded, changes collapsed.

Loading