32

Is it possible to have a nice slide up/down effect when expanding/collapsing an item of a ExpandableListView?

If yes, how?

Thanks in advance.

thomaus
  • 6,170
  • 8
  • 45
  • 63

6 Answers6

29

So This is a complete duplicate of this. In short, I used a regular list, made my own dropdown view, used a custom drop down animation and voila success (look at link for more description).

Edit: Step by step guide:

First I create the xml list_row:

<?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:id="@+id/row_parent"
    android:orientation="vertical">
    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/row_simple_parent"
        >
    <View
        android:id="@+id/row_simple_parent_invis_cover"
        android:visibility="gone"
        android:layout_height="something that fills out your content"
        android:layout_width="match_parent"
        android:background="@android:color/transparent"/>
    </RelativeLayout>

    <!-- Dropdown -->
    <RelativeLayout 
        android:id="@+id/row_dropdown"
        android:layout_height="wrap_content"
        android:layout_width="match_parent">
    </RelativeLayout>
</LinearLayout>

The animation for dropdown is following:

import android.app.Activity;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.View.MeasureSpec;
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 mStartVisibility;

    /**
     * Initializes expand collapse animation. If the passed view is invisible/gone the animation will be a drop down, 
     * if it is visible the animation will be collapse from bottom
     * @param view The view to animate
     * @param duration
     */ 
    public ExpandCollapseAnimation(View view, int duration) {
        setDuration(duration);
        mAnimatedView = view;
        mEndHeight = mAnimatedView.getLayoutParams().height;
        mStartVisibility = mAnimatedView.getVisibility();
        if(mStartVisibility == View.GONE || mStartVisibility == View.INVISIBLE) {
            mAnimatedView.setVisibility(View.VISIBLE);
            mAnimatedView.getLayoutParams().height = 0;
        }
    }

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

    /**
     * This methode can be used to calculate the height and set itm 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;
    }
}

Then inside my adapter (you'll ofcourse add more syntax, and if you want the dropdown to not close when out of sight in the list, you need to remember this in the holder with some kind of parameter as well):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder holder;
    if(convertView == null) {
        // setup holder
        holder = new ViewHolder();
        convertView = mInflater.inflate(R.layout.list_row, null);
        holder.mDropDown = convertView.findViewById(R.id.row_dropdown);
        convertView.setTag(holder);
    } else {
        // get existing row view
        holder = (ViewHolder) convertView.getTag();
    }
    holder.mDropDown.setVisibility(View.GONE);
    return convertView;
}

Then the magic happens in your lists onItemClick:

@Override
public void onListItemClick(ListView list, View view, int position, long id) {
    final ListItem item = (ListItem) list.getAdapter().getItem(position);
    // set dropdown data
    ViewHolder holder = (ViewHolder) view.getTag();
    final View dropDown = holder.mDropDown;

    // set click close on top part of view, this is so you can click the view
    // and it can close or whatever, if you start to add buttons etc. you'll loose
    // the ability to click the view until you set the dropdown view to gone again.
    final View simpleView = view.findViewById(R.id.row_simple_parent_invis_cover);
    simpleView.setVisibility(View.VISIBLE);

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 0) {
                // first we measure height, we need to do this because we used wrap_content
                // if we use a fixed height we could just pass that in px.
                ExpandCollapseAnimation.setHeightForWrapContent(getActivity(), dropDown);
                ExpandCollapseAnimation expandAni = new ExpandCollapseAnimation(dropDown, DROP_DOWN_TIME);
                dropDown.startAnimation(expandAni);

                Message newMsg = new Message();

            } else if(msg.what == 1) {
                ExpandCollapseAnimation expandAni = new ExpandCollapseAnimation(dropDown, DROP_DOWN_TIME);
                dropDown.startAnimation(expandAni);

                simpleView.setOnClickListener(null);
                simpleView.setVisibility(View.GONE);
            }
        }
    };

    simpleView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            handler.sendEmptyMessage(1);
        }
    });

    // start drop down animation
    handler.sendEmptyMessage(0);
}

Final comment: I'm not sure this is the best way of doing it but this is what worked for me.

Edit: There is a different solution by DevBytes on youtube that can be viewed here.

Community
  • 1
  • 1
Warpzit
  • 27,966
  • 19
  • 103
  • 155
  • 1
    @Kermia I'll do so later. Not sure to post it here/in other post or in both =/. Since you request it I'll post it here I think. – Warpzit Apr 20 '12 at 11:21
  • Hi, I'm using your example to do my own animation, but I don't know what does the `postDropHandler` do, and to know how it's implemented if it's possible. Thanks – Alvin Baena May 13 '12 at 17:25
  • I'm pretty sure its legacy from my own implementation, will do a cleanup of the code later. I used it to tell when the animation had finished so I could do something at the end of the animation. – Warpzit May 14 '12 at 04:58
  • I'm a little confused here (basically because I tried to implement the code in your answer in my own app for testing): the `onListItemClick` and `getView` methods are for a ListView, but we're using ExpandableListView here. So why these ListView methods? Also, what is your `ViewHolder` class? – gosr Jun 22 '12 at 21:48
  • Hi Warpzit, i used this approach in a library which solves this problem once and forever: Android-SlideExpandableListView library: https://github.com/tjerkw/Android-SlideExpandableListView More about it can be read in this blog post: http://tjerktech.wordpress.com/2012/06/23/an-emerging-android-ui-pattern-for-contextual-actions/ – TjerkW Jun 23 '12 at 12:50
  • 1
    This does not help if we want to animate a group expansion in ExpandableListView. This solution is nice, but addresses a different question. – hadi Aug 03 '13 at 01:43
  • @RishabhSrivastava what part? It worked for so many others so you gotta be a little more specific – Warpzit Sep 04 '13 at 06:13
  • its showing a blank ListView...I have a TextView as a list item and I want to display three buttons with sliding animation if the item is clicked where should the xml code of buttons should be written?and where to setText for ListItem?? – Rishabh Srivastava Sep 04 '13 at 06:42
  • @RishabhSrivastava all Views that should be displayed after dropdown click should be placed in the RelativeLayout under the comment: . About setting the text, that happens in the getView() method but that is standard Android stuff. – Warpzit Sep 04 '13 at 10:10
  • i tried it but nothing happend, finally i found this incredible example https://github.com/Udinic/SmallExamples/tree/master/ExpandAnimationExample which solved my problem...short and simple... BTW thnx a lot @Warpzit – Rishabh Srivastava Sep 04 '13 at 10:25
  • @warpzit:your list shows multiple slided listview item, is it possible to close the last slided view and open the new sliderview. inside onitemclick().? – user755 Sep 27 '13 at 09:46
  • any Idea how "dropdown to not close when out of sight" – user755 Sep 27 '13 at 10:26
  • @user2439755 I've already explained that: Then inside my adapter (you'll ofcourse add more syntax, and if you want the dropdown to not close when out of sight in the list, you need to remember this in the holder with some kind of parameter as well): – Warpzit Sep 27 '13 at 18:27
5

The anwser pointed out by Warpzit is correct. I used this approach to provide a library which you can easily embed in your application without having to know how it actually works:

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

More about it can be read in this blog post: http://tjerktech.wordpress.com/2012/06/23/an-emerging-android-ui-pattern-for-contextual-actions/

TjerkW
  • 2,086
  • 21
  • 26
3

The implementation by Warpzit definitely does work, however it is unusable if you need to support groups with a lot of children (let's say 100) as you will not be making use of the ListView's optimized structure (ie. the reuse/recycling of child views). Instead, I ended up extending ExpandableListView to create an AnimatedExpandableListView that uses the technique I described here. By doing so, the AnimatedExpandableListView can animate group expansion while still offering top performance. Have a look.

Community
  • 1
  • 1
idunnololz
  • 8,058
  • 5
  • 30
  • 46
1

The expand/collapse does not work with the code from here: https://github.com/tjerkw/Android-SlideExpandableListView because OnItemExpandCollapseListener expandCollapseListener from AbstractSlideExpandableListAdapter is null; the method notifiyExpandCollapseListener is called when the animation starts, but the listener is null because: you have the ActionSlideExpandableListView:

ActionSlideExpandableListView lv = (ActionSlideExpandableListView) findViewById(R.id.list_view);
SlideExpandableListAdapter slideAdapter = new SlideExpandableListAdapter(adapter,R.id.expandable_toggle_button, R.id.expandable);

and you set the adapter: lv.setAdapter(slideAdapter); which calls the method setAdapter from SlideExpandableListView and there a new instance of SlideExpandableListAdapter is created.

I changed like this: setAdapter method from ActionSlideExpandableListView takes as a parameter also AbstractSlideExpandableListAdapter.OnItemExpandCollapseListener which is passed to the setAdapter method from SlideExpandableListView. There when I create SlideExpandableListAdapter I pass also this listener:

 public void setAdapter(ListAdapter adapter, AbstractSlideExpandableListAdapter.OnItemExpandCollapseListener expandCollapseListener) {
        this.adapter = new SlideExpandableListAdapter(adapter, expandCollapseListener);
        super.setAdapter(this.adapter);
    }

    public SlideExpandableListAdapter(ListAdapter wrapped, OnItemExpandCollapseListener expandCollapseListener) {
        this(wrapped, R.id.expandable_toggle_button, R.id.expandable);
        setItemExpandCollapseListener(expandCollapseListener);
    }
Paul
  • 3,812
  • 10
  • 50
  • 73
0

A simple solution is to use the class AnimatedExpandableListView created by Gary Guo, available here, if you're already using the BaseExpandableListAdapter, which extends ExpandableListAdapter. This way there is no need to use a modification of a ListView.

You only need to subclass AnimatedExpandableListAdapter instead of BaseExpandableListAdapter and AnimatedExpandableListView in the place of ExpandableListView.

Instead of @Override getChildrenCount just use @Override getRealChildrenCount. Do the same for @Override getChildView, using @Override getRealChildView in its place.

Then you use the animation like so:

    expandableListView.setOnGroupClickListener(new AnimatedExpandableListView.OnGroupClickListener() {
        @Override
        public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
            if (expandableListView.isGroupExpanded(groupPosition))
                expandableListView.collapseGroupWithAnimation(groupPosition);
            else
                expandableListView.expandGroupWithAnimation(groupPosition);
            return true;
        }

    });

Another details is that in your layout xml file you need to reference AnimatedExpandableListView and not ExpandableListView:

<com.your.package.project.class.location.AnimatedExpandableListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

The sample code in the project and the comments on AnimatedExpandableListView class are very useful if need further help.

neowinston
  • 7,584
  • 10
  • 52
  • 83
-3

Try this in java class

       expListView.setAdapter(listAdapter);
    expListView.setOnGroupExpandListener(new OnGroupExpandListener() {
        int previousGroup = -1;

        @Override
        public void onGroupExpand(int groupPosition) {
            if(groupPosition != previousGroup)
                expListView.collapseGroup(previousGroup);
            previousGroup = groupPosition;
        }
    });