Friday, August 29, 2014

Getting Started with Android L Animations

    Among many other new features, Android L has brought a slew of new animations that can be added to your apps. In this post I will go over some of those new animations and how to implement them into an app. All of the code for this post can be found on GitHub.

Ripples and Accents


    Android now supports a handful of predefined style attributes that relate to specific parts of the app, such as the status bar or navigation bar. This new system gives us an easy way to introduce two very simple, yet useful, animations: ripples and accented UI widgets. Ripples are the preferred way of giving users feedback from their actions on UI elements, and their color can be set by applying a color value to the colorControlHighlight attribute in your app theme.

<item name="android:colorControlHighlight">#0000AA</item>



    Just as easily, UI widgets, such as checkboxes, can be set using the colorAccent property in your styles folder to give them a color that fits with your app theme without having to use a set of different  images and state drawables.

<item name="android:colorAccent">#00FF00</item>


Circular Reveal


    One common task in Android is changing the visibility of an element on the screen. Using a ValueAnimator, developers now have an extra option for making this task stand out a bit more: the circular reveal. This can be used by using the ViewAnimationUtil.createCircularReveal method, and then changing the visibility of your view at the appropriate time using an animation listener. Here are the two very similar methods used for making a view visible or gone. Note that the reveal method has a duration set, causing it to take a second to finish the animation, whereas the hide animation is a lot faster. The getX() and getY() methods simply return the center points of the image along its given X and Y axes, and getRadius() simply returns the width of the view.

private void hideImageCircular() {
    int x = getX();
    int y = getY();
    int radius = getRadius();

    ValueAnimator anim =
            ViewAnimationUtils.createCircularReveal(mImageView, x, y, radius, 0);

    anim.addListener(new AnimatorListenerAdapter() {

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            mImageView.setVisibility( View.INVISIBLE );
        }
    });

    anim.start();
}

private void revealImageCircular() {
    int x = getX();
    int y = getY();
    int radius = getRadius();

    ValueAnimator anim =
            ViewAnimationUtils.createCircularReveal(mImageView, x, y, 0, radius);

    anim.setDuration( 1000 );
    anim.addListener( new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            mImageView.setVisibility( View.VISIBLE );
        }
    });

    anim.start();
}


Activity Transitions


    In addition to the previous animations, Android L added a few stock activity transition animations to help make apps look and feel more polished - explode, slide and fade. In order to use these transitions, you must request the content transitions feature in both your entering and exiting activities before setting your content view.

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

    While setting transitions through a style is supported, for this case I will go over how to set transitions in the activities. Simply use the getWindow().setExitTransition( Transition ) and getWindow().setEnterTransition( Transition ) methods to tell your activities how to act when opening and closing. In the case of the explode transition, the code for going from MainActivity and our ListFragment to the second activity will look like this:

ListFragment:
getActivity().getWindow().setExitTransition( new Explode() );
intent = new Intent( getActivity(), ExplodeAnimationActivity.class );
startActivity( intent );

ExplodeAnimationActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
    getWindow().setEnterTransition( new Explode() );
    getWindow().setExitTransition( new Explode() );
    setContentView(R.layout.activity_explode_animation);
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

    Notice the onBackPressed method - this is important because it lets the OS know that the animation should finish before it hides the calling second activity. Using these few simple lines of code, we have our three types of simple activity transitions:
Explode Transition

Slide Transition

Fade Transition

    One useful tool that I am not using, but definitely deserves mentioning, is the Transition.TransitionListener. Applying this to your enter or exit transition allows for performing tasks at different points in the animation lifecycle, giving you more control over how your app should act. This listener should also be removed in onDestroy in order to prevent a leak.

getWindow().getEnterTransition().addListener( new Transition.TransitionListener {
    @Override
    public void onTransitionStart(Transition transition) {

    }

    @Override
    public void onTransitionEnd(Transition transition) {

    }

    @Override
    public void onTransitionCancel(Transition transition) {

    }

    @Override
    public void onTransitionPause(Transition transition) {

    }

    @Override
    public void onTransitionResume(Transition transition) {

    }
});


Shared Element Activity Transitions


    On top of the standard activity transitions, it is now possible to support animating shared elements across two activities. The first thing that has to be done to support this is setting your activity to use content transitions and allow overlapping transitions. This can be done through the style applied to your activities:

<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowExitTransitionOverlap">true</item>

    While shared element transitions can be applied in Java, for this example I will use styles to apply the shared element transitions:

<item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>
<item name="android:windowSharedElementExitTransition">@transition/changebounds</item>

    where the changebounds transition is defined as an xml file in our resources folder. Notice that I added two additional properties, duration and interpolator, in order to make the animation a little more fun.

changebounds.xml:
<changeBounds
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/bounce" />

    The next step is to make sure you have a comparable view type (in this case we'll use ImageView) in both of your activity layouts, and that they both must have the same viewName attribute value. In our first activity, the view that will have the shared element will look like this:

<ImageView
    android:id="@+id/image"
    android:viewName="image"
    android:layout_width="match_parent"
    android:layout_height="250dp" />

    and the view in the second activity will look like this:

<ImageView
    android:id="@+id/image"
    android:layout_alignParentBottom="true"
    android:viewName="image"
    android:layout_width="match_parent"
    android:layout_height="250dp" />

    Now that all of the xml setup is ready to go, we can start coding in the transition from our activities. In the first activity, we set the transition group for the parent container view to false, and then take the drawable from the imageview that we want to share and compress and convert it into a byte stream that can be stored in an intent bundle. We then create ActivityOptions with a scene transition animation for our imageview and start the next activity.

Intent intent = new Intent( this, SharedElementSecondAnimationActivity.class );

((ViewGroup) mImageView.getParent()).setTransitionGroup( false );

ByteArrayOutputStream stream = new ByteArrayOutputStream();
( (BitmapDrawable) mImageView.getDrawable() ).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream);
intent.putExtra( "image", stream.toByteArray() );

ActivityOptions options;

try {
    options = ActivityOptions.makeSceneTransitionAnimation( this, mImageView, "image" );
} catch( NullPointerException e ) {
    Log.e( "SharedElementAnimationChangeBoundsActivity", "Did you set your ViewNames in the layout file?" );
    return;
}

if( options == null ) {
    Log.e("sharedelementanimation", "Options is null. Something broke. Good luck!");
} else {
    startActivity(intent, options.toBundle());
}

    In the second activity, we read the byte array from the intent bundle and decode it into a bitmap. We then apply that bitmap to an imageview. This second activity also has the onBackPressed method with finishAfterTransition in order to apply the transition when hitting the back button.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_shared_element_second_animation);
    mImageView = (ImageView) findViewById( R.id.image );

    byte[] byteArray = getIntent().getByteArrayExtra("image");
    Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
    mImageView.setImageBitmap(bitmap);
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

    And with that we now have a shared element transition that moves our shared element to its new location in the second activity, and has an extra bounce effect:


    These few examples of animations only begin to scratch the surface of what's possible in Android L, not to mention the scene transitions that were introduced in Kit Kat. I hope this tutorial helps other developers get started with the new possibilities, as applying animations makes for beautiful apps that are also fun to work on.

5 comments:

  1. Thank you very much for this post! Great examples and perfectly explained. It really helped me a lot :-)

    ReplyDelete
  2. Where are the methods getX(), getY(), getRadius() ?

    ReplyDelete
    Replies
    1. In the source code CircularRevealActivity.java.

      Delete