I'm trying out the new LayoutTransition
class in Honeycomb. I have set an animation that slides a View
into place when adding it to a ViewGroup
. I noticed that there is a slight delay (around 20ms) between when a view first renders and when the LayoutTransition.APPEARING
animation begins. In other words, after the view appears on screen, it hangs in the air for a moment, and then starts to animate into place. You can notice this even in the ApiDemos
sample project. In the layout animation examples, there's always a delay before a ViewGroup
's APPEARING animation starts. I've even tried setting the other LayoutTransition
animations to null, or finally giving them very short durations, but still the APPEARING
animation is delayed. Here's my code:
public class DebugExampleFour extends Activity {
private int numButtons = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug_example_four);
final ViewGroup frame = (ViewGroup) findViewById(R.id.frame_container);
LayoutTransition transitioner = new LayoutTransition();
Animator appearingAnimation = ObjectAnimator.ofFloat(null, "translationX", 600, 0);
appearingAnimation.setDuration(45);
appearingAnimation.setStartDelay(0);
appearingAnimation.setInterpolator(new DecelerateInterpolator());
appearingAnimation.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setTranslationX(0f);
}
});
transitioner.setAnimator(LayoutTransition.APPEARING, appearingAnimation);
Animator dummyAnimation = ObjectAnimator.ofInt(0, 1);
dummyAnimation.setDuration(1);
dummyAnimation.setStartDelay(0);
transitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, dummyAnimation);
transitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, dummyAnimation);
transitioner.setAnimator(LayoutTransition.DISAPPEARING, dummyAnimation);
frame.setLayoutTransition(transitioner);
Button addButton = (Button) findViewById(R.id.addNewButton);
addButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button newButton = new Button(DebugExampleFour.this);
newButton.setText("Click To Remove " + (numButtons++));
newButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
frame.removeView(v);
}
});
frame.addView(newButton, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
});
}
}
Here's the layout file that goes along with the example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Add Button"
android:id="@+id/addNewButton" />
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent"
android:id="@+id/frame_container" android:animateLayoutChanges="true" />
</LinearLayout>
How do I eliminate the delay before the APPEARING
animation?
Right - there's a delay at both levels. When you're running an Animator in the context of a LayoutTransition, you want to set the duration and startDelay on the LayoutTransition object, not on the underlying Animator (because the transition object will supply its own values for these properties to the animators that it runs).
The reason for this is that a LayoutTransition is generally going to be a sequence of animations that run on the objects inside a container. So, for example, if you want an object to 'appear' in a container, then the transition will first animate the other objects out of the way, then animate the new object into place. Imagine adding an item into the middle of a set of objects (as in the case you see in the ApiDemos apps); it first makes space, then fades the object in.
In the case of the original code above, you want the appearing animation to run immediately, so you should set the transition startDelay for APPEARING to 0 (as you did in the answer above).
Going the other way, DISAPPEARING animations by default have no startDelay, but CHANGE_DISAPPEARING animations have a startDelay equal to the duration of the DISAPPEARING animation, under the assumption that you first want to remove an item, then animate the other items in the container into their new places.
Since these are assumptions that don't necessarily apply to every situation, there are duration/startDelay properties on LayoutTransition to control the behaviors according to how you need it to work in your specific cases.
Note also that if you don't want to run one of the animation types, you should set that animation to null (see the documentation for LayoutTransition.setAnimator(int, Animator)). Setting it to your dummyAnimator will not have the same effect. For one thing, the default duration/startDelay values on the LayoutTransition will still apply, even if you supply custom Animators for these animations.
Something else to be aware of: the underlying timing mechanism (for Android, but also for most other platforms I've ever worked on) is going to have some minimum resolution. So you you set a duration of '1', that may not result in that animation ending in 1ms. Instead, it will run for one frame, then on the next frame (generally the refresh rate of the device in a well-behaved application if the system is not bogged down) it will see that the animation should end.
It turns out both Animator
and LayoutTransition
each has a start delay value. The Animator
start delay is already zero, so changing that didn't help. What is weird is that the start delay for LayoutTransition
appears to be greater than zero, at least for the case of LayoutTransition.APPEARING
. Here's the working code:
public class DebugExampleFour extends Activity {
private int numButtons = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug_example_four);
final ViewGroup frame = (ViewGroup) findViewById(R.id.frame_container);
LayoutTransition transitioner = new LayoutTransition();
Animator appearingAnimation = ObjectAnimator.ofFloat(null, "translationX", 600, 0);
appearingAnimation.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setTranslationX(0f);
}
});
transitioner.setAnimator(LayoutTransition.APPEARING, appearingAnimation);
transitioner.setDuration(LayoutTransition.APPEARING, 300);
transitioner.setStartDelay(LayoutTransition.APPEARING, 0);
frame.setLayoutTransition(transitioner);
Button addButton = (Button) findViewById(R.id.addNewButton);
addButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button newButton = new Button(DebugExampleFour.this);
newButton.setText("Click To Remove " + (numButtons++));
newButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
frame.removeView(v);
}
});
frame.addView(newButton, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With