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

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

Merge "Adds DynamicAnimation-based movement to the bubbles."

parents 5d1f0e00 b1a796b1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ android_library {
        "androidx.slice_slice-builders",
        "androidx.arch.core_core-runtime",
        "androidx.lifecycle_lifecycle-extensions",
        "androidx.dynamicanimation_dynamicanimation",
        "SystemUI-tags",
        "SystemUI-proto",
        "dagger2-2.19",
@@ -108,6 +109,7 @@ android_library {
        "androidx.slice_slice-builders",
        "androidx.arch.core_core-runtime",
        "androidx.lifecycle_lifecycle-extensions",
        "androidx.dynamicanimation_dynamicanimation",
        "SystemUI-tags",
        "SystemUI-proto",
        "metrics-helper-lib",
+104 KiB
Loading image diff...
+68.9 KiB
Loading image diff...
+56 −0
Original line number Diff line number Diff line
# Physics Animation Layout

## Overview
**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.

An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.

## PhysicsAnimationController
PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events.

### Configuration Methods
![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png)
The controller must override the following methods:

```Set<ViewProperty> getAnimatedProperties()```
Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations.

```int getNextAnimationInChain(ViewProperty property, int index)```
If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind.

```float getOffsetForChainedPropertyAnimation(ViewProperty property)```
Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc.

```SpringForce getSpringForce(ViewProperty property)```
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``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. 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.

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

```
animateValueForChildAtIndex(TRANSLATION_X, 0, 100);
animateValueForChildAtIndex(TRANSLATION_Y, 0, 200);
```

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

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```.

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

```setController(PhysicsAnimationController controller)```
Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.

```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
Sets an end listener that is called when all animations on the given property have ended.

```setMaxRenderedChildren(int max)```
Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.

It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
 No newline at end of file
+11 −0
Original line number Diff line number Diff line
# Physics Animation Testing
Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic.

For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions.

## Waiting for Animations to End
Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations.

To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important.

The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
 No newline at end of file
Loading