Expand/collapse animation in CardView

24,157

Solution 1

You'll need to create a custom class that extends CardView. Inside that class put the following methods:

public void expand() {
    int initialHeight = getHeight();

    measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    int targetHeight = getMeasuredHeight();

    int distanceToExpand = targetHeight - initialHeight;

    Animation a = new Animation() {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (interpolatedTime == 1){
                // Do this after expanded
            }

            getLayoutParams().height = (int) (initialHeight + (distanceToExpand * interpolatedTime));
            requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    a.setDuration((long) distanceToExpand);
    startAnimation(a);
}

public void collapse(int collapsedHeight) {
    int initialHeight = getMeasuredHeight();

    int distanceToCollapse = (int) (initialHeight - collapsedHeight);

    Animation a = new Animation() {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (interpolatedTime == 1){
                // Do this after collapsed
            }


            Log.i(TAG, "Collapse | InterpolatedTime = " + interpolatedTime);

            getLayoutParams().height = (int) (initialHeight - (distanceToCollapse * interpolatedTime));
            requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    a.setDuration((long) distanceToCollapse);
    startAnimation(a);
}

Note that when you collapse it, you'll need to pass along the height you want it to be when collapsed. The height when expanded is set to WRAP_CONTENT.

I've also added if/else statements that will run when the animation has completed.

Good luck!

Solution 2

I did not understand what you meant by displaying 10 elements out of 50. However, you can achieve the expand/collapse simply by showing/hiding the views and providing android:animateLayoutChanges="true" into the child layout of the CardView. Here is an example:

<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        android:padding="16dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/hello"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"/>

        <TextView
            android:id="@+id/hello2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:visibility="gone"/>

        <TextView
            android:id="@+id/hello3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:visibility="gone"/>

    </LinearLayout>
</android.support.v7.widget.CardView>

And corresponding controller:

    TextView t1 = (TextView) findViewById(R.id.hello);
    final TextView t2 = (TextView) findViewById(R.id.hello2);
    final TextView t3 = (TextView) findViewById(R.id.hello3);

    t1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (t2.getVisibility() == View.GONE) {
                t2.setVisibility(View.VISIBLE);
                t3.setVisibility(View.VISIBLE);
            } else {
                t2.setVisibility(View.GONE);
                t3.setVisibility(View.GONE);
            }
        }
    });

Tapping on the first TextView will collapse and expand the CardView along with the animation.

Share:
24,157
filol
Author by

filol

Updated on January 06, 2020

Comments

  • filol
    filol over 4 years

    I try to do something like this : enter image description here

    I managed to do my cardViewAdapter but I block to enlarge my cards. I resumed the code of this response (Here the name of the class is : CardsAnimationHelper) to do the animation but it's superimposed.

    Before expand: picture After expand: picture

    I solved the problem above but if on my cardView I display 10 elements at the same time for a list of 50. If I expand the first, the numbers 11,21,31,41 will also expand. Do you have a trick for this not to happen?

    I have reflected, it makes no sense to me. Just before my OnClick method I display a textview where the text is the position. But when I click id are correct so that would mean that when I click it detects the click on several cards. I think I may have a problem with a view in my OnClickListener

    My CardView

    <?xml version="1.0" encoding="utf-8"?>
    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
    
            app:cardBackgroundColor="@android:color/white"
            app:cardCornerRadius="2dp"
            app:cardElevation="2dp">
    
            <!-- Les CardView possèdent des attributs supplémentaires dont
                 - cardBackgroundColor
                 - cardElevation pour l'élévation (donc aussi l'ombre)
                 - cardCornerRadius pour arrondir les angles
             -->
    
            <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
                <!-- Les CardView agissent comme des FrameLayout,
                 pour avoir une organisation verticale nous devons
                 donc rajouter un LinearLayout -->
    
    
                <TextView
                    android:id="@+id/text_cards"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="?android:selectableItemBackground"
                    android:padding="20dp"
                    tools:text="Paris"
                    android:fontFamily="sans-serif"
                    android:textColor="#333"
                    android:textSize="18sp" />
    
                <ImageView
                    android:id="@+id/item_description_game_more"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical|end"
                    android:transitionName="@string/transition_cards_view"
                    app:srcCompat="@drawable/ic_expand_more_black_24dp"/>
    
                <include layout="@layout/cards_resume_game_expand"/>
    
            </android.support.design.widget.CoordinatorLayout>
    
        </android.support.v7.widget.CardView>
    
    </LinearLayout>
    

    My New Adapter

    public class CardsViewAdapter extends RecyclerView.Adapter<CardsViewAdapter.ViewHolder> {
        private Game[] mDataset;
        private boolean isPopupVisible = false;
        int rotationAngle = 0;
    
        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder
        public static class ViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView mTextView;
            public ImageView imageView;
            public LinearLayout test2;
    
            public ViewHolder(View v) {
                super(v);
                mTextView = (TextView) v.findViewById(R.id.text_cards);
                imageView = (ImageView) v.findViewById(R.id.item_description_game_more);
                test2 = (LinearLayout) v.findViewById(R.id.popup_layout);
    
            }
        }
    
        // Provide a suitable constructor (depends on the kind of dataset)
        public CardsViewAdapter(Game[] myDataset) {
            mDataset = myDataset;
        }
    
        // Create new views (invoked by the layout manager)
        @Override
        public CardsViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {
            // create a new view
            View v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.cards_resume_game, parent, false);
            // set the view's size, margins, paddings and layout parameters
            //...
    
            ViewHolder vh = new ViewHolder(v);
            return vh;
        }
    
        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(final ViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
    
            holder.mTextView.setText(String.valueOf(mDataset[position].getId_game()));
            holder.imageView.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    // Perform action on click
                    if (isPopupVisible) {
                        isPopupVisible = false;
    
                        ObjectAnimator anim = ObjectAnimator.ofFloat(v, "rotation",rotationAngle, rotationAngle + 180);
                        anim.setDuration(500);
                        anim.start();
                        rotationAngle += 180;
                        rotationAngle = rotationAngle%360;
    
    //                    CardsAnimationHelper.changeIconAnim((TextView) v, getString(R.string.icon_chevron_up));
                        CardsAnimationHelper.collapse(holder.test2);
                    } else {
                        isPopupVisible = true;
    
                        ObjectAnimator anim = ObjectAnimator.ofFloat(v, "rotation",rotationAngle, rotationAngle + 180);
                        anim.setDuration(500);
                        anim.start();
                        rotationAngle += 180;
                        rotationAngle = rotationAngle%360;
    
    //                    CardsAnimationHelper.changeIconAnim((TextView) v, getString(R.string.icon_chevron_down));
                        CardsAnimationHelper.expand(holder.test2);
                    }
                }
            });
    
    
    
        }
    
        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }
    
  • filol
    filol over 7 years
    I already have this class (see my link in my post): stackoverflow.com/a/13381228/7355733
  • LukeWaggoner
    LukeWaggoner over 7 years
    You need to have just one layout for your CardView that expands. Inside the expansion and collapsing, you can hide and show the views that you want to hide and show based on whether it's expanded or collapsed. i.e. Show the header while it's collapsed, and in the process of showing it, hide the header and show the contents.
  • filol
    filol over 7 years
    Thx, it's working. The solution was only to make a single layout
  • filol
    filol over 7 years
    However on my cardView I display 10 elements at the same time for a list of 50. If I expand the first, the numbers 11,21,31,41 will also expand. Do you have a trick for this not to happen?
  • LukeWaggoner
    LukeWaggoner over 7 years
    Why is it expanding more than just the card on which you've called expand()?
  • filol
    filol over 7 years
    This is the question ^^ why ?
  • LukeWaggoner
    LukeWaggoner over 7 years
    How are you calling expand()? You should be calling it on a specific instance of CardView, and it should only be affecting that one.
  • filol
    filol over 7 years
    Check my post, i have add my Adapter edited lastly. But for me my expand() is only on one item
  • filol
    filol over 7 years
    I have reflected, it makes no sense to me. Just before my OnClick method I display a textview where the text is the position. But when I click id are correct so that would mean that when I click it detects the click on several cards.
  • LukeWaggoner
    LukeWaggoner over 7 years
    I'd need to see your CardsAnimationHelper to be sure about this, but I think it's because your ViewHolder class is static. You're instantiating it and passing it around as the ViewHolder anyway, so you don't need that to be static, and making it not static allows you to call methods on one specific card, as opposed to multiple.
  • Parag Pawar
    Parag Pawar over 4 years
    @filol, It's also happening with me, in my case it's 30 items and every 7th or 8th item is expanding. By any chance do you remember what was the solution for it?
  • AkshayT
    AkshayT almost 4 years
    Dude, you deserve a beer! So simple and elegant. I just wasted 4-5 hours.
  • sud007
    sud007 over 3 years
    android:animateLayoutChanges="true works really fine dude!