Tuesday, September 3, 2013

Smoothing performance on Fragment transitions

Suppose you're doing a pretty standard Fragment replacement with a custom animation:

getSupportFragmentManager()
    .beginTransaction()
    .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
    .replace(android.R.id.content, new MyFragment())
    .commit();

You may notice that the performance can be a bit rough, not as smooth as you'd like. A common way to improve Android animation performance is to use hardware layers.  Normally you'd add it to the animation directly but with fragments you don't get access to it unless you take advantage of Fragment.onCreateAnimation()*.  Here's how it looks:

public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    Animation animation = super.onCreateAnimation(transit, enter, nextAnim);

    // HW layer support only exists on API 11+
    if (Build.VERSION.SDK_INT >= 11) {
        if (animation == null && nextAnim != 0) {
            animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        }

        if (animation != null) {
            getView().setLayerType(View.LAYER_TYPE_HARDWARE, null);

            animation.setAnimationListener(new AnimationListener() {
                public void onAnimationEnd(Animation animation) {
                    getView().setLayerType(View.LAYER_TYPE_NONE, null);
                }

                // ...other AnimationListener methods go here...
            });
        }
    }

    return animation;
}

Now the animation should be a lot more smooth!  In my own code, I've overridden this method in a base Fragment from which all others extend so that I always get this feature (though if you're more particular you could only apply it to certain Fragments).

* If you're not using the support library, then you'll be overriding Fragment.onCreateAnimator() and using animator-based classes.

8 comments:

  1. Nice one! I'm currently working with fragments and will test this out. Thanks!

    ReplyDelete
  2. I would say you are supposed to store the current layer type and restore it rather than always setting View.LAYER_TYPE_NONE at the end ;)

    Nice tip

    ReplyDelete
    Replies
    1. Hmm, I'm not sure that's necessarily the right action. You won't be able to restore the Paint from before without storing it elsewhere anyways, so it would need special care there regardless. Perhaps it's best used on a case-by-case basis where the context is better known.

      Delete
  3. But how to apply this Animation in Fragment? I mean where should i put this code?

    ReplyDelete
    Replies
    1. As per above example onCreateAnimation() method in MyFragment. This method called when a fragment loads an animation.

      Delete
  4. please, show us an example of implementing and using this method.

    ReplyDelete
  5. Thank you Daniel.

    I suggest this piece of code to improve yours
    if (animation != null) {
    if (getView() != null && getView().isHardwareAccelerated()) {
    getView().setLayerType(View.LAYER_TYPE_HARDWARE, null);
    }
    }

    Best regards, I don't if it is my phone or what, I implemented this, but didn't see much difference in my app, thanks anyways .

    ReplyDelete