How do I animate View.setVisibility(GONE)

AndroidAnimation

Android Problem Overview


I want to make an Animation for when a View gets it's visibility set to GONE. Instead of just dissapearing, the View should 'collapse'. I tried this with a ScaleAnimation but then the View is collapse, but the layout will only resize it's space after (or before) the Animation stops (or starts).

How can I make the Animation so that, while animating, the lower Views will stay directly below the content, instead of having a blank space?

Android Solutions


Solution 1 - Android

Put the view in a layout if it's not and set android:animateLayoutChanges="true" for that layout.

Solution 2 - Android

There doesn't seem to be an easy way to do this through the API, because the animation just changes the rendering matrix of the view, not the actual size. But we can set a negative margin to fool LinearLayout into thinking that the view is getting smaller.

So I'd recommend creating your own Animation class, based on ScaleAnimation, and overriding the "applyTransformation" method to set new margins and update the layout. Like this...

public class Q2634073 extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q2634073);
        findViewById(R.id.item1).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        view.startAnimation(new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, view, true));
    }

    public class MyScaler extends ScaleAnimation {

        private View mView;

        private LayoutParams mLayoutParams;

        private int mMarginBottomFromY, mMarginBottomToY;

        private boolean mVanishAfter = false;

        public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
                boolean vanishAfter) {
            super(fromX, toX, fromY, toY);
            setDuration(duration);
            mView = view;
            mVanishAfter = vanishAfter;
            mLayoutParams = (LayoutParams) view.getLayoutParams();
            int height = mView.getHeight();
            mMarginBottomFromY = (int) (height * fromY) + mLayoutParams.bottomMargin - height;
            mMarginBottomToY = (int) (0 - ((height * toY) + mLayoutParams.bottomMargin)) - height;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f) {
                int newMarginBottom = mMarginBottomFromY
                        + (int) ((mMarginBottomToY - mMarginBottomFromY) * interpolatedTime);
                mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
                    mLayoutParams.rightMargin, newMarginBottom);
                mView.getParent().requestLayout();
            } else if (mVanishAfter) {
                mView.setVisibility(View.GONE);
            }
        }

    }

}

The usual caveat applies: because we are overriding a protected method (applyTransformation), this is not guaranteed to work in future versions of Android.

Solution 3 - Android

I used the same technique as Andy here has presented. I wrote my own Animation class for that, that animate the margin's value, causing the effect of the item to disappear/appear. It looks like this:

public class ExpandAnimation extends Animation {

// Initializations...

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if (interpolatedTime < 1.0f) {

        // Calculating the new bottom margin, and setting it
        mViewLayoutParams.bottomMargin = mMarginStart
                + (int) ((mMarginEnd - mMarginStart) * interpolatedTime);

        // Invalidating the layout, making us seeing the changes we made
        mAnimatedView.requestLayout();
    }
}
}

I have a full example that works on my blog post http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Solution 4 - Android

I used the same technique as Andy here, and refined it so that it can be used for expanding and collapsing without glitches, also using a technique described here: https://stackoverflow.com/a/11426510/1317564

import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

class LinearLayoutVerticalScaleAnimation extends ScaleAnimation {
	private final LinearLayout view;
	private final LinearLayout.LayoutParams layoutParams;

	private final float beginY;
	private final float endY;
	private final int originalBottomMargin;

	private int expandedHeight;
	private boolean marginsInitialized = false;
	private int marginBottomBegin;
	private int marginBottomEnd;
	
	private ViewTreeObserver.OnPreDrawListener preDrawListener;

	LinearLayoutVerticalScaleAnimation(float beginY, float endY,
			LinearLayout linearLayout) {
		super(1f, 1f, beginY, endY);

		this.view = linearLayout;
		this.layoutParams = (LinearLayout.LayoutParams) linearLayout.getLayoutParams();

		this.beginY = beginY;
		this.endY = endY;
		this.originalBottomMargin = layoutParams.bottomMargin;

		if (view.getHeight() != 0) {
			expandedHeight = view.getHeight();
			initializeMargins();
		}
	}

	private void initializeMargins() {
		final int beginHeight = (int) (expandedHeight * beginY);
		final int endHeight = (int) (expandedHeight * endY);

		marginBottomBegin = beginHeight + originalBottomMargin - expandedHeight;
		marginBottomEnd = endHeight + originalBottomMargin - expandedHeight;
		marginsInitialized = true;
	}

	@Override
	protected void applyTransformation(float interpolatedTime, Transformation t) {
		super.applyTransformation(interpolatedTime, t);		

		if (!marginsInitialized && preDrawListener == null) {						
			// To avoid glitches, don't draw until we've initialized everything.
			preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
				@Override
				public boolean onPreDraw() {					
					if (view.getHeight() != 0) {
						expandedHeight = view.getHeight();
						initializeMargins();
						adjustViewBounds(0f);
						view.getViewTreeObserver().removeOnPreDrawListener(this);								
					}

					return false;
				}
			};
			
			view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);					
		}

		if (interpolatedTime < 1.0f && view.getVisibility() != View.VISIBLE) {			
			view.setVisibility(View.VISIBLE);			
		}

		if (marginsInitialized) {			
			if (interpolatedTime < 1.0f) {
				adjustViewBounds(interpolatedTime);
			} else if (endY <= 0f && view.getVisibility() != View.GONE) {				
				view.setVisibility(View.GONE);
			}
		}
	}
	
	private void adjustViewBounds(float interpolatedTime) {
		layoutParams.bottomMargin = 
				marginBottomBegin + (int) ((marginBottomEnd - marginBottomBegin) * interpolatedTime);		

		view.getParent().requestLayout();
	}
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMrSnowflakeView Question on Stackoverflow
Solution 1 - AndroidVinay WView Answer on Stackoverflow
Solution 2 - AndroidAndyView Answer on Stackoverflow
Solution 3 - AndroidUdinicView Answer on Stackoverflow
Solution 4 - AndroidLearn OpenGL ESView Answer on Stackoverflow