28

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

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

6 Answers6

87

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.

Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
  • 1
    This is not smooth in case when sliding panel have a list of images to show. Any solution for that? – USER_NAME Jun 06 '16 at 14:36
  • 4
    This is not working when getMeasuredHeight return a wrong value, which happens when you use a multiline TextView. The animation is until the wrong value, than the v.getLayoutParams().height = interpolatedTime == 1 ? LayoutParams.WRAP_CONTENT just open the rest of it without animation. – Shirane85 Sep 11 '16 at 12:22
  • @HirenPatel if we want to slow the pace of expand and collapse then what changes you suggest. – Ravi Yadav Sep 21 '17 at 08:50
  • This is exactly the same code in the post reference by the original post, which is problematically as stated by the original post. This does not work for me either, the layout blinks and do not animate to the maximum height of the view. – Sira Lam Oct 18 '17 at 09:14
  • your solutions is perfect. but i have query about height of the view which is going to exapnd. from where i can fix the height of view which is expanding. i have tried to give height to xml layout but its not working.and if i am giving height in this method for expand. it looks weird. – jeel raja Jun 28 '18 at 06:57
  • 1
    plagiarised from https://stackoverflow.com/a/13381228/7873768 – MDT May 31 '20 at 08:16
27

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

Jeremie D
  • 4,145
  • 1
  • 35
  • 41
17

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................

OneWorld
  • 17,512
  • 21
  • 86
  • 136
Ashish Saini
  • 2,328
  • 25
  • 21
  • 39
    Its very bad practice to use a String as an argument. Just use a "boolean expand". Also – TjerkW Mar 04 '16 at 15:06
  • @TjerkW better practice still make that `expandOrCollapse` method 2 methods: `expand` and `collapse`. That if inside the method is a design smell. It's doing two things, it should do just one – Jose_GD Oct 09 '18 at 21:01
  • Not working if starts as collapsed because v.getHeight() returns 0 – Krzysztof Dziuba Apr 02 '19 at 11:31
11

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

7

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

Amit Gupta
  • 8,914
  • 1
  • 25
  • 33
  • 1
    Thanks. But this cannot be a substitute to real ExpandableListView where you want to have a huge list inside a particular group. Here there's no concept of list inside a list. It's just a list and actions inside every list row that animates out. Nice library though. – Sudarshan Bhat Nov 06 '13 at 06:03
  • What is the compile command to add this library to my project. @amit – Vivek Av May 13 '17 at 15:44
1

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
}