graphic.expand_graphic.ExpandingListView.java Source code

Java tutorial

Introduction

Here is the source code for graphic.expand_graphic.ExpandingListView.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package graphic.expand_graphic;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AbsListView.LayoutParams;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.example.onedayspb.R;

/**
 * A custom listview which supports the preview of extra content corresponding to each cell
 * by clicking on the cell to hide and show the extra content.
 */
public class ExpandingListView extends ListView {

    private static boolean mShouldRemoveObserver = false;
    //for less getter
    private static List<View> mViewsToDraw = new ArrayList<View>();

    private static int[] mTranslate = new int[] { 0, 0 };

    //height of collapsed list element
    private final static int mCollapsedHeight = 68;

    private static HashMap<View, int[]> oldCoordinates;
    private static LayoutParams collapseLayoutParams;
    private static LayoutParams generalLayoutParams;
    private ViewTreeObserver.OnPreDrawListener expandPreDrawListener;
    private ViewTreeObserver.OnPreDrawListener collapsePreDrawListener;
    private static ViewHolder holder;
    private ViewTreeObserver observer; //variables for listeners;
    private static View mView;
    private static ExpandableListItem viewObject;
    private static int oldBottom;
    private static int oldTop;

    private static AnimatorSet s = new AnimatorSet();
    private static ArrayList<Animator> animations = new ArrayList<Animator>();

    public ExpandingListView(Context context) {
        super(context);
        init();
    }

    public ExpandingListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ExpandingListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        setOnItemClickListener(mItemClickListener);

        oldCoordinates = new HashMap<View, int[]>();
        collapseLayoutParams = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                mCollapsedHeight);
        generalLayoutParams = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                AbsListView.LayoutParams.WRAP_CONTENT);

        expandPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {
                /* Determine if this is the first or second pass.*/
                if (!mShouldRemoveObserver) {
                    mShouldRemoveObserver = true;

                    /* Calculate what the parameters should be for setSelectionFromTop.
                     * The ListView must be offset in a way, such that after the animation
                     * takes place, all the cells that remain visible are rendered completely
                     * by the ListView.*/
                    int newTop = mView.getTop();
                    int newBottom = mView.getBottom();

                    int newHeight = newBottom - newTop;
                    int oldHeight = oldBottom - oldTop;
                    int delta = newHeight - oldHeight;

                    getTopAndBottomTranslations(oldTop, oldBottom, delta, true);

                    int currentTop = mView.getTop();
                    int futureTop = oldTop - mTranslate[0];

                    int firstChildStartTop = getChildAt(0).getTop();
                    int firstVisiblePosition = getFirstVisiblePosition();
                    int deltaTop = currentTop - futureTop;

                    int i;
                    int childCount = getChildCount();
                    for (i = 0; i < childCount; i++) {
                        View v = getChildAt(i);
                        int height = v.getBottom() - Math.max(0, v.getTop());
                        if (deltaTop - height > 0) {
                            firstVisiblePosition++;
                            deltaTop -= height;
                        } else {
                            break;
                        }
                    }

                    if (i > 0) {
                        firstChildStartTop = 0;
                    }

                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);

                    /* Request another layout to update the layout parameters of the cells.*/
                    requestLayout();

                    /* Return false such that the ListView does not redraw its contents on
                     * this layout but only updates all the parameters associated with its
                     * children.*/
                    return false;
                }

                /* Remove the predraw listener so this method does not keep getting called. */
                mShouldRemoveObserver = false;
                observer.removeOnPreDrawListener(this);

                int yTranslateTop = mTranslate[0];
                int yTranslateBottom = mTranslate[1];

                //   ArrayList <Animator> animations = new ArrayList<Animator>(); optimize

                int index = indexOfChild(mView);

                /* Loop through all the views that were on the screen before the cell was
                 *  expanded. Some cells will still be children of the ListView while
                 *  others will not. The cells that remain children of the ListView
                 *  simply have their bounds animated appropriately. The cells that are no
                 *  longer children of the ListView also have their bounds animated, but
                 *  must also be added to a list of views which will be drawn in dispatchDraw.*/
                for (View v : oldCoordinates.keySet()) {
                    int[] old = oldCoordinates.get(v);
                    v.setTop(old[0]);
                    v.setBottom(old[1]);
                    if (v.getParent() == null) {
                        mViewsToDraw.add(v);
                        int delta = old[0] < oldTop ? -yTranslateTop : yTranslateBottom;
                        animations.add(getAnimation(v, delta, delta));
                    } else {
                        int i = indexOfChild(v);
                        if (v != mView) {
                            int delta = i > index ? yTranslateBottom : -yTranslateTop;
                            animations.add(getAnimation(v, delta, delta));
                        }
                        ViewCompat.setHasTransientState(v, false);
                    }
                }

                /* Adds animation for expanding the cell that was clicked. */
                animations.add(getAnimation(mView, -yTranslateTop, yTranslateBottom));

                /* Adds an animation for fading in the extra content. */
                animations.add(ObjectAnimator.ofFloat(holder.expandingView, //mView.findViewById(R.id.expanding_layout), optimize
                        View.ALPHA, 0, 1));

                /* Disabled the ListView for the duration of the animation.*/
                setEnabled(false);
                setClickable(false);

                /* Play all the animations created above together at the same time. */
                //   AnimatorSet s = new AnimatorSet(); optimize 
                s.playTogether(animations);
                s.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        //   viewObject.setExpanded(true); optimize
                        viewObject.mIsExpanded = true;
                        setEnabled(true);
                        setClickable(true);
                        s.removeAllListeners();
                        oldCoordinates.clear();
                        animations.clear();
                        if (mViewsToDraw.size() > 0) {
                            for (View v : mViewsToDraw) {
                                ViewCompat.setHasTransientState(v, false);
                            }
                        }
                        mViewsToDraw.clear();
                    }
                });
                s.start();
                return true;
            }
        };

        collapsePreDrawListener = new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {

                if (!mShouldRemoveObserver) {
                    /*Same as for expandingView, the parameters for setSelectionFromTop must
                     * be determined such that the necessary cells of the ListView are rendered
                     * and added to it.*/
                    mShouldRemoveObserver = true;

                    int newTop = mView.getTop();
                    int newBottom = mView.getBottom();

                    int newHeight = newBottom - newTop;
                    int oldHeight = oldBottom - oldTop;
                    int deltaHeight = oldHeight - newHeight;

                    getTopAndBottomTranslations(oldTop, oldBottom, deltaHeight, false);

                    int currentTop = mView.getTop();
                    int futureTop = oldTop + mTranslate[0];

                    int firstChildStartTop = getChildAt(0).getTop();
                    int firstVisiblePosition = getFirstVisiblePosition();
                    int deltaTop = currentTop - futureTop;

                    int i;
                    int childCount = getChildCount();
                    for (i = 0; i < childCount; i++) {
                        View v = getChildAt(i);
                        int height = v.getBottom() - Math.max(0, v.getTop());
                        if (deltaTop - height > 0) {
                            firstVisiblePosition++;
                            deltaTop -= height;
                        } else {
                            break;
                        }
                    }

                    if (i > 0) {
                        firstChildStartTop = 0;
                    }

                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);

                    requestLayout();

                    return false;
                }

                mShouldRemoveObserver = false;
                observer.removeOnPreDrawListener(this);

                int yTranslateTop = mTranslate[0];
                int yTranslateBottom = mTranslate[1];

                int index = indexOfChild(mView);
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View v = getChildAt(i);
                    int[] old = oldCoordinates.get(v);
                    if (old != null) {
                        /* If the cell was present in the ListView before the collapse and
                         * after the collapse then the bounds are reset to their old values.*/
                        v.setTop(old[0]);
                        v.setBottom(old[1]);
                        ViewCompat.setHasTransientState(v, false);
                    } else {
                        /* If the cell is present in the ListView after the collapse but
                         * not before the collapse then the bounds are calculated using
                         * the bottom and top translation of the collapsing cell.*/
                        int delta = i > index ? yTranslateBottom : -yTranslateTop;
                        v.setTop(v.getTop() + delta);
                        v.setBottom(v.getBottom() + delta);
                    }
                }

                final View expandingLayout = holder.expandingView;// mView.findViewById (R.id.expanding_layout); optimize

                /* Animates all the cells present on the screen after the collapse. */
                //   ArrayList <Animator> animations = new ArrayList<Animator>(); optimize
                for (int i = 0; i < childCount; i++) {
                    View v = getChildAt(i);
                    if (v != mView) {
                        float diff = i > index ? -yTranslateBottom : yTranslateTop;
                        animations.add(getAnimation(v, diff, diff));
                    }
                }

                /* Adds animation for collapsing the cell that was clicked. */
                animations.add(getAnimation(mView, yTranslateTop, -yTranslateBottom));

                /* Adds an animation for fading out the extra content. */
                animations.add(ObjectAnimator.ofFloat(expandingLayout, View.ALPHA, 1, 0));

                /* Disabled the ListView for the duration of the animation.*/
                setEnabled(false);
                setClickable(false);

                /* Play all the animations created above together at the same time. */
                //   AnimatorSet s = new AnimatorSet();
                s.playTogether(animations);
                s.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        expandingLayout.setVisibility(View.GONE);
                        mView.setLayoutParams(generalLayoutParams);
                        viewObject.mIsExpanded = false; //     viewObject.setExpanded(false);
                        s.removeAllListeners();
                        animations.clear();
                        oldCoordinates.clear();
                        setEnabled(true);
                        setClickable(true);
                        /* Note that alpha must be set back to 1 in case this view is reused
                         * by a cell that was expanded, but not yet collapsed, so its state
                         * should persist in an expanded state with the extra content visible.*/
                        expandingLayout.setAlpha(1);
                    }
                });
                s.start();

                return true;
            }
        };
    }

    /**
     * Listens for item clicks and expands or collapses the selected view depending on
     * its current state.
     */
    private AdapterView.OnItemClickListener mItemClickListener = new AdapterView.OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            //   viewObject = (ExpandableListItem) getAdapter().getItem(position); // getItemAtPosition(position); //getPositionForView(view) optimize
            mView = view;
            holder = (ViewHolder) view.getTag();
            viewObject = holder.viewObject;
            if (!viewObject.mIsExpanded) {
                expandView(mView);
            } else {
                collapseView(mView);
            }
        }
    };

    /**
     * Calculates the top and bottom bound changes of the selected item. These
     * values are also used to move the bounds of the items around the one that
     * is actually being expanded or collapsed.
     * <p/>
     * This method can be modified to achieve different user experiences
     * depending on how you want the cells to expand or collapse. In this
     * specific demo, the cells always try to expand downwards (leaving top
     * bound untouched), and similarly, collapse upwards (leaving top bound
     * untouched). If the change in bounds results in the complete disappearance
     * of a cell, its lower bound is moved is moved to the top of the screen so
     * as not to hide any additional content that the user has not interacted
     * with yet. Furthermore, if the collapsed cell is partially off screen when
     * it is first clicked, it is translated such that its full contents are
     * visible. Lastly, this behaviour varies slightly near the bottom of the
     * listview in order to account for the fact that the bottom bounds of the
     * actual listview cannot be modified.
     */
    private int[] getTopAndBottomTranslations(int top, int bottom, int yDelta, boolean isExpanding) {
        int yTranslateTop = 0;
        int yTranslateBottom = yDelta;

        int height = bottom - top;

        if (isExpanding) {
            boolean isOverTop = top < 0;
            boolean isBelowBottom = (top + height + yDelta) > getHeight();
            if (isOverTop) {
                yTranslateTop = top;
                yTranslateBottom = yDelta - yTranslateTop;
            } else if (isBelowBottom) {
                int deltaBelow = top + height + yDelta - getHeight();
                yTranslateTop = top - deltaBelow < 0 ? top : deltaBelow;
                yTranslateBottom = yDelta - yTranslateTop;
            }
        } else {
            int offset = computeVerticalScrollOffset();
            int range = computeVerticalScrollRange();
            int extent = computeVerticalScrollExtent();
            int leftoverExtent = range - offset - extent;
            // Hack to fix negative leftover caused by items not filling view
            if (leftoverExtent < 0) {
                leftoverExtent *= -1;
            }

            boolean isCollapsingBelowBottom = (yTranslateBottom > leftoverExtent);
            boolean isCellCompletelyDisappearing = bottom - yTranslateBottom < 0;

            // Hack to force the view to shrink correctly
            isCollapsingBelowBottom = (yTranslateTop + yTranslateBottom > range) ? false : isCollapsingBelowBottom;

            if (isCollapsingBelowBottom) {
                yTranslateTop = yTranslateBottom - leftoverExtent;
                // Hack to force the view not shift the top bound when it is
                // visible
                yTranslateTop = yTranslateTop < range ? 0 : yTranslateTop;
                yTranslateBottom = yDelta - yTranslateTop;
            } else if (isCellCompletelyDisappearing) {
                yTranslateBottom = bottom;
                yTranslateTop = yDelta - yTranslateBottom;
            }
        }

        return new int[] { yTranslateTop, yTranslateBottom };
    }

    /**
     * This method expands the view that was clicked and animates all the views
     * around it to make room for the expanding view. There are several steps
     * required to do this which are outlined below.
     * <p/>
     * 1. Store the current top and bottom bounds of each visible item in the
     * listview. 2. Update the layout parameters of the selected view. In the
     * context of this method, the view should be originally collapsed and set
     * to some custom height. The layout parameters are updated so as to wrap
     * the content of the additional text that is to be displayed.
     * <p/>
     * After invoking a layout to take place, the listview will order all the
     * items such that there is space for each view. This layout will be
     * independent of what the bounds of the items were prior to the layout so
     * two pre-draw passes will be made. This is necessary because after the
     * layout takes place, some views that were visible before the layout may
     * now be off bounds but a reference to these views is required so the
     * animation completes as intended.
     * <p/>
     * 3. The first predraw pass will set the bounds of all the visible items to
     * their original location before the layout took place and then force
     * another layout. Since the bounds of the cells cannot be set directly, the
     * method setSelectionFromTop can be used to achieve a very similar effect.
     * 4. The expanding view's bounds are animated to what the final values
     * should be from the original bounds. 5. The bounds above the expanding
     * view are animated upwards while the bounds below the expanding view are
     * animated downwards. 6. The extra text is faded in as its contents become
     * visible throughout the animation process.
     * <p/>
     * It is important to note that the listview is disabled during the
     * animation because the scrolling behaviour is unpredictable if the bounds
     * of the items within the listview are not constant during the scroll.
     */

    private void expandView(final View view) {

        /* Store the original top and bottom bounds of all the cells. */
        final int oldTop = view.getTop();
        final int oldBottom = view.getBottom();

        final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>();

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View v = getChildAt(i);
            ViewCompat.setHasTransientState(v, true);
            oldCoordinates.put(v, new int[] { v.getTop(), v.getBottom() });
        }

        /* Update the layout so the extra content becomes visible. */
        final View expandingLayout = view.findViewById(R.id.expanding_layout);
        expandingLayout.setVisibility(View.VISIBLE);

        /*
           * Add an onPreDraw Listener to the listview. onPreDraw will get invoked
         * after onLayout and onMeasure have run but before anything has been
         * drawn. This means that the final post layout properties for all the
         * items have already been determined, but still have not been rendered
         * onto the screen.
         */
        final ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {
                /* Determine if this is the first or second pass. */
                if (!mShouldRemoveObserver) {
                    mShouldRemoveObserver = true;

                    /*
                     * Calculate what the parameters should be for
                     * setSelectionFromTop. The ListView must be offset in a
                     * way, such that after the animation takes place, all the
                     * cells that remain visible are rendered completely by the
                     * ListView.
                     */
                    int newTop = view.getTop();
                    int newBottom = view.getBottom();

                    int newHeight = newBottom - newTop;
                    int oldHeight = oldBottom - oldTop;
                    int delta = newHeight - oldHeight;

                    mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, delta, true);

                    int currentTop = view.getTop();
                    int futureTop = oldTop - mTranslate[0];

                    int firstChildStartTop = getChildAt(0).getTop();
                    int firstVisiblePosition = getFirstVisiblePosition();
                    int deltaTop = currentTop - futureTop;

                    int i;
                    int childCount = getChildCount();
                    for (i = 0; i < childCount; i++) {
                        View v = getChildAt(i);
                        int height = v.getBottom() - Math.max(0, v.getTop());
                        if (deltaTop - height > 0) {
                            firstVisiblePosition++;
                            deltaTop -= height;
                        } else {
                            break;
                        }
                    }

                    if (i > 0) {
                        firstChildStartTop = 0;
                    }

                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);

                    /*
                     * Request another layout to update the layout parameters of
                     * the cells.
                     */
                    requestLayout();

                    /*
                     * Return false such that the ListView does not redraw its
                     * contents on this layout but only updates all the
                     * parameters associated with its children.
                     */
                    return false;
                }

                /*
                 * Remove the predraw listener so this method does not keep
                 * getting called.
                 */
                mShouldRemoveObserver = false;
                observer.removeOnPreDrawListener(this);

                int yTranslateTop = mTranslate[0];
                int yTranslateBottom = mTranslate[1];

                ArrayList<Animator> animations = new ArrayList<Animator>();

                int index = indexOfChild(view);

                /*
                 * Loop through all the views that were on the screen before the
                 * cell was expanded. Some cells will still be children of the
                 * ListView while others will not. The cells that remain
                 * children of the ListView simply have their bounds animated
                 * appropriately. The cells that are no longer children of the
                 * ListView also have their bounds animated, but must also be
                 * added to a list of views which will be drawn in dispatchDraw.
                 */
                for (View v : oldCoordinates.keySet()) {
                    int[] old = oldCoordinates.get(v);
                    v.setTop(old[0]);
                    v.setBottom(old[1]);
                    if (v.getParent() == null) {
                        mViewsToDraw.add(v);
                        int delta = old[0] < oldTop ? -yTranslateTop : yTranslateBottom;
                        animations.add(getAnimation(v, delta, delta));
                    } else {
                        int i = indexOfChild(v);
                        if (v != view) {
                            int delta = i > index ? yTranslateBottom : -yTranslateTop;
                            animations.add(getAnimation(v, delta, delta));
                        }
                        ViewCompat.setHasTransientState(v, false);
                    }
                }

                /* Adds animation for expanding the cell that was clicked. */
                animations.add(getAnimation(view, -yTranslateTop, yTranslateBottom));

                /* Adds an animation for fading in the extra content. */
                animations.add(ObjectAnimator.ofFloat(view.findViewById(R.id.expanding_layout), View.ALPHA, 0, 1));

                /* Disabled the ListView for the duration of the animation. */
                setEnabled(false);
                setClickable(false);

                /*
                 * Play all the animations created above together at the same
                 * time.
                 */
                AnimatorSet s = new AnimatorSet();
                s.playTogether(animations);
                s.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        viewObject.setExpanded(true);
                        setEnabled(true);
                        setClickable(true);
                        if (mViewsToDraw.size() > 0) {
                            for (View v : mViewsToDraw) {
                                ViewCompat.setHasTransientState(v, false);
                            }
                        }
                        mViewsToDraw.clear();
                    }
                });
                s.start();
                return true;
            }
        });
    }

    /**
     * By overriding dispatchDraw, we can draw the cells that disappear during
     * the expansion process. When the cell expands, some items below or above
     * the expanding cell may be moved off screen and are thus no longer
     * children of the ListView's layout. By storing a reference to these views
     * prior to the layout, and guaranteeing that these cells do not get
     * recycled, the cells can be drawn directly onto the canvas during the
     * animation process. After the animation completes, the references to the
     * extra views can then be discarded.
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);

        if (mViewsToDraw.size() == 0) {
            return;
        }

        for (View v : mViewsToDraw) {
            canvas.translate(0, v.getTop());
            v.draw(canvas);
            canvas.translate(0, -v.getTop());
        }
    }

    /**
     * This method collapses the view that was clicked and animates all the
     * views around it to close around the collapsing view. There are several
     * steps required to do this which are outlined below.
     * <p/>
     * 1. Update the layout parameters of the view clicked so as to minimize its
     * height to the original collapsed (default) state. 2. After invoking a
     * layout, the listview will shift all the cells so as to display them most
     * efficiently. Therefore, during the first predraw pass, the listview must
     * be offset by some amount such that given the custom bound change upon
     * collapse, all the cells that need to be on the screen after the layout
     * are rendered by the listview. 3. On the second predraw pass, all the
     * items are first returned to their original location (before the first
     * layout). 4. The collapsing view's bounds are animated to what the final
     * values should be. 5. The bounds above the collapsing view are animated
     * downwards while the bounds below the collapsing view are animated
     * upwards. 6. The extra text is faded out as its contents become visible
     * throughout the animation process.
     */

    private void collapseView(final View view) {

        /* Store the original top and bottom bounds of all the cells. */
        final int oldTop = view.getTop();
        final int oldBottom = view.getBottom();

        final HashMap<View, int[]> oldCoordinates = new HashMap<View, int[]>();

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View v = getChildAt(i);
            ViewCompat.setHasTransientState(v, true);
            oldCoordinates.put(v, new int[] { v.getTop(), v.getBottom() });
        }

        /* Update the layout so the extra content becomes invisible. */
        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, viewObject.mCollapsedHeight));

        /* Add an onPreDraw listener. */
        final ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {

                if (!mShouldRemoveObserver) {
                    /*
                     * Same as for expandingView, the parameters for
                     * setSelectionFromTop must be determined such that the
                     * necessary cells of the ListView are rendered and added to
                     * it.
                     */
                    mShouldRemoveObserver = true;

                    int newTop = view.getTop();
                    int newBottom = view.getBottom();

                    int newHeight = newBottom - newTop;
                    int oldHeight = oldBottom - oldTop;
                    int deltaHeight = oldHeight - newHeight;

                    mTranslate = getTopAndBottomTranslations(oldTop, oldBottom, deltaHeight, false);

                    int currentTop = view.getTop();
                    int futureTop = oldTop + mTranslate[0];

                    int firstChildStartTop = getChildAt(0).getTop();
                    int firstVisiblePosition = getFirstVisiblePosition();
                    int deltaTop = currentTop - futureTop;

                    int i;
                    int childCount = getChildCount();
                    for (i = 0; i < childCount; i++) {
                        View v = getChildAt(i);
                        int height = v.getBottom() - Math.max(0, v.getTop());
                        if (deltaTop - height > 0) {
                            firstVisiblePosition++;
                            deltaTop -= height;
                        } else {
                            break;
                        }
                    }

                    if (i > 0) {
                        firstChildStartTop = 0;
                    }

                    setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop);

                    requestLayout();

                    return false;
                }

                mShouldRemoveObserver = false;
                observer.removeOnPreDrawListener(this);

                int yTranslateTop = mTranslate[0];
                int yTranslateBottom = mTranslate[1];

                int index = indexOfChild(view);
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View v = getChildAt(i);
                    int[] old = oldCoordinates.get(v);
                    if (old != null) {
                        /*
                         * If the cell was present in the ListView before the
                         * collapse and after the collapse then the bounds are
                         * reset to their old values.
                         */
                        v.setTop(old[0]);
                        v.setBottom(old[1]);
                        ViewCompat.setHasTransientState(v, false);
                    } else {
                        /*
                         * If the cell is present in the ListView after the
                         * collapse but not before the collapse then the bounds
                         * are calculated using the bottom and top translation
                         * of the collapsing cell.
                         */
                        int delta = i > index ? yTranslateBottom : -yTranslateTop;
                        v.setTop(v.getTop() + delta);
                        v.setBottom(v.getBottom() + delta);
                    }
                }

                final View expandingLayout = view.findViewById(R.id.expanding_layout);

                /*
                 * Animates all the cells present on the screen after the
                 * collapse.
                 */
                ArrayList<Animator> animations = new ArrayList<Animator>();
                for (int i = 0; i < childCount; i++) {
                    View v = getChildAt(i);
                    if (v != view) {
                        float diff = i > index ? -yTranslateBottom : yTranslateTop;
                        animations.add(getAnimation(v, diff, diff));
                    }
                }

                /* Adds animation for collapsing the cell that was clicked. */
                animations.add(getAnimation(view, yTranslateTop, -yTranslateBottom));

                /* Adds an animation for fading out the extra content. */
                animations.add(ObjectAnimator.ofFloat(expandingLayout, View.ALPHA, 1, 0));

                /* Disabled the ListView for the duration of the animation. */
                setEnabled(false);
                setClickable(false);

                /*
                 * Play all the animations created above together at the same
                 * time.
                 */
                AnimatorSet s = new AnimatorSet();
                s.playTogether(animations);
                s.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        expandingLayout.setVisibility(View.GONE);
                        view.setLayoutParams(
                                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
                        viewObject.setExpanded(false);
                        setEnabled(true);
                        setClickable(true);
                        /*
                         * Note that alpha must be set back to 1 in case this
                         * view is reused by a cell that was expanded, but not
                         * yet collapsed, so its state should persist in an
                         * expanded state with the extra content visible.
                         */
                        expandingLayout.setAlpha(1);
                    }
                });
                s.start();

                return true;
            }
        });
    }

    /**
     * This method takes some view and the values by which its top and bottom
     * bounds should be changed by. Given these params, an animation which will
     * animate these bound changes is created and returned.
     */
    private Animator getAnimation(final View view, float translateTop, float translateBottom) {

        int top = view.getTop();
        int bottom = view.getBottom();

        int endTop = (int) (top + translateTop);
        int endBottom = (int) (bottom + translateBottom);

        PropertyValuesHolder translationTop = PropertyValuesHolder.ofInt("top", top, endTop);
        PropertyValuesHolder translationBottom = PropertyValuesHolder.ofInt("bottom", bottom, endBottom);

        return ObjectAnimator.ofPropertyValuesHolder(view, translationTop, translationBottom);
    }
}