commit | 178fbc357f7e0a73829d5bb0ee40e3758ea52dff | [log] [tgz] |
---|---|---|
author | Jeff Verkoeyen <featherless@google.com> | Fri Nov 10 16:43:55 2017 |
committer | Jeff Verkoeyen <featherless@google.com> | Fri Nov 10 16:43:55 2017 |
tree | 5da95ebd85ddb03bd9ed06af5e17373ac6c6346a | |
parent | 92996ade76b63337de3588349be1b19c534e252b [diff] | |
parent | f1c48bdcc126704ef65c48fc74f08d800644ae6e [diff] |
Merge branch 'release-candidate' into stable
A Motion Animator creates performant, interruptible animations from motion specs.
This library provides APIs that turn Motion Interchange motion specifications into animations.
A motion specification defines the delay, duration, and acceleration of animations in a simple data format that can live separate from your animation logic.
For example, let's say we wanted to describe the motion for this animation:
We might create a specification like so:
struct CalendarChipTiming { MDMMotionTiming chipWidth; MDMMotionTiming chipHeight; MDMMotionTiming chipY; MDMMotionTiming chipContentOpacity; MDMMotionTiming headerContentOpacity; MDMMotionTiming navigationBarY; }; typedef struct CalendarChipTiming CalendarChipTiming; struct CalendarChipMotionSpec { CalendarChipTiming expansion; CalendarChipTiming collapse; }; typedef struct CalendarChipMotionSpec CalendarChipMotionSpec; FOUNDATION_EXTERN struct CalendarChipMotionSpec CalendarChipSpec;
With our implementation of the spec looking like so:
struct CalendarChipMotionSpec CalendarChipSpec = { .expansion = { .chipWidth = { .delay = 0.000, .duration = 0.285, .curve = MDMEightyForty, }, .chipHeight = { .delay = 0.015, .duration = 0.360, .curve = MDMEightyForty, }, ... }, .collapse = { .chipWidth = { .delay = 0.045, .duration = 0.330, .curve = MDMEightyForty, }, .chipHeight = { .delay = 0.000, .duration = 0.330, .curve = MDMEightyForty, }, ... }, };
Our spec defines two different transition states: expansion and collapse. At runtime, we determine which of these two specs we intend to use and then use the timings to animate our views with an instance of MDMMotionAnimator
:
CalendarChipTiming timing = _expanded ? CalendarChipSpec.expansion : CalendarChipSpec.collapse; MDMMotionAnimator *animator = [[MDMMotionAnimator alloc] init]; animator.shouldReverseValues = !_expanded; [animator animateWithTiming:timing.chipHeight toLayer:chipView.layer withValues:@[ @(chipFrame.size.height), @(headerFrame.size.height) ] keyPath:MDMKeyPathHeight]; ...
A working implementation of this example can be seen in the included example app.
Check out a local copy of the repo to access the Catalog application by running the following commands:
git clone https://github.com/material-motion/motion-animator-objc.git cd motion-animator-objc pod install open MotionAnimator.xcworkspace
CocoaPods is a dependency manager for Objective-C and Swift libraries. CocoaPods automates the process of using third-party libraries in your projects. See the Getting Started guide for more information. You can install it with the following command:
gem install cocoapods
Add motion-animator
to your Podfile
:
pod 'MotionAnimator'
Then run the following command:
pod install
Import the framework:
@import MotionAnimator;
You will now have access to all of the APIs.
A motion spec is a complete representation of the motion curves that meant to be applied during an animation. Your motion spec might consist of a single MDMMotionTiming
instance, or it might be a nested structure of MDMMotionTiming
instances, each representing motion for a different part of a larger animation. In either case, your magic motion constants now have a place to live.
Consider a simple example of animating a view on and off-screen. Without a spec, our code might look like so:
CGPoint before = dismissing ? onscreen : offscreen; CGPoint after = dismissing ? offscreen : onscreen; view.center = before; [UIView animateWithDuration:0.5 animations:^{ view.center = after; }];
What if we want to change this animation to use a spring curve instead of a cubic bezier? To do so we'll need to change our code to use a new API:
CGPoint before = dismissing ? onscreen : offscreen; CGPoint after = dismissing ? offscreen : onscreen; view.center = before; [UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:0 options:0 animations:^{ view.center = after; } completion:nil];
Now let's say we wrote the same code with a motion spec and animator:
static const MDMMotionTiming kMotionSpec = { .duration = 0.5, .curve = _MDMSpring(1, 100, 1), }; MDMMotionAnimator *animator = [[MDMMotionAnimator alloc] init]; animator.shouldReverseValues = dismissing; view.center = offscreen; [_animator animateWithTiming:kMotionSpec animations:^{ view.center = onscreen; }]
Now if we want to change our motion back to an easing curve, we only have to change the spec:
static const MDMMotionTiming kMotionSpec = { .duration = 0.5, .curve = _MDMBezier(0.4f, 0.0f, 0.2f, 1.0f), };
The animator code stays the same. It's now possible to modify the motion parameters at runtime without affecting any of the animation logic.
This pattern is useful for building transitions and animations. To learn more through examples, see the following implementations:
Material Components Activity Indicator
Material Components Progress View
Material Components Masked Transition
MDMMotionAnimator
provides an explicit API for adding animations to animatable CALayer key paths. This API is similar to creating a CABasicAnimation
and adding it to the layer.
[animator animateWithTiming:timing.chipHeight toLayer:chipView.layer withValues:@[ @(chipFrame.size.height), @(headerFrame.size.height) ] keyPath:MDMKeyPathHeight];
MDMMotionAnimator
provides an API that is similar to UIView's animateWithDuration:
. Use this API when you want to apply the same timing to a block of animations:
chipView.frame = chipFrame; [animator animateWithTiming:timing.chipHeight animations:^{ chipView.frame = headerFrame; }]; // chipView.layer's position and bounds will now be animated with timing.chipHeight's timing.
Start by creating an MDMMotionAnimator
instance.
MDMMotionAnimator *animator = [[MDMMotionAnimator alloc] init];
When we describe our transition we‘ll describe it as though we’re moving forward and take advantage of the shouldReverseValues
property on our animator to handle the reverse direction.
animator.shouldReverseValues = isTransitionReversed;
To animate a property on a view, we invoke the animate
method. We must provide a timing, values, and a key path:
[animator animateWithTiming:timing toLayer:view.layer withValues:@[ @(collapsedHeight), @(expandedHeight) ] keyPath:MDMKeyPathHeight];
MDMMotionAnimator
is configured by default to generate interruptible animations using Core Animation‘s additive animation APIs. You can simply re-execute the animate
calls when your transition’s direction changes and the animator will add new animations for the updated direction.
We welcome contributions!
Check out our upcoming milestones.
Learn more about our team, our community, and our contributor essentials.
Licensed under the Apache 2.0 license. See LICENSE for details.