rharter
rharter

Reputation: 2495

LayoutTransition.DISAPPEARING animations don't run when removing child views

I have a FrameLayout container that I'm trying to have animate it's changes. I call container.setLayoutTransition(layoutTransition) on the view, but when I later call container.removeView(v) the DISAPPEARING animation isn't executed, the view is simply immediately removed.

Here's my code for the transition:

LayoutTransition layoutTransition = new LayoutTransition();
layoutTransition.setAnimator(LayoutTransition.APPEARING,
    ObjectAnimator.ofFloat(null, View.TRANSLATION_Y, toolHeight, 0f));
layoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
layoutTransition.setDuration(LayoutTransition.APPEARING, 1000);
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING,
    ObjectAnimator.ofFloat(null, View.TRANSLATION_Y, 0f, toolHeight));
layoutTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
layoutTransition.setDuration(LayoutTransition.DISAPPEARING, 1000);
container.setLayoutTransition(layoutTransition);

And to swap views I do this:

if (container.getChildCount() > 0) {
  for (int i = container.getChildCount() - 1; i >= 0; i--) {
    container.removeViewAt(i);
  }
}
container.addView(newView);

I've slowed down the animations to confirm the situation, and I can clearly see that the APPEARING transition is executed appropriately, but the DISAPPEARING transition isn't, the view is simply removed immediately.

I've found that if I remove the view from the LayoutTransition.removeChild() method it works, but that adds quite a bit of complexity that doesn't seem necessary. Here's what that becomes:

if (container.getChildCount() > 0) {
  final View v = container.getChildAt(0);
  container.getLayoutTransition().removeChild(container, v);
  container.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() {
    @Override
    public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
    }

    @Override
    public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
      transition.removeTransitionListener(this);
      if (transitionType == LayoutTransition.DISAPPEARING) {
        container.removeView(view);
      }
    }
  }
}
container.addView(newView);

Think that's bad? That's only for removing a single view at a time, if there is more than one view in the container then I've got to add a transition listener that counts executions to know when to remove itself, and that's just a mess.

The removing itself is interesting, because if I simply put that transition listener on the LayoutTransition and it doesn't remove itself, then the disappearing transition doesn't run again.

Clearly I'm missing something pertaining to making the remove transition execute. Any help would be appreciated.

EDIT

It's been pointed out to me that adding views cancels the DISAPPEARING transitions:

public class ViewGroup {
  private void addViewInner(...) {
    if (mTransition != null) {
      // Don't prevent other add transitions from completing, but cancel remove
      // transitions to let them complete the process before we add to the container
      mTransition.cancel(LayoutTransition.DISAPPEARING);
    }
  }
}

This doesn't make a lot of sense to me. Wonder what the reasoning is here.

Upvotes: 3

Views: 1862

Answers (1)

devunwired
devunwired

Reputation: 63293

As you've noticed, LayoutTransition doesn't really sequence animation types the way some of the newer APIs do. It's designed to handle a single item change at a time. The cleanest code will come from sequencing the add yourself with a delay, rather than chaining listener calls:

if (container.getChildCount() > 0) {
  for (int i = container.getChildCount() - 1; i >= 0; i--) {
    container.removeViewAt(i);
  }

  //Start the add once removals are complete
  long duration =
    container.getLayoutTransition().getDuration(LayoutTransition.DISAPPEARING);
  container.postOnAnimationDelayed(new Runnable(){
    @Override
    public void run() {
      container.addView(newView);
    }
  }, duration);
} else {
  container.addView(newView);
}

You might get better choreography out of Scenes for something like this, but that is API 19+

Upvotes: 4

Related Questions