26

I have a list of buttons. When I press a button, a View should slide in a downwards motion out of the button, like this:

Start:
enter image description here

Halfway:
enter image description here

End:
enter image description here

How would I go about this? The View that should slide out is bigger than the button, so first hiding the View behind the button and then sliding it downwards causes the View to be visible above the button. That should not happen.

Any ideas or examples on how to approach this?

nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • Or for that matter, is this not something an ExpandableListView unable to achieve? http://techdroid.kbeanie.com/2010/09/expandablelistview-on-android.html – DeeV Dec 19 '12 at 13:21
  • I'm developing for miminum 8. I will look into expandable listview, thanks for that. I'd like to see a custom solution for this however, because I want to use it in other environments where I only have 1 banner as well. – nhaarman Dec 19 '12 at 13:33
  • The expandable listview does not seem to offer such an animation. Though I am using minSdkVersion of 8, it's not a huge problem if the animation doesn't work on sdk version lower than, say, 11. – nhaarman Dec 19 '12 at 13:46
  • i just wants to ask that .... smooth animation is compulsory in it or not...or u want show/hide effect – Nipun Gogia Dec 19 '12 at 13:50
  • I need the smooth animation indeed, however, it is not compulsory in API < 11. – nhaarman Dec 19 '12 at 13:54
  • The list of `Buttons` actually represents a `ListView` with `Buttons`? – user Dec 19 '12 at 14:34
  • I'm not 100% sure about last image. But if you want and expand animation, you can try [this](http://stackoverflow.com/a/12711774/1050058).If that's ok, i'll add answer. – Trung Nguyen Dec 19 '12 at 14:53

7 Answers7

75

I believe the simplest approach is to extend Animation class and override applyTransformation() to change the view's height as follows:

import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

public class MyCustomAnimation extends Animation {

    public final static int COLLAPSE = 1;
    public final static int EXPAND = 0;

    private View mView;
    private int mEndHeight;
    private int mType;
    private LinearLayout.LayoutParams mLayoutParams;

    public MyCustomAnimation(View view, int duration, int type) {

        setDuration(duration);
        mView = view;
        mEndHeight = mView.getHeight();
        mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
        mType = type;
        if(mType == EXPAND) {
            mLayoutParams.height = 0;
        } else {
            mLayoutParams.height = LayoutParams.WRAP_CONTENT;
        }
        view.setVisibility(View.VISIBLE);
    }

    public int getHeight(){
        return mView.getHeight();
    }

    public void setHeight(int height){
        mEndHeight = height;
    }

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

        super.applyTransformation(interpolatedTime, t);
        if (interpolatedTime < 1.0f) {
            if(mType == EXPAND) {
                mLayoutParams.height =  (int)(mEndHeight * interpolatedTime);
            } else {
                mLayoutParams.height = (int) (mEndHeight * (1 - interpolatedTime));
            }
            mView.requestLayout();
        } else {
            if(mType == EXPAND) {
                mLayoutParams.height = LayoutParams.WRAP_CONTENT;
                mView.requestLayout();
            }else{
                mView.setVisibility(View.GONE);
            }
        }
    }
}

To use it, set your onclick() as follows:

int height;

@Override
public void onClick(View v) {
    if(view2.getVisibility() == View.VISIBLE){
        MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.COLLAPSE);
        height = a.getHeight();
        view2.startAnimation(a);
    }else{
        MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.EXPAND);
        a.setHeight(height);
        view2.startAnimation(a);
    }
}

Regards.

Patrick Boos
  • 6,789
  • 3
  • 35
  • 36
Luis
  • 11,978
  • 3
  • 27
  • 35
  • 3
    Sorry dude, but what is MyAnimation?? – Umesh Sep 30 '13 at 06:02
  • In case you wonder how it works, see this answer: http://stackoverflow.com/questions/4292930/custom-animation-in-android – Terry Nov 30 '13 at 21:30
  • @Luis what is MyAnimation?? – Rishabh Srivastava Apr 14 '14 at 10:25
  • @RishabhSrivastava, I believe you are looking for the MyAnimation.COLLAPSE. It's the constant value defined in the class begining (1 for COLLAPSE and 0 for EXPAND). Regards, Luis – Luis Apr 14 '14 at 21:07
  • @Luis of so it is MyCustomAnimation. – Rishabh Srivastava Apr 15 '14 at 04:03
  • works like a charm, small change I've added to smooth the animation is when collapse's finished `mLayoutParams.Height = mEndHeight` Thanks! – 242Eld Jun 26 '14 at 13:25
  • 3
    I try use this and it working great. But I faced some bug when first time expand it not showing animation just showing the view. After that the animation is showing. – Akirayjin Jul 15 '14 at 06:55
  • 2
    In my case `view2` appears abruptly ie. without the sliding animation but it disappears with a perfect sliding animation. How do I apply the sliding effect to `EXPAND` as well? – Flame of udun Nov 06 '15 at 23:20
  • I tried this but having issues with it, here is my SO on it http://stackoverflow.com/questions/35022050/how-to-animate-slide-in-from-right-to-left – Lion789 Jan 27 '16 at 01:58
  • @Flameofudun I had the same challenge that when the view starts froom a collapsed state wtih `View.GONE` and attempts to animate to the expand state the animation is abrupt and not smooth. Replacing `mEndHeight = mView.getHeight();` with `mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); mEndHeight = mView.getMeasuredHeight();` created a smooth animation for me. Used some guidance from: [link] (https://stackoverflow.com/a/38745247/1652110). Hope that helps! – user1652110 Dec 06 '17 at 00:48
  • @Akirayjin just remove a.setHeight(height); in onClick – FanFM Dec 25 '17 at 13:41
  • @user1652110 I have tried by your way but sill not working smoothly for Expanding animation – Mohit Dholakia Sep 24 '19 at 09:30
4

Use something like:

 Animation a = new ScaleAnimation(1, 1, 0, 1, Animation.RELATIVE_TO_SELF, (float) 0.5,    Animation.RELATIVE_TO_SELF, (float) 0);
 a.setFillAfter(true);
 view.setAnimation(a);
 a.setDuration(1000);
 view.startAnimation(a);
neteinstein
  • 17,529
  • 11
  • 93
  • 123
  • In some way this does not animate the view. The `applyTransformation` method is called however. When the animation is done the view is shown ('popped' into sight). Also, the view has a height of `match_parent`, instead of `150dp` as defined in the xml file. – nhaarman Dec 19 '12 at 14:13
  • Well, this sort of works. However, the view below the sliding view pops out of the way instead of a smooth animation. – nhaarman Dec 19 '12 at 14:40
  • To be able to have a smooth animation on the one below you may have to have another animation moving it down (on the same time, or before this one). – neteinstein Dec 19 '12 at 14:48
4

Here is simple example of hand-made animation, that provide what you want. It works in test app, but I'm not sure that there is no bugs:

public class MainActivity extends Activity implements OnClickListener {
private Timer timer;
private TimerTask animationTask;
private View view1;
private View view2;
boolean animating;
boolean increasing = true;
int initHeight = -1;
private LayoutParams params;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    timer = new Timer();

    view1 = findViewById(R.id.view1);// clickable view
    view1.setOnClickListener(this); 

    view2 = findViewById(R.id.view2);// animated view
    params = view2.getLayoutParams();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    timer.cancel();
}

@Override
public void onClick(View v) {
    Toast.makeText(this, "start animating...", Toast.LENGTH_SHORT).show();

    animationTask = new TimerTask() {
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (animationFinished()) {
                        animating = false;
                        cancel();//canceling animating task
                        return;
                    }
                    params.height += increasing ? 1 : -1;
                    view2.setLayoutParams(params);
                }
            });
        }

        private boolean animationFinished() {
            int viewHeight = view2.getHeight();
            if (increasing && viewHeight >= initHeight) {
                return true;
            }
            if (!increasing && viewHeight <= 0) {
                return true;
            }
            return false;
        }
    };

    //if we already animating - we just change direction of animation
    increasing = !increasing;
    if (!animating) {
        animating = true;
        int height = view2.getHeight();

        params.height = height;
        view2.setLayoutParams(params);//change param "height" from "wrap_conent" to real height

        if (initHeight < 0) {//height of view - we setup it only once
            initHeight = height;
        }
        timer.schedule(animationTask, 0, 10);//changing repeat time here will fasten or slow down animation
    }
}
}
Jin35
  • 8,602
  • 3
  • 32
  • 52
  • I've copy pasted this in a test project, looks good. I'll try it out tomorrow in my main project! – nhaarman Dec 20 '12 at 18:28
  • Though this works on an empty View, when I apply it to a TextView it animates, but jumps back to the original size. – nhaarman Dec 21 '12 at 09:05
2

Maybe you can set the height to 0 and gradually increase the height. But then you will have the problem that you have to be sure your text is aligned at the bottom of the view. And also to know what the maximal height of the view should be.

Tim
  • 41,901
  • 18
  • 127
  • 145
ndsmyter
  • 6,535
  • 3
  • 22
  • 37
2

Simply pass android:animateLayoutChanges to LinearLayout that holds all the views, you will achieve your desired result.

Ricardo
  • 9,136
  • 3
  • 29
  • 35
Muhammad Hassaan
  • 874
  • 6
  • 18
1

use a sliding list adapter so much easier than messing around with animations

https://github.com/tjerkw/Android-SlideExpandableListView

Aiden Fry
  • 1,672
  • 3
  • 21
  • 45
0

I would do it like that. First the layout for the whole collapsible panel component: (pseudo xml)

RelativeLayout (id=panel, clip)
    LinearLayout (id=content, alignParentBottom=true)
    LinearLayout (id=handle, above=content)

This should ensure that the content is always below the handle.

Then when you need to collapse:

  • Animate the top margin of content from 0 to -content.height
  • Animate the height of the panel from current to current-content.height
Vincent Mimoun-Prat
  • 28,208
  • 16
  • 81
  • 124