Taking Android animations to the next level
Hi!
google.com/+CosminStefan
cosmin@silverbit.ro
Goal for today
Animations?
Why should you bother?
Draw focus and inform of function
Make things behave naturally
Make your app look polished
Should you always animate?
Wrong moment can bring more harm
Poor performance is painful
What are our options?
Basic animation framework
- View animations (ViewPropertyAnimator)
- Property animations (ObjectAnimator)
- Drawable animations (AnimationList)
Transition animations
- Layout transitions (Layout)
- Scene/Transitions framework (Layout)
- Windows Transitions (Activity / Fragment)
View-level animations
- Interaction (Ripple, Reveal)
- View state changes (StateListAnimator)
Unconventional use-cases
- Frame-by-frame animations
- External-driven animations
Going back to the basics
Sometimes you need to build a view from scratch
Let's say you want this
Possible approaches
- Build it in the layout directly
- Or build a compound view
- Animate using the classic framework
Custom view
to the rescue
The custom view way
- Embeddable view
- Black-box implementation
- Class extending View
- Draw animation frame-by-frame
- Compute current state based on needs
- Override onDraw()
Isn't that very hard to do?
Let's see how it's done
@Override
protected void onDraw(Canvas canvas) {
}
Let's see how it's done
@Override
protected void onDraw(Canvas canvas) {
// Draw background.
canvas.drawBitmap(mBackground, 0, 0, null);
}
Let's see how it's done
@Override
protected void onDraw(Canvas canvas) {
// Draw background.
canvas.drawBitmap(mBackground, 0, 0, null);
// Make sure we get re-drawn as soon as possible
invalidate();
}
Let's see how it's done
@Override
protected void onDraw(Canvas canvas) {
// Draw background.
canvas.drawBitmap(mBackground, 0, 0, null);
// Draw each of the two lines
long elapsedTime = System.currentTimeMillis() - mStartTimestamp;
drawLine(canvas, computeNewAngle(elapsedTime, 1000), LINE_HEIGHT_LONG);
drawLine(canvas, computeNewAngle(elapsedTime, 10000), LINE_HEIGHT_SHORT);
// Make sure we get re-drawn as soon as possible
invalidate();
}
Let's see how it's done
@Override
protected void onDraw(Canvas canvas) {
// Draw background.
canvas.drawBitmap(mBackground, 0, 0, null);
// Draw each of the two lines
long elapsedTime = System.currentTimeMillis() - mStartTimestamp;
drawLine(canvas, computeNewAngle(elapsedTime, 1000), LINE_HEIGHT_LONG);
drawLine(canvas, computeNewAngle(elapsedTime, 10000), LINE_HEIGHT_SHORT);
// Make sure we get re-drawn as soon as possible
invalidate();
}
private void drawLine(Canvas canvas, int angle, int lineHeight) {
canvas.save();
canvas.rotate(angle, mCenterX, mCenterY);
canvas.drawRect(mCenterX - LINE_WIDTH / 2,
mCenterY - LINE_OFFSET - lineHeight,
mCenterX + LINE_WIDTH / 2,
mCenterY - LINE_OFFSET,
mLinePaint);
canvas.restore();
}
What to keep in mind
- Encapsulated views that can be reused
- Extremely versatile
- More complicated and time-consuming
- Good performance
- Works best when you don't change size
- Driven by frames, time or events
Driving your animations from the outside
What if you want a more complex scenario?
- Changing animations mid-way
- Adapting to live changes
- Take natural laws into account
Introducing Rebound
Spring dynamics for Android"
What does it do?
- Open source library, by Facebook
- Spring models
- Natural feel through real world physics
- Drive animations
Configuration
- how strong the spring is (tension)
- what slows it down (friction)
T:40
F: 2
T:40
F:25
T:95
F: 2
Let's see why that's helpful
Starting with our goal
Laying out the groundwork
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/background"/>
</FrameLayout>
public class MainActivity extends AppCompatActivity {
private ImageView mItemView;
private int mItemWidth;
private int mItemHeight;
private int mWindowWidth;
private int mWindowHeight;
...
}
Setting up the Springs
private void initializeRebound() {
// Setup spring system and the springs
SpringSystem springSystem = SpringSystem.create();
SpringConfig config = new SpringConfig(60, 8);
mSpringX = springSystem.createSpring()
.setSpringConfig(config);
mSpringY = springSystem.createSpring()
.setSpringConfig(config);
}
@Override
protected void onStart() {
// Setup layout and initialize fields
...
initializeRebound();
}
Setting up the Springs
private void initializeRebound() {
// Setup spring system and the springs
SpringSystem springSystem = SpringSystem.create();
SpringConfig config = new SpringConfig(60, 8);
mSpringX = springSystem.createSpring()
.setSpringConfig(config);
mSpringY = springSystem.createSpring()
.setSpringConfig(config);
// Initial setup
mSpringX.setCurrentValue(mWindowWidth / 2);
mSpringY.setCurrentValue(mWindowHeight / 2);
// ... <To be continued>
}
@Override
protected void onStart() {
// Setup layout and initialize fields
...
initializeRebound();
}
Drawing the animation
private void updateView() {
mItemView.setX((float) mSpringX.getCurrentValue() - mItemWidth / 2);
mItemView.setY((float) mSpringY.getCurrentValue() - mItemHeight / 2);
}
private void initializeRebound() {
// Setup spring system and the springs and perform initial setup
...
// Trigger the initial view update
updateView()
// Listen for value changes received from the spring system
mSpringX.addListener(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
updateView();
}
});
}
Hooking everything up
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
|| event.getAction() == MotionEvent.ACTION_MOVE) {
mSpringX.setEndValue(event.getX());
mSpringY.setEndValue(event.getY());
return true;
}
return super.onTouchEvent(event);
}
What if we want more?
Adding resizing
Sizing it up
private void initializeRebound() {
...
mSpringX.addListener(new SimpleSpringListener() {
@Override
public void onSpringEndStateChange(Spring spring) {
mSpringSize.setEndValue(0.7);
}
});
mSpringSize = springSystem.createSpring().addListener(new SimpleSpringListener() {
public void onSpringUpdate(Spring spring) {
updateViewSize();
}
});
mSpringSize.setCurrentValue(1);
updateViewSize();
}
private void updateViewSize() {
mItemView.setScaleX((float) mSpringSize.getCurrentValue());
mItemView.setScaleY((float) mSpringSize.getCurrentValue());
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
mSpringSize.setEndValue(1);
} else ...
}
A few more examples...
Inertia Ball
Origami Example
Spring Chain
Animating your layout changes
Layout transitions
The way things used to be
(since API Level 11)
How layout transitions work
- Automatic transitions on layout changes
- Supports "appearing" and "disappearing" changes
- Platform defaults via animateLayoutChanges="true"
- Custom behaviour by extending LayoutTransition
Introducing Transitions API
What does it bring?
- Coordinated animations on groups of views
- Based on start and end states of the UI
- Set of built-in animations
- Supports definition via resource files
- Fine-grained control via life-cycle callbacks
Let's start with the concepts
-
Scene - the state of a view hierarchy
- a snapshot of how the UI looks like
- includes views and property values
-
Transition - the set of animations that get applied
- changes needed to transition between
- start scene and end scene
- current state and new state of the scene
- built-in or customisable behaviours
- changes needed to transition between
Getting started with scenes
mSceneRoot = (ViewGroup) findViewById(R.id.root);
mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, this);
mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, this);
mSceneRoot = (ViewGroup) mSceneRootView;
// View hierarchy that will be added as a child of the scene root when entering the scene
mViewHierarchy = (ViewGroup) mSceneView;
mScene = new Scene(mSceneRoot, mViewHierarchy);
Generation from layout resource files
Defining them via code and existing views
Now let's get start transitioning
Creating transitions...
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
// Create and define a simple transitions
Transition fadeTransition = new Fade();
// Or combine multiple ones in a set
TransitionSet transitionSet = new TransitionSet()
.addTransition(new Scale(0.3f))
.addTransition(new Fade())
.setInterpolator(new LinearOutSlowInInterpolator());
From a resource file
Via code definition
Transition transition = TransitionInflater.from(mContext)
.inflateTransition(R.transition.fade_transition);
Running transitions
// Update the hierarchy inside the scene root with the one from the ending scene,
// running the animations defined by the transition
TransitionManager.go(endScene, transition);
// Start recording changes to the view hierarchy
TransitionManager.beginDelayedTransition(rootView, transition);
// Add the new View to the hierarchy
rootView.addView(mNewTextView);
Applying the transition with a target scene
Applying the transition without a scene
Let's checkout some examples
Fade In transition
@Override
public void onClick (View v){
TransitionManager.beginDelayedTransition(mViewContainer);
mIsVisible = !mIsVisible;
mTextView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
}
Change text transition
@Override
public void onClick (View v){
TransitionManager.beginDelayedTransition(mViewContainer,
new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN));
textView.setText(isSecondText ?
"Thanks! Another tap?" :
"Hi, i am a text. Tap on me!");
}
Path motion transition
@Override
public void onClick(View v) {
Transition transition = new ChangeBounds()
.setPathMotion(new ArcMotion())
.setDuration(500);
TransitionManager.beginDelayedTransition(viewContainer,
transition);
mToRightAnimation = !mToRightAnimation;
LayoutParams params = (LayoutParams) button.getLayoutParams();
params.gravity = mToRightAnimation ?
(Gravity.RIGHT | Gravity.BOTTOM) :
(Gravity.LEFT | Gravity.TOP);
button.setLayoutParams(params);
}
Exploding tile
public void onItemClick(View view) {
final Rect viewRect = new Rect();
view.getGlobalVisibleRect(viewRect);
Transition transition1 = new Explode()
.setEpicenterCallback(new EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return viewRect;
}
});
TransitionSet set = new TransitionSet()
.addTransition(transition1.excludeTarget(view, true))
.addTransition(new Fade().addTarget(view));
TransitionManager.beginDelayedTransition(mRecyclerView, set);
mRecyclerView.setAdapter(null);
}
Can I use this?
Yes, with some caveats
- Transition Framework added in API 19
- Fade, ChangeBounds, Visibility, AutoTransition
- Transitions added in API 21
- Slide, Explode etc.
- Added in support library 24.2.0 in Aug. 2016
- Fade, ChangeBounds, Visibility, AutoTransition
-
Transitions Everywhere to the rescue
- Backports for API 14
Animating view state changes
Dealing with state changes
- Views change states via interaction
- pressed, selected, activated, enabled
- Animating the changes brings smoothness
How can this be done?
State List Animator
- API Level 21
- Animations that run on state changes
How does it look like?
<selector xmlns:android="...">
<item android:state_pressed="true">
<set>
<objectAnimator
android:duration="300"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0.4"
android:valueType="floatType"/>
<objectAnimator
android:duration="300"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="1.2"
android:valueType="floatType"/>
</set>
</item>
...
</selector>
<Button ...
android:stateListAnimator="@anim/demo_selector"/>
What if you want this behavior
on API <21?
It's pretty easy as well
public class CustomButton extends Button {
...
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
int targetRotation = isPressed() ? 180 : 0;
if (targetRotation != getRotation())
this.animate().rotation(targetRotation);
}
}
Wrapping it up
What we've covered
- Frame-by-frame animations
- Externally-driven animations
- Transitions framework
- State change animations
What to keep in mind
There's no silver bullet
Always consider alternative ways
Be creative and
have fun!
Thank you!
Questions?
Taking Android animations to the next level
By Cosmin Stefan
Taking Android animations to the next level
An exploration of the most important ways to deal with animations in Android, from using classic tools of the trade to more complex solutions for special use-cases
- 648