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.
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.
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
How to Get the Source Code from an Apk File
Why Does an Image Captured Using Camera Intent Gets Rotated on Some Devices on Android
How to Prevent a Dialog from Closing When a Button Is Clicked
Example: Communication Between Activity and Service Using Messaging
Android: Check Whether the Phone Is Dual Sim
How to Manually Include External Aar Package Using Gradle For Android
How to Resize a Bitmap in Android
How to Scale an Image in Imageview to Keep the Aspect Ratio
Recyclerview Inside Scrollview Is Not Working
Horizontalscrollview Within Scrollview Touch Handling
Run/Install/Debug Android Applications Over Wi-Fi
How to Increase Heap Size of an Android Application
Passing Android Bitmap Data Within Activity Using Intent in Android
Adb Server Version Doesn't Match This Client
How to Set a Custom Font in the Actionbar Title
Comparison of Android Networking Libraries: Okhttp, Retrofit, and Volley