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

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