Android: Expand/Collapse Animation

Android: Expand/collapse animation

I see that this question became popular so I post my actual solution. The main advantage is that you don't have to know the expanded height to apply the animation and once the view is expanded, it adapts height if content changes. It works great for me.

public static void expand(final View v) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = v.getMeasuredHeight();

// Older versions of android (pre API 21) cancel animations for views with a height of 0.
v.getLayoutParams().height = 1;
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)(targetHeight * interpolatedTime);
v.requestLayout();
}

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

// Expansion speed of 1dp/ms
a.setDuration((int)(targetHeight / 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;
}
};

// Collapse speed of 1dp/ms
a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}

As mentioned by @Jefferson in the comments, you can obtain a smoother animation by changing the duration (and hence the speed) of the animation. Currently, it has been set at a speed of 1dp/ms

expand/collapse Animation Android half view

Use Transition API which is available in support package (androidx). Just call TransitionManager.beginDelayedTransition then change height of view. TransitionManager will handle this changes and it will provide transition which will change imageView with animation.

Sample Image

scaleType of ImageView here is centerCrop thats why image scales when collapse and expand. Unfortunetly there is no "fill width and crop bottom" scaleType, so if you need it I think it can be done throught scaleType = matrix .

import androidx.appcompat.app.AppCompatActivity;
import androidx.transition.TransitionManager;

public class MainActivity extends AppCompatActivity {

private ImageView image;
private ViewGroup parent;
boolean collapse = true;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

image = findViewById(R.id.image);
parent = findViewById(R.id.parent);

findViewById(R.id.btn).setOnClickListener(view -> {
collapse = !collapse;
collapse();
});
}

private void collapse() {
TransitionManager.beginDelayedTransition(parent);
//change layout params
int height = image.getHeight();
LayoutParams layoutParams = image.getLayoutParams();
layoutParams.height = !collapse ? height / 2 : height * 2;
image.requestLayout();
}
}

Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/btn"
android:text="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:src="@drawable/qwe" />

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="random text"
android:layout_margin="8dp"/>
</LinearLayout>

UPDATE:

There is beginDelayedTransition(ViewGroup, Transtion) method. beginDelayedTransition(ViewGroup) by default use AutoTransition as transition.
So if you need handle start/end of transition you can do it like this:

    AutoTransition transition = new AutoTransition();
transition.addListener(new TransitionListenerAdapter(){
@Override
public void onTransitionStart(@NonNull Transition transition) {
//TODO
}
@Override
public void onTransitionEnd(@NonNull Transition transition) {
//TODO
}
});
TransitionManager.beginDelayedTransition(parent, transition);

Expand/collapse animation in CardView

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!

Android expand/collapse RelativeLayout

It is because

            if (isVisible) {
mAnimationManager.collapse(mRelativeToSlide, 1000, 200);
isVisible = false;
} else if (!isVisible){
mAnimationManager.expand(mRelativeToSlide, 1000, 200);
isVisible = true;
}

collapse() and expand() do the same thing, they are both expand Animations in this case. You need to pass a different value to your collapse() method;
The simple solution is

    mAnimationManager.collapse(mRelativeToSlide, 1000, -200);

But there are some more issues with your coding style, for example you could just get rid of your collapse() method because calling expand two times like this would also work :

            if (isVisible) {
mAnimationManager.expand(mRelativeToSlide, 1000, -200);
isVisible = false;
} else if (!isVisible){
mAnimationManager.expand(mRelativeToSlide, 1000, 200);
isVisible = true;
}

I suggest you post it on Code Review.

How to achieve smooth expand/collapse animation

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.

Sample Image

View Animations in Android Collapse/Expand Views in LinearLayout

I have made separate class for it. Check if it works for you:

public class DropDownAnim extends Animation {
private final int sourceHeight;
private final int targetHeight;
private final View view;
private final boolean down;

public DropDownAnim(View view, int sourceHeight, int targetHeight, boolean down) {
this.view = view;
this.sourceHeight = sourceHeight;
this.targetHeight = targetHeight;
this.down = down;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
if (down) {
newHeight = sourceHeight + (int) (targetHeight * interpolatedTime);
} else {
newHeight = sourceHeight + targetHeight - (int) (targetHeight * interpolatedTime);//(int) (targetHeight * (1 - interpolatedTime));
}
view.getLayoutParams().height = newHeight;
view.requestLayout();
view.setVisibility(down ? View.VISIBLE : View.GONE);
}

@Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}

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

While working with this. For expand

public void expand(final View v) {
final int sourceHeight = v.getLayoutParams().height;
final int targetHeight = getResources().getDimensionPixelSize(R.dimen.notification_height);//v.getMeasuredHeight();
DropDownAnim a = new DropDownAnim(v,targetHeight,true);
a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density));
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
// Your code on end of animation
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
v.setVisibility(View.INVISIBLE);
v.startAnimation(a);
}

For collapse:

public void collapse(final View v) {
final int sourceHeight = v.getLayoutParams().height;
final int targetHeight = v.getMeasuredHeight();
DropDownAnim a = new DropDownAnim(v, targetHeight, false);
a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density));
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
// Your code on end of animation
}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
v.startAnimation(a);
}

Update:

sourceHeight added to prevent view blinking.

Animated Expand/Collapse group in horizontal ScrollView (SwiftUI)

You need to use value for animation:

struct ContentView: View {
@State var expanded = false
let colors: [Color] = [.red, .green, .blue, .orange, .blue, .red, .green, .blue, .orange, .blue]
var body: some View {
VStack {
ScrollView(.horizontal) {
VStack(spacing: 20) {
ForEach(colors.prefix(upTo: expanded ? 10 : 5), id: \.self) { color in
Text(color.description.capitalized)
}
}
}
Button("Expand/collapse") {
expanded.toggle()
}
Spacer()
}.animation(.easeIn(duration: 1), value: expanded) // <<: Here!
}
}


Related Topics



Leave a reply



Submit