51

I have an Activity with a list of items and when you click on an item, I want playback controls for that item to slide up from the bottom of the screen and become visible. I've defined an animation set for the slide in and the slide out and they work. I've setup my animationListener in my activity and started my slide in animation onClick of an item. My problem is, the first time I run the app, when I click on an item, the onClick callback is executed, but the animation doesn't happen. Second time I click, the slide in animation happens, but not the slide out. Third and subsequent times, it works as expected. Here are my animation sets.

vm_slide_in.xml

    <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
           android:fillAfter="true">
    <translate
        android:fromYDelta="800"
        android:toYDelta="0"
        android:duration="600" />
</set>

vm_slide_out.xml

    <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
           android:fillAfter="true">
    <translate
        android:fromYDelta="0"
        android:toYDelta="800"
        android:duration="600" />
</set>

Here is my activity layout

    <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/AppBG">
    <RelativeLayout 
        style="@style/LogoBar" 
        android:id="@+id/logo_bar"      
        android:layout_alignParentTop="true">
        <include layout="@layout/logobar"></include>
    </RelativeLayout>
    <RelativeLayout 
        style="@style/TitleBar" 
        android:id="@+id/title_bar"     
        android:layout_below="@+id/logo_bar">
        <include layout="@layout/titlebar"></include>"
        <Button style="@style/TitleBarButton"
            android:id="@+id/vm_speaker_btn"
            android:text="@string/vm_speaker_btn_label" 
            android:layout_alignParentLeft="true"
            android:layout_margin="4dp">
        </Button>
        <Button style="@style/TitleBarButton"
            android:id="@+id/vm_edit_btn"
            android:text="@string/vm_edit_btn_label" 
            android:layout_alignParentRight="true"
            android:layout_margin="4dp">
        </Button>
    </RelativeLayout>
    <ListView
        android:id="@+id/@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/title_bar"/>
    <RelativeLayout
        android:id="@+id/vm_control_panel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/footer"
        android:visibility="gone">
        <include layout="@layout/vm_control"/>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/footer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">
        <include layout="@layout/taskbar"/>
    </RelativeLayout>
</RelativeLayout>

Here is the layout for the included control

    <?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <RelativeLayout 
        android:id="@+id/vm_control"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/dark_grey">
        <TextView
            android:id="@+id/vm_ctl_branding"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="5dp"
            android:text="@string/branding"
            android:textColor="@color/white">
        </TextView>
        <TextView style="@style/TimeMark"
            android:id="@+id/vm_timestamp"
            android:layout_toLeftOf="@+id/vm_progress"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_marginRight="3dp"
            android:text="0:00">
        </TextView>
        <SeekBar
            android:id="@+id/vm_progress"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_centerHorizontal="true">
        </SeekBar>
        <TextView style="@style/TimeMark"
            android:id="@+id/vm_timeleft"
            android:layout_toRightOf="@+id/vm_progress"
            android:layout_below="@+id/vm_ctl_branding"
            android:layout_marginLeft="3dp"
            android:text="-0:00">
        </TextView>
        <LinearLayout
            android:id="@+id/vm_action_button_layout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/vm_timestamp"
            android:layout_alignRight="@+id/vm_timeleft"
            android:orientation="horizontal"
            android:layout_below="@+id/vm_progress">
            <TextView style="@style/Vm_Action_Button"
                android:background="@drawable/vm_action_btn_call"
                android:id="@+id/vm_callback_btn"
                android:layout_marginRight="5dp"
                android:text="@string/vm_action_btn_callback">
            </TextView>
            <TextView style="@style/Vm_Action_Button"
                android:background="@drawable/vm_action_btn_delete"
                android:id="@+id/vm_delete_btn"
                android:layout_marginLeft="5dp"
                android:text="@string/vm_action_btn_delete">
            </TextView>
        </LinearLayout>
    </RelativeLayout>
</merge>

From my onCreate method...

// Handle list item selections
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final Voicemail vmItem = (Voicemail) vmla.getItem(position);
        Toast.makeText(ctx, "Name: " + vmItem.getCallerName() + " Position: " + position, Toast.LENGTH_SHORT)
                .show();
        vVm_controls.startAnimation(mSlideIn);
    }
});

And my animation callbacks...

@Override
public void onAnimationStart(Animation animation) {
    vm_controls_in = !vm_controls_in;
    if (vm_controls_in) {
        vVm_controls.setVisibility(View.GONE);
    } else {
        vVm_controls.setVisibility(View.VISIBLE);
        // vVm_controls.bringToFront();
    }
}

@Override
public void onAnimationEnd(Animation animation) {
    if (!vm_controls_in) {
        try {
            Thread.sleep(1000);
            vVm_controls.startAnimation(mSlideOut);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

@Override
public void onAnimationRepeat(Animation animation) {
    // TODO Auto-generated method stub

}
Wim Coenen
  • 63,995
  • 12
  • 149
  • 237
brockoli
  • 4,376
  • 6
  • 34
  • 45

11 Answers11

134

As you discovered, the issue is visibility. When a view is initially set as gone in the xml file, Android will not render the layout until the visibility is changed to visible or invisible. If you attempt to animate on a view that has not been rendered yet, the animation will occur on the view without a layout. After the animation has completed, it will render it and suddenly the view will appear without animation. It works subsequent times because the view has a layout even when set to gone.

The key is to set the visibility to invisible instead of gone in the xml file so that it is rendered yet hidden. When the animation occurs the first time the view appear and will move as expected.

mindriot
  • 13,419
  • 4
  • 26
  • 38
  • 4
    this solution is not working in my case.. any alternative to render the view manually.? – suresh cheemalamudi Aug 09 '13 at 11:54
  • 1
    @sureshcheemalamudi It's difficult to help without more information as there could be an issue with your animation code. Also, this answer was written based on API 11 (Honeycomb). Please ask a new question with specific details of your issue and hopefully someone can provide an answer. – mindriot Aug 10 '13 at 22:30
  • 1
    The "invisible" at times is not ideal though as it might block the touch event. :( – Elye Aug 18 '15 at 12:53
  • it doesn't work for me.My view is in a ViewStub.i wanna my view slide in and out.The first time i inflate the viewStub and wanna my RecyclerView which is nested in it slide in.but it just appear without any animation.And i didn't set any visibility to it, it works subsequet times – Allen Vork Mar 22 '16 at 13:35
  • I had to set it as VISIBLE inittially. Later in OnCreate I set it as INVISIBLE. Then the animation is done properly on the first time. If I set visibility in other order, it doesn't works. – Ernesto Vega Feb 05 '19 at 09:59
  • Use `post()` to animate after render, see my answer. – A. Ferrand Jul 26 '19 at 14:10
  • If animation is related to visibility and you are changing the view's alpha, make sure to set this property in your layout - `android:alpha="0"` – Varundroid Mar 24 '20 at 04:09
12

Of course not 20 minutes after posting this question I figured out my problem. Since I was setting the visibility of my layout to "GONE", when I tried to start the animation on it, it wasn't triggering the first time. Not sure why it goes the 2nd and subsequent times, but if I set the visibility to "VISIBLE" right before I start the animation, it fixed the problem. Here is the code where I changed it...

// Handle list item selections
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final Voicemail vmItem = (Voicemail) vmla.getItem(position);
        Toast.makeText(ctx, "Name: " + vmItem.getCallerName() + " Position: " + position, Toast.LENGTH_SHORT)
                .show();
        vVm_controls.setVisibility(View.VISIBLE);
        vVm_controls.startAnimation(mSlideIn);
    }
});
brockoli
  • 4,376
  • 6
  • 34
  • 45
  • 1
    Your solution is better than "setting the visibility to invisible" – Bobs Feb 24 '13 at 07:07
  • No it's not because then you have to worry about where your view is positioned so the user doesn't see a jump. It's fine if the animation starts at the view's position, but not if the animation is brought to the view's position (or other offset animations). – Ben Kane Oct 10 '13 at 16:44
6

I do not know whether it is still up to date but yeah, it is a problem with the visibility. So I set the visibility in the xml file to invisible and called at the place where your view is initialized:

view.post(new Runnable() {
    @Override
    public void run() {
        view.animate().translationY(view.getHeight());
    }
};

Now the call

view.animate().translationY(0).setListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
        view.setVisibility(View.VISIBLE);
    }
}

works also on the first time.

Jason Saruulo
  • 1,757
  • 4
  • 18
  • 21
2

I had similar issue in Android 4.4.3. I tried most of solution proposed, but only one solution worked fine for me. You've to run your animation after window completes rendering and the best way is to run it inside onWindowFocusChanged. Don't use delay through runnable as it will affect your application performance in high speed handsets.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
   super.onWindowFocusChanged(hasFocus);
   if (hasFocus)
      yourView.startAnimation(anim);
}

more information can be found in this post: https://damianflannery.wordpress.com/2011/06/08/start-animation-in-oncreate-or-onresume-on-android/

Ayman Al-Absi
  • 2,278
  • 21
  • 21
  • This is good, though you might want to check if the window is focusing for the first time, if you want your object to animate only once. – marticztn May 27 '21 at 22:39
2

Had the exact same problem even though I was setting the view to visible in animation start. I followed brockoli's advice and set it to visible before starting the animation and it worked like a charm.

BEFORE:

Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_left);
animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        view.setVisibility(View.VISIBLE);
    }

    @Override
    public void onAnimationEnd(Animation animation) { }

    @Override
    public void onAnimationRepeat(Animation animation) {       }
);
view.startAnimation(animation);

AFTER:

Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_left);
view.setVisibility(View.VISIBLE);
view.startAnimation(animation);
Joetron
  • 71
  • 1
  • 2
2

If visibility is the issue, you only need to delay animation after render with something like :

vVm_controls.setVisibility(View.INVISIBLE);
vVm_controls.post(() -> vVm_controls.startAnimation(mSlideIn));
A. Ferrand
  • 558
  • 5
  • 17
2

Many of the current answers don't work for me, and the only one that worked was using onFocusWindowChange(), but I don't like that solution.

My way of solving this issue: First set the current view that you are trying to animate as invisible in your xml layout.

Next in onCreate add a listener to the layout to notify you when the view is finished drawing:

mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mLayout.setVisibility(View.GONE);
                mLayout.animate()
                        .translationX(-mLayout.getWidth());
                        
                mLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

It moves it offscreen after its finished rendering

DIRTY DAVE
  • 921
  • 1
  • 6
  • 30
1

If you use RecyclerView and load list items after setting the adapter ty use this

recyclerView.scheduleLayoutAnimation();

after

adapter.notifyDataSetChanged();

It's work on my case

Anwar SE
  • 354
  • 1
  • 3
  • 11
0

I had the exact same problem, but by using the ViewPropertyAnimator. For example you declare a GridView called mGridView. All you have to do now is to call mGridView.animate() with you desired changes.

The animation of my GridView was triggered but due to unknown circumstances there was no actual animation visible. I also changed all View.GONE to View.VISIBLE, but it did not change anything. In my case I figured out, that I just had to remove android:requiresFadingEdge="vertical" in my XML of this particular GridView. It might be not possible to use a Fading Edge in combination with ViewPropertyAnimator.

janwo
  • 714
  • 1
  • 8
  • 17
0

None of the answers in whole SO have worked for me, even without using Visibility.GONE at all.

So I ended up with a band-aid and I will some day look into it.

My temporary solution has been setting height to 1px (instead of 0dp, which makes it impossible to work in my case) and visibility to INVISIBLE. So I end up losing 1px but it is quite difficult to notice.

Code:

ResizeAnimation.kt

class ResizeAnimation(private val view: View, private val newHeight: Int) : Animation() {
    private val initialHeight: Int = view.layoutParams.height
    private val deltaHeight: Int

    init {
        deltaHeight = newHeight - initialHeight
    }

    override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
        view.layoutParams.height = (initialHeight + deltaHeight * interpolatedTime).toInt()
        view.requestLayout()
    }

    override fun willChangeBounds(): Boolean {
        return true
    }
}

Usage:

private fun displayProgressBar() {
        progressBar.visibility = View.VISIBLE
        val animation = ResizeAnimation(progressBar, 78).apply {
            duration = inAnimationDuration
        }
        progressBar.startAnimation(animation)
    }

private fun removeProgressBar() {
        val animation = ResizeAnimation(progressBar, 1).apply {
            duration = outAnimationDuration
        }
        progressBar.startAnimation(animation)
        progressBar.visibility = View.INVISIBLE
    }

Please do not take that as a final solution, treat that as a band-aid.

Sergi Mascaró
  • 348
  • 4
  • 13
-1

I know this question is old but my answer may help others. When we need to animate view but by default it visibility is gone then please set the all properties hide code in xml For my case I used translationY and alpha in java file. I did simple things to solve the problem in first run. Then added all the attribute in XML which is used in code.

Rick
  • 1,066
  • 1
  • 18
  • 26