Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42804

How to achieve smooth expand/collapse animation

I'm referring the expand / collapse animation code found here.

Android: Expand/collapse animation

Although it works, it doesn't do the job well. The animation isn't smooth.

I do some logging in the code.

public static void expand(final View v) {
    v.measure(MeasureSpec.makeMeasureSpec(((View)v.getParent()).getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(1024, MeasureSpec.AT_MOST));
    final int targtetHeight = v.getMeasuredHeight();

    v.getLayoutParams().height = 0;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targtetHeight * interpolatedTime);
            Log.i("CHEOK", "E v.getLayoutParams().height = " + v.getLayoutParams().height);
            v.requestLayout();
        }

The following log message is printed.

10-09 12:29:58.808: I/CHEOK(7874): E v.getLayoutParams().height = 0
10-09 12:29:58.808: I/CHEOK(7874): E v.getLayoutParams().height = 0
10-09 12:29:58.918: I/CHEOK(7874): E v.getLayoutParams().height = 11
10-09 12:29:59.015: I/CHEOK(7874): E v.getLayoutParams().height = 35
10-09 12:29:59.117: I/CHEOK(7874): E v.getLayoutParams().height = 64
10-09 12:29:59.215: I/CHEOK(7874): E v.getLayoutParams().height = 85
10-09 12:29:59.316: I/CHEOK(7874): E v.getLayoutParams().height = -2
10-09 12:29:59.406: I/CHEOK(7874): E v.getLayoutParams().height = -2

New height occur every ~100ms. So, the FPS of the animation is around 10fps

I want to see what is the ideal animation frame rate. I remove the v.requestLayout();. I get the following logging.

10-09 12:32:06.547: I/CHEOK(8926): E v.getLayoutParams().height = 0
10-09 12:32:06.562: I/CHEOK(8926): E v.getLayoutParams().height = 0
10-09 12:32:06.605: I/CHEOK(8926): E v.getLayoutParams().height = 4
10-09 12:32:06.625: I/CHEOK(8926): E v.getLayoutParams().height = 7
10-09 12:32:06.644: I/CHEOK(8926): E v.getLayoutParams().height = 10
10-09 12:32:06.664: I/CHEOK(8926): E v.getLayoutParams().height = 14
10-09 12:32:06.679: I/CHEOK(8926): E v.getLayoutParams().height = 18
10-09 12:32:06.699: I/CHEOK(8926): E v.getLayoutParams().height = 22
10-09 12:32:06.715: I/CHEOK(8926): E v.getLayoutParams().height = 27
10-09 12:32:06.734: I/CHEOK(8926): E v.getLayoutParams().height = 32
10-09 12:32:06.750: I/CHEOK(8926): E v.getLayoutParams().height = 37
10-09 12:32:06.769: I/CHEOK(8926): E v.getLayoutParams().height = 42
10-09 12:32:06.785: I/CHEOK(8926): E v.getLayoutParams().height = 47
10-09 12:32:06.804: I/CHEOK(8926): E v.getLayoutParams().height = 52
10-09 12:32:06.828: I/CHEOK(8926): E v.getLayoutParams().height = 59
10-09 12:32:06.840: I/CHEOK(8926): E v.getLayoutParams().height = 62
10-09 12:32:06.863: I/CHEOK(8926): E v.getLayoutParams().height = 67
10-09 12:32:06.879: I/CHEOK(8926): E v.getLayoutParams().height = 71
10-09 12:32:06.894: I/CHEOK(8926): E v.getLayoutParams().height = 75
10-09 12:32:06.910: I/CHEOK(8926): E v.getLayoutParams().height = 79
10-09 12:32:06.929: I/CHEOK(8926): E v.getLayoutParams().height = 82
10-09 12:32:06.945: I/CHEOK(8926): E v.getLayoutParams().height = 85
10-09 12:32:06.965: I/CHEOK(8926): E v.getLayoutParams().height = 88
10-09 12:32:06.984: I/CHEOK(8926): E v.getLayoutParams().height = 89
10-09 12:32:07.000: I/CHEOK(8926): E v.getLayoutParams().height = 91
10-09 12:32:07.019: I/CHEOK(8926): E v.getLayoutParams().height = 91
10-09 12:32:07.039: I/CHEOK(8926): E v.getLayoutParams().height = -2
10-09 12:32:07.054: I/CHEOK(8926): E v.getLayoutParams().height = -2

New height occur every ~20ms. So, the FPS of the animation is around 50fps

Of course, I can't just remove requestLayout, as UI won't updated on screen.

I was wondering, is there any improvement can be done, to achieve animation FPS closed to 50fps? I had seen some commercial product with smooth Expand/collapse example. So, I think this is something achievable. Just that, I'm not sure exactly how.

My layout code is as followed :

        <LinearLayout
            android:clickable="true"
            android:id="@+id/chart_linear_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:background="@drawable/dummy"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="0dp"
                    android:width="0dp"
                    android:layout_weight="0.6"
                    android:layout_height="wrap_content"
                    android:gravity="left"
                    android:textSize="20sp" 
                    android:textColor="#ff000000"
                    android:text="Summary chart" />
                <TextView
                    android:id="@+id/chart_price_text_view"
                    android:layout_width="0dp"
                    android:width="0dp"
                    android:layout_weight="0.4"
                    android:layout_height="wrap_content"
                    android:gravity="right"
                    android:textSize="20sp" 
                    android:textColor="#ffF76D3C"
                    android:text="$2.99" />

            </LinearLayout>
            <TextView
                android:visibility="gone"
                android:id="@+id/chart_description_text_view"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:layout_marginBottom="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"         
                android:text="@string/currency_exchange_description"
                android:textColor="#ff626262"
                android:textSize="15sp" />                 
        </LinearLayout>

I wish to perform smooth animation on chart_description_text_view from

Collapsing (During app startup)

enter image description here

Expanding (When user taps on it)

enter image description here

One of the "role models" I can think of is https://play.google.com/store/apps/details?id=ch.bitspin.timely. Try to invoke the below dialog from its Shop menu item. You will realize how smooth their animation is. Not exactly sure how they achieve that.

enter image description here

Upvotes: 28

Views: 80891

Answers (6)

Vikash Kumar Tiwari
Vikash Kumar Tiwari

Reputation: 835

You can apply CHANGING layout transition on layout. You can use below extension function which can be used to apply CHANGING transition on any layout in android by passing the duration in milliseconds

fun ViewGroup.setChangingTransition(animationDuration: Long) {
    val layoutTransition = LayoutTransition()
    layoutTransition.setDuration(animationDuration)
    layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
    this.layoutTransition = layoutTransition
}

Upvotes: 1

Here is kotlin way smoth animation. I added some fade animation to @TomEsterez answer:

Expand:

  fun expand(v: View) {
        if (v.visibility == View.VISIBLE) return
        val durations: Long
        val matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(
            (v.parent as View).width,
            View.MeasureSpec.EXACTLY
        )
        val wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(
            0,
            View.MeasureSpec.UNSPECIFIED
        )
        v.measure(matchParentMeasureSpec, wrapContentMeasureSpec)
        val targetHeight = v.measuredHeight

        // Older versions of android (pre API 21) cancel animations for views with a height of 0.
        v.layoutParams.height = 1
        v.visibility = View.VISIBLE
        durations = ((targetHeight / v.context.resources
            .displayMetrics.density)).toLong()

        v.alpha = 0.0F
        v.visibility = View.VISIBLE
        v.animate().alpha(1.0F).setDuration(durations).setListener(null)

        val a: Animation = object : Animation() {
            override fun applyTransformation(
                interpolatedTime: Float,
                t: Transformation
            ) {
                v.layoutParams.height =
                    if (interpolatedTime == 1f) LinearLayout.LayoutParams.WRAP_CONTENT else (targetHeight * interpolatedTime).toInt()
                v.requestLayout()
            }

            override fun willChangeBounds(): Boolean {
                return true
            }
        }

        // Expansion speed of 1dp/ms
        a.duration = durations
        v.startAnimation(a)
    }

Collapse

fun collapse(v: View) {
        if (v.visibility == View.GONE) return
        val durations: Long
        val initialHeight = v.measuredHeight
        val a: Animation = object : Animation() {
            override fun applyTransformation(
                interpolatedTime: Float,
                t: Transformation
            ) {
                if (interpolatedTime == 1f) {
                    v.visibility = View.GONE
                } else {
                    v.layoutParams.height =
                        initialHeight - (initialHeight * interpolatedTime).toInt()
                    v.requestLayout()
                }
            }

            override fun willChangeBounds(): Boolean {
                return true
            }
        }

        durations = (initialHeight / v.context.resources
            .displayMetrics.density).toLong()

        v.alpha = 1.0F
        v.animate().alpha(0.0F).setDuration(durations)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    v.visibility = View.GONE
                    v.alpha = 1.0F
                }
            })

        // Collapse speed of 1dp/ms
        a.duration = durations
        v.startAnimation(a)
    }

Here is the output result

Then you can rotate Icon if you want. (

// Expande
exImgHeader.animate().rotation(180f).start()
// Collapse
exImgHeader.animate().rotation(0f).start()

) Result will be like this:

enter image description here

Upvotes: 13

Hiren Patel
Hiren Patel

Reputation: 52810

You can do Smooth Animation of View this way:

public class ViewAnimationUtils {

    public static void expand(final View v) {
        v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        final int targtetHeight = v.getMeasuredHeight();

        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        Animation a = new Animation()
        {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                v.getLayoutParams().height = interpolatedTime == 1
                        ? LayoutParams.WRAP_CONTENT
                        : (int)(targtetHeight * interpolatedTime);
                v.requestLayout();
            }

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

        a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
        v.startAnimation(a);
    }

    public static void collapse(final View v) {
        final int initialHeight = v.getMeasuredHeight();

        Animation a = new Animation()
        {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if(interpolatedTime == 1){
                    v.setVisibility(View.GONE);
                }else{
                    v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                    v.requestLayout();
                }
            }

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

        a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
        v.startAnimation(a);
    }
}

Hope it will help you.

Upvotes: 87

Ashish Saini
Ashish Saini

Reputation: 2318

Its very easy to get smooth animation (Expand/Collapse) on View

public void expandOrCollapse(final View v,String exp_or_colpse) {
    TranslateAnimation anim = null;
    if(exp_or_colpse.equals("expand"))
    {
        anim = new TranslateAnimation(0.0f, 0.0f, -v.getHeight(), 0.0f);
        v.setVisibility(View.VISIBLE);  
    }
    else{
        anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, -v.getHeight());
        AnimationListener collapselistener= new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            v.setVisibility(View.GONE);
            }
        };

        anim.setAnimationListener(collapselistener);
    }

     // To Collapse
        //

    anim.setDuration(300);
    anim.setInterpolator(new AccelerateInterpolator(0.5f));
    v.startAnimation(anim);
}

And use this method in your code. I have used in TextView and it is tested .You may pass your view in this method as a parameter.

        TextView tv=(TextView)findViewById(R.id.textview);
          //TO Expand 
         expandOrCollapse(tv,"expand");

        //TO Collapse
        expandOrCollapse(tv,"collapse");

ANd Enjoy smooth Collapse and Expand Animation................

Upvotes: 17

Jeremie D
Jeremie D

Reputation: 4204

The easiest way to do animations is by adding the following to your xml for your linear layout:

android:animateLayoutChanges="true"

It works great and uses default animations directly from Android

Upvotes: 27

Amit Gupta
Amit Gupta

Reputation: 8939

These is a very good example on SlideExpandibleList in Github.

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

Hope this will help you to achieve smooth animation and collapse.

In this example , it saved the state of expand list item. So even if you will scroll down the list it wont let the expanded list item to close.

In this example expand or collapse event is given on Button, so you need to change it List item parent layout.

I attached the screen shots.

Hope this will help you.

enter image description here

Upvotes: 7

Related Questions