Android animate drop down/up view proper
Asked Answered
L

2

18

I'm trying to do a proper slide-down animation. The view that slides down should push all views below it down in one smooth movement and again when it slides up all the views should follow in one smooth movement.

What I've tried:

In code:

LinearLayout lin = (LinearLayout)findViewById(R.id.user_list_container);
                setLayoutAnimSlidedownfromtop(lin, this);
                lin.addView(getLayoutInflater().inflate(R.layout.user_panel,null),0);

And:

public static void setLayoutAnimSlidedownfromtop(ViewGroup panel, Context ctx) {

      AnimationSet set = new AnimationSet(true);

      Animation animation = new AlphaAnimation(0.0f, 1.0f);
      animation.setDuration(100);
      set.addAnimation(animation);

      animation = new TranslateAnimation(
          Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
          Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f
      );
      animation.setDuration(500);
      set.addAnimation(animation);

      LayoutAnimationController controller =
          new LayoutAnimationController(set, 0.25f);
      panel.setLayoutAnimation(controller);

}

My user_panel.xml:

<?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="40dp"
    android:orientation="vertical" >
    <ImageView 
        android:layout_alignParentLeft="true"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:src="@drawable/icon" />
</LinearLayout>

Top of the main XML:

<LinearLayout
        android:id="@+id/user_list_container"
        android:layout_alignParentTop="true"
        android:orientation="vertical" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    <LinearLayout
        android:id="@+id/container"
        android:layout_below="@+id/user_list_container"
        android:orientation="vertical" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

The problem with above approach is that when I start the animation first the empty space for the view is created and then the view slides down. I'd like it to slowly push all the other views down instead of doing it in one hard motion.

Lindgren answered 12/2, 2012 at 12:53 Comment(1)
I think you need to look at LayoutTransition class. It provides mechanisms for animating adding/removing views to/from layout.Paisley
L
58

So I ended up doing it myself with some help from this answer. If it had been Android 3.0, I could have used property animation, but it's not so I had to do that myself.

Here is what I ended up with:

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

/**
 * Class for handling collapse and expand animations.
 * @author Esben Gaarsmand
 *
 */
public class ExpandCollapseAnimation extends Animation {
    private View mAnimatedView;
    private int mEndHeight;
    private int mType;

    /**
     * Initializes expand collapse animation, has two types, collapse (1) and expand (0).
     * @param view The view to animate
     * @param duration
     * @param type The type of animation: 0 will expand from gone and 0 size to visible and layout size defined in XML. 
     * 1 will collapse view and set to gone
     */
    public ExpandCollapseAnimation(View view, int duration, int type) {
        setDuration(duration);
        mAnimatedView = view;
        mEndHeight = mAnimatedView.getLayoutParams().height;
        mType = type;
        if(mType == 0) {
            mAnimatedView.getLayoutParams().height = 0;
            mAnimatedView.setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        if (interpolatedTime < 1.0f) {
            if(mType == 0) {
                mAnimatedView.getLayoutParams().height = (int) (mEndHeight * interpolatedTime);
            } else {
                mAnimatedView.getLayoutParams().height = mEndHeight - (int) (mEndHeight * interpolatedTime);
            }
            mAnimatedView.requestLayout();
        } else {
            if(mType == 0) {
                mAnimatedView.getLayoutParams().height = mEndHeight;
                mAnimatedView.requestLayout();
            } else {
                mAnimatedView.getLayoutParams().height = 0;
                mAnimatedView.setVisibility(View.GONE);
                mAnimatedView.requestLayout();
                mAnimatedView.getLayoutParams().height = mEndHeight;
            }
        }
    }
}

Example ussage:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class AnimationTestActivity extends Activity {
    private boolean mActive = false;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final Button animatedButton = (Button) findViewById(R.id.animatedButton);
        
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                ExpandCollapseAnimation animation = null;
                if(mActive) {
                    animation = new ExpandCollapseAnimation(animatedButton, 1000, 1);
                    mActive = false;
                } else {
                    animation = new ExpandCollapseAnimation(animatedButton, 1000, 0);
                    mActive = true;
                }
                animatedButton.startAnimation(animation);
            }
        });
    }
}

XML:

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/animatedButton"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:text="@string/hello"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <Button
        android:id="@+id/button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello"/>
</LinearLayout>

Edit

Measure wrap_content height:

So in order to get this to work for wrap_content, I measured the height of the view before I start the animation and then use this measured height as the actual height. Below is the code for measuring the height of the view and set this as the new height (I assume the view uses screen width, change according to your own needs):

/**
 * This method can be used to calculate the height and set it for views with wrap_content as height. 
 * This should be done before ExpandCollapseAnimation is created.
 * @param activity
 * @param view
 */
public static void setHeightForWrapContent(Activity activity, View view) {
    DisplayMetrics metrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);

    int screenWidth = metrics.widthPixels;
    
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY);

    view.measure(widthMeasureSpec, heightMeasureSpec);
    int height = view.getMeasuredHeight();
    view.getLayoutParams().height = height;
}
Lindgren answered 15/2, 2012 at 9:23 Comment(9)
Ok for forcer-height elements. But unfortunately this method doesn't work for wrap-content elements :(Urbain
@bixi actually it does, you just have to calculate the height for wrap content. It is doable (did it but can't find the reference quick =/, tell me if you need it)Lindgren
Great example! You can also perhaps reuse View.VISIBLE and View.GONE to indicate the type (animation outcome)Minor
@Lindgren I need this to work with wrap content. Could you tell me how to do it or add it to your post?Mayflower
@gsingh2011 I've added code for measuring wrap_content for the view.Lindgren
Replace mEndHeight = mAnimatedView.getLayoutParams().height; with mEndHeight = type == 0 ? mAnimatedView.getLayoutParams().height : mAnimatedView.getMeasuredHeight(); and it'll work with wrap_content without the need for the extra method setHeightForWrapContent(Activity activity, View view)Durarte
Oh, also need if(mEndHeight < 0) mEndHeight = mAnimatedView.getMeasuredHeight(); (without it if the view starts minimized mEndHeight will be -2)Durarte
Hey Warpzit,this is useful but how can I set direction for collapse/expand? Right now collapse is from down to up and expand is from up to down...What I need how can I set it t-->b, b-->t, r-->l, l-->r fro both expand and collapse?Flare
@Flare you'd have to make a new question about that.Lindgren
B
5

Thank you Warpzit! That was a very helpful answer. In my case, I was only trying to animate views with height that was wrap_content. I tried triggs two-line suggestion, but it didn't work in my case. (I didn't spend much time pursuing why.) I ended up using a slightly modified form of Warpzit's ExpandCollapseAnimation with his static method to determine the height of the view

In slightly more detail:

  1. I included his static method setHeightForWrapContent() in the ExpandCollapseAnimation class.
  2. I call the setHeightForWrapContent() in the ExpandCollapseAnimation constructor to properly determine the height of the view. To do this, I have to pass the activity in with the constructor.
  3. In the applyTransformation() method, when the view is finally reduced to zero height, I return the view's height back to wrap_content. If you don't do this, and change the content of the view later, when you expand it the view will expand to the height previously determined.

The code is here:

public class ExpandCollapseAnimation extends Animation {
    private View mAnimatedView;
    private int mEndHeight;
    private int mType;

    public ExpandCollapseAnimation(View view, int duration, int type, Activity activity) {
        setDuration(duration);
        mAnimatedView = view;

        setHeightForWrapContent(activity, view);

        mEndHeight = mAnimatedView.getLayoutParams().height;

        mType = type;
        if(mType == 0) {
            mAnimatedView.getLayoutParams().height = 0;
            mAnimatedView.setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        if (interpolatedTime < 1.0f) {
            if(mType == 0) {
                mAnimatedView.getLayoutParams().height = (int) (mEndHeight * interpolatedTime);
            } else {
                mAnimatedView.getLayoutParams().height = mEndHeight - (int) (mEndHeight * interpolatedTime);
            }
            mAnimatedView.requestLayout();
        } else {
            if(mType == 0) {
                mAnimatedView.getLayoutParams().height = mEndHeight;
                mAnimatedView.requestLayout();
            } else {
                mAnimatedView.getLayoutParams().height = 0;
                mAnimatedView.setVisibility(View.GONE);
                mAnimatedView.requestLayout();
                mAnimatedView.getLayoutParams().height = LayoutParams.WRAP_CONTENT;     // Return to wrap
            }
        }
    }

    public static void setHeightForWrapContent(Activity activity, View view) {
        DisplayMetrics metrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);

        int screenWidth = metrics.widthPixels;

        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY);

        view.measure(widthMeasureSpec, heightMeasureSpec);
        int height = view.getMeasuredHeight();
        view.getLayoutParams().height = height;
    }
}

Thank you again, Warpzit!

Burgoo answered 5/9, 2013 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.