5

I have a custom view, which will perform animation during

  1. Activity first time launched.
  2. Selection changes on action bar drop down navigation.

The code looks like

DividendBarChartFragment .java

public class DividendBarChartFragment extends SherlockFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.dividend_bar_chart_fragment, container, false);

        // barChartCompositeViewByYear is custom view.
        this.barChartCompositeViewByYear = (BarChartCompositeView)v.findViewById(R.id.bar_chart_composite_view_by_year);

        final ViewTreeObserver viewTreeObserver0 = this.barChartCompositeViewByYear.getViewTreeObserver();

        // Only perform animation when view is ready?!
        viewTreeObserver0.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @SuppressLint("NewApi")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                } else {
                    DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }

                DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
            }

        });  

I wish to start animation (animateCurrentBarHeight), only when the Fragment is ready.

I use addOnGlobalLayoutListener. However, as you can see in my video, seems like animation has happened even before Fragment is visible to user.

https://www.youtube.com/watch?v=87_DOuZw88w&feature=youtu.be

If I only perform animation during onNavigationItemSelected of Activity (Using the same animation code, which is animateCurrentBarHeight), thing went much more smoother.

https://www.youtube.com/watch?v=yvJqtOSKKok&feature=youtu.be

May I know, what is the best time I can trigger my animation code, when activity first launched, so that the animation appears natural and smooth to users?

Code for animateCurrentBarHeight

public void animateCurrentBarHeight() {
    PropertyValuesHolder barHeightScalePropertyValuesHolder = PropertyValuesHolder.ofFloat("barHeightScale", barHeightScale, 1.0f);        
    ValueAnimator valueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, barHeightScalePropertyValuesHolder);
    valueAnimator.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
    valueAnimator.setRepeatCount(0);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.start();        
}

Finalized answer after reading all suggested answers

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.dividend_bar_chart_fragment, container, false);

    // barChartCompositeViewByYear is custom view.
    this.barChartCompositeViewByYear = (BarChartCompositeView)v.findViewById(R.id.bar_chart_composite_view_by_year);

    final ViewTreeObserver viewTreeObserver0 = this.barChartCompositeViewByYear.getViewTreeObserver();

    viewTreeObserver0.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @SuppressLint("NewApi")
        @Override
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            } else {
                DividendBarChartFragment.this.barChartCompositeViewByYear.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }

            final int activityTransitionDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
            final BarChartCompositeView barChartCompositeView = DividendBarChartFragment.this.barChartCompositeViewByYear;

            // Key to the solution!
            barChartCompositeView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    barChartCompositeView.animateCurrentBarHeight();
                }
            }, activityTransitionDuration);
        }

    });        
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • What happens when you use an `onPreDraw()` listener instead of a `onGlobalLayout` listener? – Alex Lockwood Dec 06 '14 at 16:52
  • what about observing event = windowfocus changed and dispatching your animation then? see accepted ans here : http://stackoverflow.com/questions/6965516/android-is-there-a-callback-that-gets-called-right-after-onresume – Robert Rowntree Dec 06 '14 at 22:13
  • @AlexLockwood Using `onPreDraw` doesn't seem much different. The animation still isn't smooth. – Cheok Yan Cheng Dec 08 '14 at 15:04
  • Yeah, I didn't think so... but it was worth a shot. Usually `onPreDraw()` is preferred over `onGlobalLayout` listener when it comes to animation since it is called immediately before the first display frame is drawn. :) – Alex Lockwood Dec 08 '14 at 15:38

3 Answers3

5

I don't believe there is a way to set an end listener on activity transitions like this, so one possible solution would be to simply set a start delay on the animation. This will ensure that the animation does not begin while the second activity is still animating/fading in to the screen.

The default activity transition duration can be obtained by calling:

getResources().getInteger(android.R.integer.config_mediumAnimTime);
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • Thanks. How do you know config_mediumAnimTime is the default delay for Activity transition? I google through, but can't find it. – Cheok Yan Cheng Dec 08 '14 at 15:05
  • There are a bunch of animations defined in the source code [here](https://android.googlesource.com/platform/frameworks/base/+/6c8e20f96ae26533eb8081f4eab845ec710f9c9b/core/res/res/anim). For example, take a look at `task_open_enter.xml`. – Alex Lockwood Dec 08 '14 at 15:35
2

Generally animations can be controlled with messages on the UI thread. Without knowing the exact implementation of that view, I would give a try in posting a runnable to be ran when all current messagese are already delivered,

Instead of ...animateCurrentBarHeight();, Do:

    new Handler().post(new Runnable(){
        @Override
        public void run() {
            DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
        }
    });
Sean
  • 5,176
  • 2
  • 34
  • 50
  • 2
    It doesn't help, unless `postDelayed` is used. – Cheok Yan Cheng Dec 08 '14 at 15:06
  • Thank you, just for the effort and completeness. But mind you as Cheok said above, post() alone will *not* have any effect, you must use postDelayed with at least 50 ms. (tested on Nexus 5 and Galaxy S3) – Odaym May 19 '15 at 06:46
0

It is hard to tell exactly what is causing the stutter (to my naked eye, 1 of the animations in the second video seemed to have almost as much stutter), so this is a theoretical answer that at the very worst should make things better.

To start with, I personally don't see the value in using the ViewTreeObserver approach when it is simply starting an activity. It would make much more sense in the appropriate lifecycle event given that you are performing that lifecycle by starting the Activity. To understand exactly what is going on in your current configuration, I would suggest looking at the Activity and Fragment lifecycles (in particular the lifecycle flowchart diagrams).

Based on what you are trying to do, you want the animation to happen as late in the lifecycle of displaying the activity as possible. By trying to perform the animation in the onGlobalLayout() event, you are trying to perform the animation when layout actions are being completed. In Android, layout operations and drawing (let alone displaying on screen), are very different actions. For example, the basic View lifecycle (which you are essentially observing with your onGlobalLayout() listener) goes onMeasure(), onLayout(), onDraw(). Based on that, you are attempting to perform your animation, before the View would even have been drawn to screen (this conclusion is based on a few simplifying assumptions, but the general gist remains).

It would make much more sense to be performing this animation for the activity creation event in the onResume() method of either your Activity or Fragment (depends on your app setup - but looks like the Fragment.onResume() method would be appropriate). The side effect of this is that the animation will happen whenever the user reopens your application. If this isn't desirable, you could always set a flag in your onCreate() method that ensures the animation is only run once. Or you could also look at the onStart() as this could be a better place depending on your application structure. Sample code for the most basic solution:

public class DividendBarChartFragment extends SherlockFragment {
    // ... existing code ...

    @Override
    public void onResume() {
        super.onResume();
        DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight();
    }
}

This is as late as you have access to in terms of the Activity/ Fragment lifecycle (you could go to Activity.onPostResume() but that shouldn't be necessary and is not advised in the method documentation). If this does not resolve the issues, then your next option is to still start your animation here, but include a delay in starting it. This is much less robust, but should be guaranteed to do the trick. Given you haven't provided an implementation of your animateCurrentBarHeight() function, I can only suggest that you extend it to take a delay int parameter and adjust accordingly (Animation.startOffset() is one of the many ways you could achieve this depending on how you're performing your animation). Your code with a delay in onResume() would still pretty much be the same:

public class DividendBarChartFragment extends SherlockFragment {
    // ... existing code ...

    @Override
    public void onResume() {
        super.onResume();
        int delayMS = 1000;
        DividendBarChartFragment.this.barChartCompositeViewByYear.animateCurrentBarHeight(delayMS);
    }
}

If this does not solver the stutter, it would be extremely helpful if you could provide code for exactly how you are doing the animation.

btalb
  • 6,937
  • 11
  • 36
  • 44
  • Using `onResume` doesn't help. Take note that, `onGlobalLayout event came after `onResume`. So, I thought a better place to trigger animation, is in `onGlobalLayout`. – Cheok Yan Cheng Dec 08 '14 at 15:07