Reputation: 42804
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
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.
Upvotes: 28
Views: 80891
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
Reputation: 582
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)
}
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:
Upvotes: 13
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
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
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
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.
Upvotes: 7