more button between two recycler view - android

I have two recycler views in one screen and in between of them, there is one more button like this.
I want to update the height of recycler view of the upper one to MATCH PARENT with animation, So I had tried this.
binding.moreLl.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ValueAnimator anim = ValueAnimator.ofInt();
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = binding.fragmentWalletRv.getLayoutParams();
layoutParams.height = val;
binding.fragmentWalletRv.setLayoutParams(layoutParams);
}
});
anim.setDuration(500);
anim.start();
}
});
But unfortunately i cant get the expected result. So please guide me how i can do that thing.
Thanks in advance.

If you just want to have an animation when layout changes, no need to use a custom animation.
Try adding default layout change animation by enabling
android:animateLayoutChanges="true"
to the parent layout in xml.
On your more button click, only change the LayoutParams of the Recyclerview.
To change the speed of animation
LinearLayout layout = mContentView.findViewById(R.id.parent_layout);
LayoutTransition lt = layout.getLayoutTransition();
lt.setDuration(2000);

public class SlideAnimation extends Animation {
int mFromHeight;
int mToHeight;
View mView;
public SlideAnimation(View view, int fromHeight, int toHeight) {
this.mView = view;
this.mFromHeight = fromHeight;
this.mToHeight = toHeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation transformation) {
int newHeight;
if (mView.getHeight() != mToHeight) {
newHeight = (int) (mFromHeight + ((mToHeight - mFromHeight) * interpolatedTime));
mView.getLayoutParams().height = newHeight;
mView.requestLayout();
}
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Use above animation like below
rv1 = findViewById(R.id.rv1);
Animation animation = new SlideAnimation(rv1, rv1.getHeight(), 500);
animation.setInterpolator(new AccelerateInterpolator());
animation.setDuration(500);
rv1.setAnimation(animation);
rv1.startAnimation(animation);

Related

Enlarge animation in android

I want to achieve the below animation in android I have tried scenes but scenes do not work with text as per docs it is confirmed :
"If you try to resize a TextView with an animation, the text will pop to a new location before the object has completely resized. To avoid this problem, do not animate the resizing of views that contain text."
Please any solution , the enlarged layout text can contain images too.
animation video
this thing worked some how ,but the animation is little jittery,I guess layout height final value is attained first and layoutWidth later, have to fix this. this is my enlarge/reduce animation :
public class EnlargeAnimation extends Animation {
private final int diffHeight;
private final int diffWidth;
private final int initialHeight;
private final int initialWidth;
private final View targetView;
public EnlargeAnimation(View targetView, float targetHeight, float targetWidth) {
this.targetView = targetView;
this.initialHeight = targetView.getMeasuredHeight();
this.initialWidth = targetView.getMeasuredWidth();
this.diffHeight = (int) (targetHeight-initialHeight);
this.diffWidth = (int) (targetWidth-initialWidth);
}
#Override
public boolean willChangeBounds() {
return true;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float newHeight = initialHeight + diffHeight * interpolatedTime;
float newWidth = initialWidth + diffWidth * interpolatedTime;
targetView.getLayoutParams().height = (int) newHeight;
targetView.getLayoutParams().width = (int) newWidth;
targetView.requestLayout();
}
}
this is when enlarge animation is called :
I am using viewpager so i have to make padding negative to enlarge the card size :
ValueAnimator paddingAnimator = ValueAnimator.ofInt(20, -10).setDuration(400);
paddingAnimator.setInterpolator(new LinearInterpolator());
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int padding = (int) animation.getAnimatedValue();
view.setPadding(padding,
(int) DeviceUtils.convertDpToPx(50, v.getContext()), padding,
(int) DeviceUtils.convertDpToPx(50, v.getContext()));
view.requestLayout();
}
});
viewPagerItemSizeListener.onEnlarged();
EnlargeAnimation
enlargeAnimation =
new EnlargeAnimation(cardView, screenHeight, screenWidth);
enlargeAnimation.setDuration(400);
enlargeAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
paddingAnimator.start();
seeExampleText.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationEnd(Animation animation) {
stage.setVisibility(View.GONE);
cardEnlargedWidth = cardView.getLayoutParams().width;
cardEnlargedHeight = cardView.getLayoutParams().height;
crossContianer.setVisibility(View.VISIBLE);
detailTextContianer.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(enlargeAnimation);
I guess no one is reading it, but if something is confusing about variables let me know i will edit the answer.

animate a view from a particular point to fill whole screen and the re size it back to that point

I Know how to animate a view to whole screen and animate back to its original size. here is the link to do this Re sizing through animation
But the problem is that this technique works only when if my view is place at the start of the screen.
What i want is that if my view of height and width (50,50) is placed in center of screen below some button and i click that view it should animate to fill the whole screen and when clicked again it animates back to its original size (50,50)
If you're using LinearLayout, try to set AnimationListener for this animation and toggle the visibility of the button appropriately. Set button's visibility to View.GONE onAnimationStart when 'expanding' view and to View.VISIBLE onAnimationEnd when 'collapsing' it. Using RelativeLayout can be the solution for this problem too.
For animation:
public class ExpandCollapseViewAnimation extends Animation {
int targetWidth;
int targetHeight;
int initialWidth;
int initialHeight;
boolean expand;
View view;
public ExpandCollapseViewAnimation(View view, int targetWidth, int targetHeight ,boolean expand) {
this.view = view;
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
this.initialWidth = view.getWidth();
this.initialHeight = view.getHeight();
this.expand = expand;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newWidth, newHeight;
if (expand) {
newWidth = this.initialWidth
+ (int) ((this.targetWidth - this.initialWidth) * interpolatedTime);
newHeight = this.initialHeight
+ (int) ((this.targetHeight - this.initialHeight) * interpolatedTime);
} else {
newWidth = this.initialWidth
- (int) ((this.initialWidth - this.targetWidth) * interpolatedTime);
newHeight = this.initialHeight
- (int) ((this.initialHeight - this.targetHeight) * interpolatedTime);
}
view.getLayoutParams().width = newWidth;
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
And layout XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button android:id="#+id/btn"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:text="Test"/>
<View android:id="#+id/centered_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:clickable="true"
android:layout_gravity="center_horizontal"
android:background="#FF0000"/>
</LinearLayout>
This code works:
animatedView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!expand) expandView();
else collapseView();
}
});
private void expandView() {
expand = true;
animatedView.clearAnimation();
Display display = this.getWindowManager().getDefaultDisplay();
int maxWidth = display.getWidth();
int maxHeight = display.getHeight();
ExpandCollapseViewAnimation animation = new ExpandCollapseViewAnimation(animatedView, maxWidth,maxHeight, expand);
animation.setDuration(500);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
btn.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
});
animatedView.startAnimation(animation);
animatedView.invalidate();
}
private void collapseView() {
expand = false;
animatedView.clearAnimation();
ExpandCollapseViewAnimation animation = new ExpandCollapseViewAnimation(
animatedView, dpToPx(this, 50),dpToPx(this, 50), expand);
animation.setDuration(500);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
btn.setVisibility(View.VISIBLE);
}
});
animatedView.startAnimation(animation);
animatedView.invalidate();
}

How to make expandable panel layout in android? [duplicate]

Let's say I have a vertical linearLayout with :
[v1]
[v2]
By default v1 has visibily = GONE. I would like to show v1 with an expand animation and push down v2 at the same time.
I tried something like this:
Animation a = new Animation()
{
int initialHeight;
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final int newHeight = (int)(initialHeight * interpolatedTime);
v.getLayoutParams().height = newHeight;
v.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
initialHeight = height;
}
#Override
public boolean willChangeBounds() {
return true;
}
};
But with this solution, I have a blink when the animation starts. I think it's caused by v1 displaying full size before the animation is applied.
With javascript, this is one line of jQuery! Any simple way to do this with android?
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
I stumbled over the same problem today and I guess the real solution to this question is this
<LinearLayout android:id="#+id/container"
android:animateLayoutChanges="true"
...
/>
You will have to set this property for all topmost layouts, which are involved in the shift. If you now set the visibility of one layout to GONE, the other will take the space as the disappearing one is releasing it. There will be a default animation which is some kind of "fading out", but I think you can change this - but the last one I have not tested, for now.
If using this in a RecyclerView item, set the visibility of the view to expand/collapse in onBindViewHolder and call notifyItemChanged(position) to trigger the transformation.
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...
holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position)
}
}
If you perform expensive operations in onBindViewHolder you can optimize for partial changes using notifyItemChanged(position, payload)
private const val UPDATE_LIST_VISIBILITY = 1
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(UPDATE_LIST_VISIBILITY)) {
holder.list.visibility = data[position].listVisibility
} else {
onBindViewHolder(holder, position)
}
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...
holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position, UPDATE_LIST_VISIBILITY)
}
}
I was trying to do what I believe was a very similar animation and found an elegant solution. This code assumes that you are always going from 0->h or h->0 (h being the maximum height). The three constructor parameters are view = the view to be animated (in my case, a webview), targetHeight = the maximum height of the view, and down = a boolean which specifies the direction (true = expanding, false = collapsing).
public class DropDownAnim extends Animation {
private final int targetHeight;
private final View view;
private final boolean down;
public DropDownAnim(View view, int targetHeight, boolean down) {
this.view = view;
this.targetHeight = targetHeight;
this.down = down;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
if (down) {
newHeight = (int) (targetHeight * interpolatedTime);
} else {
newHeight = (int) (targetHeight * (1 - interpolatedTime));
}
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I took #LenaYan 's solution that didn't work properly
to me (because it was transforming the View to a 0 height view before collapsing and/or expanding) and made some changes.
Now it works great, by taking the View's previous height and start expanding with this size. Collapsing is the same.
You can simply copy and paste the code below:
public static void expand(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
public static void collapse(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
Usage:
//Expanding the View
expand(yourView, 2000, 200);
// Collapsing the View
collapse(yourView, 2000, 100);
Easy enough!
Thanks LenaYan for the initial code!
An alternative is to use a scale animation with the following scaling factors for expanding:
ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1);
and for collapsing:
ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
#Tom Esterez's answer, but updated to use view.measure() properly per Android getMeasuredHeight returns wrong values !
// http://easings.net/
Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
public static Animation expand(final View view) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = view.getMeasuredHeight();
// Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead.
view.getLayoutParams().height = 1;
view.setVisibility(View.VISIBLE);
Animation animation = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
animation.setInterpolator(easeInOutQuart);
animation.setDuration(computeDurationFromHeight(view));
view.startAnimation(animation);
return animation;
}
public static Animation collapse(final View view) {
final int initialHeight = view.getMeasuredHeight();
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
view.setVisibility(View.GONE);
} else {
view.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
view.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setInterpolator(easeInOutQuart);
int durationMillis = computeDurationFromHeight(view);
a.setDuration(durationMillis);
view.startAnimation(a);
return a;
}
private static int computeDurationFromHeight(View view) {
// 1dp/ms * multiplier
return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density);
}
Ok, I just found a VERY ugly solution :
public static Animation expand(final View v, Runnable onEnd) {
try {
Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
m.setAccessible(true);
m.invoke(
v,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(((View)v.getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST)
);
} catch (Exception e){
Log.e("test", "", e);
}
final int initialHeight = v.getMeasuredHeight();
Log.d("test", "initialHeight="+initialHeight);
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final int newHeight = (int)(initialHeight * interpolatedTime);
v.getLayoutParams().height = newHeight;
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration(5000);
v.startAnimation(a);
return a;
}
Feel free to propose a better solution !
public static void expand(final View v, int duration, int targetHeight) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
public static void collapse(final View v, int duration, int targetHeight) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
If you don't want to expand or collapse all the way - here is a simple HeightAnimation -
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class HeightAnimation extends Animation {
protected final int originalHeight;
protected final View view;
protected float perValue;
public HeightAnimation(View view, int fromHeight, int toHeight) {
this.view = view;
this.originalHeight = fromHeight;
this.perValue = (toHeight - fromHeight);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Usage:
HeightAnimation heightAnim = new HeightAnimation(view, view.getHeight(), viewPager.getHeight() - otherView.getHeight());
heightAnim.setDuration(1000);
view.startAnimation(heightAnim);
I adapted the currently accepted answer by Tom Esterez, which worked but had a choppy and not very smooth animation. My solution basically replaces the Animation with a ValueAnimator, which can be fitted with an Interpolator of your choice to achieve various effects such as overshoot, bounce, accelerate, etc.
This solution works great with views that have a dynamic height (i.e. using WRAP_CONTENT), as it first measures the actual required height and then animates to that height.
public static void expand(final View v) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
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);
ValueAnimator va = ValueAnimator.ofInt(1, targetHeight);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
v.requestLayout();
}
});
va.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
#Override public void onAnimationStart(Animator animation) {}
#Override public void onAnimationCancel(Animator animation) {}
#Override public void onAnimationRepeat(Animator animation) {}
});
va.setDuration(300);
va.setInterpolator(new OvershootInterpolator());
va.start();
}
public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();
ValueAnimator va = ValueAnimator.ofInt(initialHeight, 0);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
v.requestLayout();
}
});
va.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
v.setVisibility(View.GONE);
}
#Override public void onAnimationStart(Animator animation) {}
#Override public void onAnimationCancel(Animator animation) {}
#Override public void onAnimationRepeat(Animator animation) {}
});
va.setDuration(300);
va.setInterpolator(new DecelerateInterpolator());
va.start();
}
You then simply call expand( myView ); or collapse( myView );.
Making use of Kotlin Extension Functions this is tested and shortest answer
Just call animateVisibility(expand/collapse) on any View.
fun View.animateVisibility(setVisible: Boolean) {
if (setVisible) expand(this) else collapse(this)
}
private fun expand(view: View) {
view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val initialHeight = 0
val targetHeight = view.measuredHeight
// Older versions of Android (pre API 21) cancel animations for views with a height of 0.
//v.getLayoutParams().height = 1;
view.layoutParams.height = 0
view.visibility = View.VISIBLE
animateView(view, initialHeight, targetHeight)
}
private fun collapse(view: View) {
val initialHeight = view.measuredHeight
val targetHeight = 0
animateView(view, initialHeight, targetHeight)
}
private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
valueAnimator.addUpdateListener { animation ->
v.layoutParams.height = animation.animatedValue as Int
v.requestLayout()
}
valueAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator) {
v.layoutParams.height = targetHeight
}
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
valueAnimator.duration = 300
valueAnimator.interpolator = DecelerateInterpolator()
valueAnimator.start()
}
Adding to Tom Esterez's excellent answer and Erik B's excellent update to it, I thought I'd post my own take, compacting the expand and contract methods into one. This way, you could for example have an action like this...
button.setOnClickListener(v -> expandCollapse(view));
... which calls the method below and letting it figure out what to do after each onClick()...
public static void expandCollapse(View view) {
boolean expand = view.getVisibility() == View.GONE;
Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
view.measure(
View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
int height = view.getMeasuredHeight();
int duration = (int) (height/view.getContext().getResources().getDisplayMetrics().density);
Animation animation = new Animation() {
#Override protected void applyTransformation(float interpolatedTime, Transformation t) {
if (expand) {
view.getLayoutParams().height = 1;
view.setVisibility(View.VISIBLE);
if (interpolatedTime == 1) {
view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
view.getLayoutParams().height = (int) (height * interpolatedTime);
}
view.requestLayout();
} else {
if (interpolatedTime == 1) {
view.setVisibility(View.GONE);
} else {
view.getLayoutParams().height = height - (int) (height * interpolatedTime);
view.requestLayout();
}
}
}
#Override public boolean willChangeBounds() {
return true;
}
};
animation.setInterpolator(easeInOutQuart);
animation.setDuration(duration);
view.startAnimation(animation);
}
For Smooth animation please use Handler with run method.....And Enjoy Expand /Collapse animation
class AnimUtils{
public void expand(final View v) {
int ANIMATION_DURATION=500;//in milisecond
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;
}
};
// 1dp/ms
a.setDuration(ANIMATION_DURATION);
// a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public 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;
}
};
// 1dp/ms
a.setDuration(ANIMATION_DURATION);
// a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
}
And Call using this code:
private void setAnimationOnView(final View inactive ) {
//I am applying expand and collapse on this TextView ...You can use your view
//for expand animation
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
new AnimationUtililty().expand(inactive);
}
}, 1000);
//For collapse
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
new AnimationUtililty().collapse(inactive);
//inactive.setVisibility(View.GONE);
}
}, 8000);
}
Other solution is:
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);
}
I would like to add something to the very helpful answer above. If you don't know the height you'll end up with since your views .getHeight() returns 0 you can do the following to get the height:
contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
int finalHeight = view.getMeasuredHeight();
Where DUMMY_HIGH_DIMENSIONS is the width/height (in pixels) your view is constrained to ... having this a huge number is reasonable when the view is encapsulated with a ScrollView.
This is a snippet that I used to resize the width of a view (LinearLayout) with animation.
The code is supposed to do expand or shrink according the target size. If you want a fill_parent width, you will have to pass the parent .getMeasuredWidth as target width while setting the flag to true.
Hope it helps some of you.
public class WidthResizeAnimation extends Animation {
int targetWidth;
int originaltWidth;
View view;
boolean expand;
int newWidth = 0;
boolean fillParent;
public WidthResizeAnimation(View view, int targetWidth, boolean fillParent) {
this.view = view;
this.originaltWidth = this.view.getMeasuredWidth();
this.targetWidth = targetWidth;
newWidth = originaltWidth;
if (originaltWidth > targetWidth) {
expand = false;
} else {
expand = true;
}
this.fillParent = fillParent;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (expand && newWidth < targetWidth) {
newWidth = (int) (newWidth + (targetWidth - newWidth) * interpolatedTime);
}
if (!expand && newWidth > targetWidth) {
newWidth = (int) (newWidth - (newWidth - targetWidth) * interpolatedTime);
}
if (fillParent && interpolatedTime == 1.0) {
view.getLayoutParams().width = -1;
} else {
view.getLayoutParams().width = newWidth;
}
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Yes, I agreed with the above comments. And indeed, it does seem like the right (or at least the easiest?) thing to do is to specify (in XML) an initial layout height of "0px" -- and then you can pass in another argument for "toHeight" (i.e. the "final height") to the constructor of your custom Animation sub-class, e.g. in the example above, it would look something like so:
public DropDownAnim( View v, int toHeight ) { ... }
Anyways, hope that helps! :)
combined solutions from #Tom Esterez and #Geraldo Neto
public static void expandOrCollapseView(View v,boolean expand){
if(expand){
v.measure(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
final int targetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(500);
valueAnimator.start();
}
else
{
final int initialHeight = v.getMeasuredHeight();
ValueAnimator valueAnimator = ValueAnimator.ofInt(initialHeight,0);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
if((int)animation.getAnimatedValue() == 0)
v.setVisibility(View.GONE);
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(500);
valueAnimator.start();
}
}
//sample usage
expandOrCollapseView((Your ViewGroup),(Your ViewGroup).getVisibility()!=View.VISIBLE);
Here is my solution. I think it is simpler. It only expands the view but can easy be extended.
public class WidthExpandAnimation extends Animation
{
int _targetWidth;
View _view;
public WidthExpandAnimation(View view)
{
_view = view;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
if (interpolatedTime < 1.f)
{
int newWidth = (int) (_targetWidth * interpolatedTime);
_view.layout(_view.getLeft(), _view.getTop(),
_view.getLeft() + newWidth, _view.getBottom());
}
else
_view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
{
super.initialize(width, height, parentWidth, parentHeight);
_targetWidth = width;
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I think the easiest solution is to set android:animateLayoutChanges="true" to your LinearLayout and then just show/hide view by seting its visibility. Works like a charm, but you have no controll on the animation duration
You can use Transition or Animator that changes visibility of section to be expanded/collapsed, or ConstraintSet with different layouts.
Easiest one is to use motionLayout with 2 different layouts and constraintSets to change from one layout to another on button click. You can change between layouts with
val constraintSet = ConstraintSet()
constraintSet.clone(this, R.layout.layout_collapsed)
val transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator(1.0f)
transition.setDuration(300)
TransitionManager.beginDelayedTransition(YOUR_VIEW, transition)
constraintSet.applyTo(YOUR_VIEW)
With Transition api
RotateX.kt
I created the one in gif using Transitions api that change rotationX.
class RotateX : Transition {
#Keep
constructor() : super()
#Keep
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun getTransitionProperties(): Array<String> {
return TRANSITION_PROPERTIES
}
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) return null
val startRotation = startValues.values[PROP_ROTATION] as Float
val endRotation = endValues.values[PROP_ROTATION] as Float
if (startRotation == endRotation) return null
val view = endValues.view
// ensure the pivot is set
view.pivotX = view.width / 2f
view.pivotY = view.height / 2f
return ObjectAnimator.ofFloat(view, View.ROTATION_X, startRotation, endRotation)
}
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
if (view == null || view.width <= 0 || view.height <= 0) return
transitionValues.values[PROP_ROTATION] = view.rotationX
}
companion object {
private const val PROP_ROTATION = "iosched:rotate:rotation"
private val TRANSITION_PROPERTIES = arrayOf(PROP_ROTATION)
}
}
create xml file that targets expand button
<?xml version="1.0" encoding="utf-8"?>
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:interpolator/fast_out_slow_in">
<transition class="com.smarttoolfactory.tutorial3_1transitions.transition.RotateX">
<targets>
<target android:targetId="#id/ivExpand" />
</targets>
</transition>
<autoTransition android:duration="200" />
</transitionSet>
My layout to be expanded or collapsed
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="2dp"
android:clickable="true"
android:focusable="true"
android:transitionName="#string/transition_card_view"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="false">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/avatar_1_raster" />
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivExpand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_baseline_expand_more_24" />
<TextView
android:id="#+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:text="Some Title"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/ivAvatar"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="#+id/tvTitle"
app:layout_constraintTop_toBottomOf="#id/tvTitle"
tools:text="Tuesday 7pm" />
<TextView
android:id="#+id/tvBody"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:lines="1"
android:text="#string/bacon_ipsum_short"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/ivAvatar"
app:layout_constraintTop_toBottomOf="#id/tvDate" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
android:overScrollMode="never"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvBody"
tools:listitem="#layout/item_image_destination" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
And set up visibility of items to collapse or expand
private fun setUpExpandedStatus() {
if (isExpanded) {
binding.recyclerView.visibility = View.VISIBLE
binding.ivExpand.rotationX = 180f
} else {
binding.recyclerView.visibility = View.GONE
binding.ivExpand.rotationX = 0f
}
}
And start transition with
val transition = TransitionInflater.from(itemView.context)
.inflateTransition(R.transition.icon_expand_toggle)
TransitionManager.beginDelayedTransition(parent, transition)
isExpanded = !isExpanded
setUpExpandedStatus()
I created animation and transitions samples including the one on the gif, you can check them out there.
You are on the right track. Make sure you have v1 set to have a layout height of zero right before the animation starts. You want to initialize your setup to look like the first frame of the animation before starting the animation.
This was my solution, my ImageView grows from 100% to 200% and return to his original size, using two animation files inside res/anim/ folder
anim_grow.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>
anim_shrink.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator">
<scale
android:fromXScale="2.0"
android:toXScale="1.0"
android:fromYScale="2.0"
android:toYScale="1.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>
Send an ImageView to my method setAnimationGrowShrink()
ImageView img1 = (ImageView)findViewById(R.id.image1);
setAnimationGrowShrink(img1);
setAnimationGrowShrink() method:
private void setAnimationGrowShrink(final ImageView imgV){
final Animation animationEnlarge = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_grow);
final Animation animationShrink = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_shrink);
imgV.startAnimation(animationEnlarge);
animationEnlarge.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationShrink);
}
});
animationShrink.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationEnlarge);
}
});
}
This is a proper working solution, I have tested it:
Exapnd:
private void expand(View v) {
v.setVisibility(View.VISIBLE);
v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
final int targetHeight = v.getMeasuredHeight();
mAnimator = slideAnimator(0, targetHeight);
mAnimator.setDuration(800);
mAnimator.start();
}
Collapse:
private void collapse(View v) {
int finalHeight = v.getHeight();
mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
llDescp.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
Value Animator:
private ValueAnimator slideAnimator(int start, int end) {
ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
layoutParams.height = value;
v.setLayoutParams(layoutParams);
}
});
return mAnimator;
}
View v is the view to be animated, PARENT_VIEW is the container view containing the view.
Based on solutions by #Tom Esterez and #Seth Nelson (top 2) I simlified them. As well as original solutions it doesn't depend on Developer options (animation settings).
private void resizeWithAnimation(final View view, int duration, final int targetHeight) {
final int initialHeight = view.getMeasuredHeight();
final int distance = targetHeight - initialHeight;
view.setVisibility(View.VISIBLE);
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1 && targetHeight == 0) {
view.setVisibility(View.GONE);
}
view.getLayoutParams().height = (int) (initialHeight + distance * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration(duration);
view.startAnimation(a);
}
This is really simple with droidQuery. For starts, consider this layout:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="#+id/v1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 1" />
</LinearLayout>
<LinearLayout
android:id="#+id/v2"
android:layout_width="wrap_content"
android:layout_height="0dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 3" />
</LinearLayout>
</LinearLayout>
We can animate the height to the desired value - say 100dp - using the following code:
//convert 100dp to pixel value
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
Then use droidQuery to animate. The simplest way is with this:
$.animate("{ height: " + height + "}", new AnimationOptions());
To make the animation more appealing, consider adding an easing:
$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE));
You can also change the duration on AnimationOptions using the duration() method, or handle what happens when the animation ends. For a complex example, try:
$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE)
.duration(1000)
.complete(new Function() {
#Override
public void invoke($ d, Object... args) {
$.toast(context, "finished", Toast.LENGTH_SHORT);
}
}));
Best solution for expand/collapse view's:
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
View view = buttonView.getId() == R.id.tb_search ? fSearch : layoutSettings;
transform(view, 200, isChecked
? ViewGroup.LayoutParams.WRAP_CONTENT
: 0);
}
public static void transform(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
v.setVisibility(View.VISIBLE);
ValueAnimator animator;
if (targetHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
animator = ValueAnimator.ofInt(prevHeight, v.getMeasuredHeight());
} else {
animator = ValueAnimator.ofInt(prevHeight, targetHeight);
}
animator.addUpdateListener(animation -> {
v.getLayoutParams().height = (animation.getAnimatedFraction() == 1.0f)
? targetHeight
: (int) animation.getAnimatedValue();
v.requestLayout();
});
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(duration);
animator.start();
}
You can use a ViewPropertyAnimator with a slight twist. To collapse, scale the view to a height of 1 pixel, then hide it. To expand, show it, then expand it to its height.
private void collapse(final View view) {
view.setPivotY(0);
view.animate().scaleY(1/view.getHeight()).setDuration(1000).withEndAction(new Runnable() {
#Override public void run() {
view.setVisibility(GONE);
}
});
}
private void expand(View view, int height) {
float scaleFactor = height / view.getHeight();
view.setVisibility(VISIBLE);
view.setPivotY(0);
view.animate().scaleY(scaleFactor).setDuration(1000);
}
The pivot tells the view where to scale from, default is in the middle. The duration is optional (default = 1000). You can also set the interpolator to use, like .setInterpolator(new AccelerateDecelerateInterpolator())
I created version in which you don't need to specify layout height, hence it's a lot easier and cleaner to use. The solution is to get the height in the first frame of the animation (it's available at that moment, at least during my tests). This way you can provide a View with an arbitrary height and bottom margin.
There's also one little hack in the constructor - the bottom margin is set to -10000 so that the view stays hidden before the transformation (prevents flicker).
public class ExpandAnimation extends Animation {
private View mAnimatedView;
private ViewGroup.MarginLayoutParams mViewLayoutParams;
private int mMarginStart, mMarginEnd;
public ExpandAnimation(View view) {
mAnimatedView = view;
mViewLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
mMarginEnd = mViewLayoutParams.bottomMargin;
mMarginStart = -10000; //hide before viewing by settings very high negative bottom margin (hack, but works nicely)
mViewLayoutParams.bottomMargin = mMarginStart;
mAnimatedView.setLayoutParams(mViewLayoutParams);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//view height is already known when the animation starts
if(interpolatedTime==0){
mMarginStart = -mAnimatedView.getHeight();
}
mViewLayoutParams.bottomMargin = (int)((mMarginEnd-mMarginStart) * interpolatedTime)+mMarginStart;
mAnimatedView.setLayoutParams(mViewLayoutParams);
}
}
Use ValueAnimator:
ValueAnimator expandAnimation = ValueAnimator.ofInt(mainView.getHeight(), 400);
expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animation) {
int height = (Integer) animation.getAnimatedValue();
RelativeLayout.LayoutParams lp = (LayoutParams) mainView.getLayoutParams();
lp.height = height;
}
});
expandAnimation.setDuration(500);
expandAnimation.start();
public static void slide(View v, int speed, int pos) {
v.animate().setDuration(speed);
v.animate().translationY(pos);
v.animate().start();
}
// slide down
slide(yourView, 250, yourViewHeight);
// slide up
slide(yourView, 250, 0);

Android expandable pull down bar - SlidingDrawer like [duplicate]

Let's say I have a vertical linearLayout with :
[v1]
[v2]
By default v1 has visibily = GONE. I would like to show v1 with an expand animation and push down v2 at the same time.
I tried something like this:
Animation a = new Animation()
{
int initialHeight;
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final int newHeight = (int)(initialHeight * interpolatedTime);
v.getLayoutParams().height = newHeight;
v.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
initialHeight = height;
}
#Override
public boolean willChangeBounds() {
return true;
}
};
But with this solution, I have a blink when the animation starts. I think it's caused by v1 displaying full size before the animation is applied.
With javascript, this is one line of jQuery! Any simple way to do this with android?
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
I stumbled over the same problem today and I guess the real solution to this question is this
<LinearLayout android:id="#+id/container"
android:animateLayoutChanges="true"
...
/>
You will have to set this property for all topmost layouts, which are involved in the shift. If you now set the visibility of one layout to GONE, the other will take the space as the disappearing one is releasing it. There will be a default animation which is some kind of "fading out", but I think you can change this - but the last one I have not tested, for now.
If using this in a RecyclerView item, set the visibility of the view to expand/collapse in onBindViewHolder and call notifyItemChanged(position) to trigger the transformation.
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...
holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position)
}
}
If you perform expensive operations in onBindViewHolder you can optimize for partial changes using notifyItemChanged(position, payload)
private const val UPDATE_LIST_VISIBILITY = 1
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(UPDATE_LIST_VISIBILITY)) {
holder.list.visibility = data[position].listVisibility
} else {
onBindViewHolder(holder, position)
}
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...
holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position, UPDATE_LIST_VISIBILITY)
}
}
I was trying to do what I believe was a very similar animation and found an elegant solution. This code assumes that you are always going from 0->h or h->0 (h being the maximum height). The three constructor parameters are view = the view to be animated (in my case, a webview), targetHeight = the maximum height of the view, and down = a boolean which specifies the direction (true = expanding, false = collapsing).
public class DropDownAnim extends Animation {
private final int targetHeight;
private final View view;
private final boolean down;
public DropDownAnim(View view, int targetHeight, boolean down) {
this.view = view;
this.targetHeight = targetHeight;
this.down = down;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
if (down) {
newHeight = (int) (targetHeight * interpolatedTime);
} else {
newHeight = (int) (targetHeight * (1 - interpolatedTime));
}
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I took #LenaYan 's solution that didn't work properly
to me (because it was transforming the View to a 0 height view before collapsing and/or expanding) and made some changes.
Now it works great, by taking the View's previous height and start expanding with this size. Collapsing is the same.
You can simply copy and paste the code below:
public static void expand(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
public static void collapse(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
Usage:
//Expanding the View
expand(yourView, 2000, 200);
// Collapsing the View
collapse(yourView, 2000, 100);
Easy enough!
Thanks LenaYan for the initial code!
An alternative is to use a scale animation with the following scaling factors for expanding:
ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1);
and for collapsing:
ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
#Tom Esterez's answer, but updated to use view.measure() properly per Android getMeasuredHeight returns wrong values !
// http://easings.net/
Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
public static Animation expand(final View view) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = view.getMeasuredHeight();
// Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead.
view.getLayoutParams().height = 1;
view.setVisibility(View.VISIBLE);
Animation animation = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
animation.setInterpolator(easeInOutQuart);
animation.setDuration(computeDurationFromHeight(view));
view.startAnimation(animation);
return animation;
}
public static Animation collapse(final View view) {
final int initialHeight = view.getMeasuredHeight();
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
view.setVisibility(View.GONE);
} else {
view.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
view.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setInterpolator(easeInOutQuart);
int durationMillis = computeDurationFromHeight(view);
a.setDuration(durationMillis);
view.startAnimation(a);
return a;
}
private static int computeDurationFromHeight(View view) {
// 1dp/ms * multiplier
return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density);
}
Ok, I just found a VERY ugly solution :
public static Animation expand(final View v, Runnable onEnd) {
try {
Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
m.setAccessible(true);
m.invoke(
v,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(((View)v.getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST)
);
} catch (Exception e){
Log.e("test", "", e);
}
final int initialHeight = v.getMeasuredHeight();
Log.d("test", "initialHeight="+initialHeight);
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final int newHeight = (int)(initialHeight * interpolatedTime);
v.getLayoutParams().height = newHeight;
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration(5000);
v.startAnimation(a);
return a;
}
Feel free to propose a better solution !
public static void expand(final View v, int duration, int targetHeight) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
public static void collapse(final View v, int duration, int targetHeight) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(duration);
valueAnimator.start();
}
If you don't want to expand or collapse all the way - here is a simple HeightAnimation -
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class HeightAnimation extends Animation {
protected final int originalHeight;
protected final View view;
protected float perValue;
public HeightAnimation(View view, int fromHeight, int toHeight) {
this.view = view;
this.originalHeight = fromHeight;
this.perValue = (toHeight - fromHeight);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Usage:
HeightAnimation heightAnim = new HeightAnimation(view, view.getHeight(), viewPager.getHeight() - otherView.getHeight());
heightAnim.setDuration(1000);
view.startAnimation(heightAnim);
I adapted the currently accepted answer by Tom Esterez, which worked but had a choppy and not very smooth animation. My solution basically replaces the Animation with a ValueAnimator, which can be fitted with an Interpolator of your choice to achieve various effects such as overshoot, bounce, accelerate, etc.
This solution works great with views that have a dynamic height (i.e. using WRAP_CONTENT), as it first measures the actual required height and then animates to that height.
public static void expand(final View v) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
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);
ValueAnimator va = ValueAnimator.ofInt(1, targetHeight);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
v.requestLayout();
}
});
va.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
#Override public void onAnimationStart(Animator animation) {}
#Override public void onAnimationCancel(Animator animation) {}
#Override public void onAnimationRepeat(Animator animation) {}
});
va.setDuration(300);
va.setInterpolator(new OvershootInterpolator());
va.start();
}
public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();
ValueAnimator va = ValueAnimator.ofInt(initialHeight, 0);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
v.requestLayout();
}
});
va.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
v.setVisibility(View.GONE);
}
#Override public void onAnimationStart(Animator animation) {}
#Override public void onAnimationCancel(Animator animation) {}
#Override public void onAnimationRepeat(Animator animation) {}
});
va.setDuration(300);
va.setInterpolator(new DecelerateInterpolator());
va.start();
}
You then simply call expand( myView ); or collapse( myView );.
Making use of Kotlin Extension Functions this is tested and shortest answer
Just call animateVisibility(expand/collapse) on any View.
fun View.animateVisibility(setVisible: Boolean) {
if (setVisible) expand(this) else collapse(this)
}
private fun expand(view: View) {
view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val initialHeight = 0
val targetHeight = view.measuredHeight
// Older versions of Android (pre API 21) cancel animations for views with a height of 0.
//v.getLayoutParams().height = 1;
view.layoutParams.height = 0
view.visibility = View.VISIBLE
animateView(view, initialHeight, targetHeight)
}
private fun collapse(view: View) {
val initialHeight = view.measuredHeight
val targetHeight = 0
animateView(view, initialHeight, targetHeight)
}
private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
valueAnimator.addUpdateListener { animation ->
v.layoutParams.height = animation.animatedValue as Int
v.requestLayout()
}
valueAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator) {
v.layoutParams.height = targetHeight
}
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
valueAnimator.duration = 300
valueAnimator.interpolator = DecelerateInterpolator()
valueAnimator.start()
}
Adding to Tom Esterez's excellent answer and Erik B's excellent update to it, I thought I'd post my own take, compacting the expand and contract methods into one. This way, you could for example have an action like this...
button.setOnClickListener(v -> expandCollapse(view));
... which calls the method below and letting it figure out what to do after each onClick()...
public static void expandCollapse(View view) {
boolean expand = view.getVisibility() == View.GONE;
Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);
view.measure(
View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
int height = view.getMeasuredHeight();
int duration = (int) (height/view.getContext().getResources().getDisplayMetrics().density);
Animation animation = new Animation() {
#Override protected void applyTransformation(float interpolatedTime, Transformation t) {
if (expand) {
view.getLayoutParams().height = 1;
view.setVisibility(View.VISIBLE);
if (interpolatedTime == 1) {
view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
view.getLayoutParams().height = (int) (height * interpolatedTime);
}
view.requestLayout();
} else {
if (interpolatedTime == 1) {
view.setVisibility(View.GONE);
} else {
view.getLayoutParams().height = height - (int) (height * interpolatedTime);
view.requestLayout();
}
}
}
#Override public boolean willChangeBounds() {
return true;
}
};
animation.setInterpolator(easeInOutQuart);
animation.setDuration(duration);
view.startAnimation(animation);
}
For Smooth animation please use Handler with run method.....And Enjoy Expand /Collapse animation
class AnimUtils{
public void expand(final View v) {
int ANIMATION_DURATION=500;//in milisecond
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;
}
};
// 1dp/ms
a.setDuration(ANIMATION_DURATION);
// a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public 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;
}
};
// 1dp/ms
a.setDuration(ANIMATION_DURATION);
// a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
}
And Call using this code:
private void setAnimationOnView(final View inactive ) {
//I am applying expand and collapse on this TextView ...You can use your view
//for expand animation
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
new AnimationUtililty().expand(inactive);
}
}, 1000);
//For collapse
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
new AnimationUtililty().collapse(inactive);
//inactive.setVisibility(View.GONE);
}
}, 8000);
}
Other solution is:
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);
}
I would like to add something to the very helpful answer above. If you don't know the height you'll end up with since your views .getHeight() returns 0 you can do the following to get the height:
contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
int finalHeight = view.getMeasuredHeight();
Where DUMMY_HIGH_DIMENSIONS is the width/height (in pixels) your view is constrained to ... having this a huge number is reasonable when the view is encapsulated with a ScrollView.
This is a snippet that I used to resize the width of a view (LinearLayout) with animation.
The code is supposed to do expand or shrink according the target size. If you want a fill_parent width, you will have to pass the parent .getMeasuredWidth as target width while setting the flag to true.
Hope it helps some of you.
public class WidthResizeAnimation extends Animation {
int targetWidth;
int originaltWidth;
View view;
boolean expand;
int newWidth = 0;
boolean fillParent;
public WidthResizeAnimation(View view, int targetWidth, boolean fillParent) {
this.view = view;
this.originaltWidth = this.view.getMeasuredWidth();
this.targetWidth = targetWidth;
newWidth = originaltWidth;
if (originaltWidth > targetWidth) {
expand = false;
} else {
expand = true;
}
this.fillParent = fillParent;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (expand && newWidth < targetWidth) {
newWidth = (int) (newWidth + (targetWidth - newWidth) * interpolatedTime);
}
if (!expand && newWidth > targetWidth) {
newWidth = (int) (newWidth - (newWidth - targetWidth) * interpolatedTime);
}
if (fillParent && interpolatedTime == 1.0) {
view.getLayoutParams().width = -1;
} else {
view.getLayoutParams().width = newWidth;
}
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Yes, I agreed with the above comments. And indeed, it does seem like the right (or at least the easiest?) thing to do is to specify (in XML) an initial layout height of "0px" -- and then you can pass in another argument for "toHeight" (i.e. the "final height") to the constructor of your custom Animation sub-class, e.g. in the example above, it would look something like so:
public DropDownAnim( View v, int toHeight ) { ... }
Anyways, hope that helps! :)
combined solutions from #Tom Esterez and #Geraldo Neto
public static void expandOrCollapseView(View v,boolean expand){
if(expand){
v.measure(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
final int targetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(targetHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(500);
valueAnimator.start();
}
else
{
final int initialHeight = v.getMeasuredHeight();
ValueAnimator valueAnimator = ValueAnimator.ofInt(initialHeight,0);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
v.getLayoutParams().height = (int) animation.getAnimatedValue();
v.requestLayout();
if((int)animation.getAnimatedValue() == 0)
v.setVisibility(View.GONE);
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(500);
valueAnimator.start();
}
}
//sample usage
expandOrCollapseView((Your ViewGroup),(Your ViewGroup).getVisibility()!=View.VISIBLE);
Here is my solution. I think it is simpler. It only expands the view but can easy be extended.
public class WidthExpandAnimation extends Animation
{
int _targetWidth;
View _view;
public WidthExpandAnimation(View view)
{
_view = view;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
if (interpolatedTime < 1.f)
{
int newWidth = (int) (_targetWidth * interpolatedTime);
_view.layout(_view.getLeft(), _view.getTop(),
_view.getLeft() + newWidth, _view.getBottom());
}
else
_view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
{
super.initialize(width, height, parentWidth, parentHeight);
_targetWidth = width;
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I think the easiest solution is to set android:animateLayoutChanges="true" to your LinearLayout and then just show/hide view by seting its visibility. Works like a charm, but you have no controll on the animation duration
You can use Transition or Animator that changes visibility of section to be expanded/collapsed, or ConstraintSet with different layouts.
Easiest one is to use motionLayout with 2 different layouts and constraintSets to change from one layout to another on button click. You can change between layouts with
val constraintSet = ConstraintSet()
constraintSet.clone(this, R.layout.layout_collapsed)
val transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator(1.0f)
transition.setDuration(300)
TransitionManager.beginDelayedTransition(YOUR_VIEW, transition)
constraintSet.applyTo(YOUR_VIEW)
With Transition api
RotateX.kt
I created the one in gif using Transitions api that change rotationX.
class RotateX : Transition {
#Keep
constructor() : super()
#Keep
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun getTransitionProperties(): Array<String> {
return TRANSITION_PROPERTIES
}
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) return null
val startRotation = startValues.values[PROP_ROTATION] as Float
val endRotation = endValues.values[PROP_ROTATION] as Float
if (startRotation == endRotation) return null
val view = endValues.view
// ensure the pivot is set
view.pivotX = view.width / 2f
view.pivotY = view.height / 2f
return ObjectAnimator.ofFloat(view, View.ROTATION_X, startRotation, endRotation)
}
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
if (view == null || view.width <= 0 || view.height <= 0) return
transitionValues.values[PROP_ROTATION] = view.rotationX
}
companion object {
private const val PROP_ROTATION = "iosched:rotate:rotation"
private val TRANSITION_PROPERTIES = arrayOf(PROP_ROTATION)
}
}
create xml file that targets expand button
<?xml version="1.0" encoding="utf-8"?>
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:interpolator/fast_out_slow_in">
<transition class="com.smarttoolfactory.tutorial3_1transitions.transition.RotateX">
<targets>
<target android:targetId="#id/ivExpand" />
</targets>
</transition>
<autoTransition android:duration="200" />
</transitionSet>
My layout to be expanded or collapsed
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.card.MaterialCardView
android:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="2dp"
android:clickable="true"
android:focusable="true"
android:transitionName="#string/transition_card_view"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="false">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/avatar_1_raster" />
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivExpand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_baseline_expand_more_24" />
<TextView
android:id="#+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:text="Some Title"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="#+id/ivAvatar"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="#+id/tvTitle"
app:layout_constraintTop_toBottomOf="#id/tvTitle"
tools:text="Tuesday 7pm" />
<TextView
android:id="#+id/tvBody"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:lines="1"
android:text="#string/bacon_ipsum_short"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/ivAvatar"
app:layout_constraintTop_toBottomOf="#id/tvDate" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
android:overScrollMode="never"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvBody"
tools:listitem="#layout/item_image_destination" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
And set up visibility of items to collapse or expand
private fun setUpExpandedStatus() {
if (isExpanded) {
binding.recyclerView.visibility = View.VISIBLE
binding.ivExpand.rotationX = 180f
} else {
binding.recyclerView.visibility = View.GONE
binding.ivExpand.rotationX = 0f
}
}
And start transition with
val transition = TransitionInflater.from(itemView.context)
.inflateTransition(R.transition.icon_expand_toggle)
TransitionManager.beginDelayedTransition(parent, transition)
isExpanded = !isExpanded
setUpExpandedStatus()
I created animation and transitions samples including the one on the gif, you can check them out there.
You are on the right track. Make sure you have v1 set to have a layout height of zero right before the animation starts. You want to initialize your setup to look like the first frame of the animation before starting the animation.
This was my solution, my ImageView grows from 100% to 200% and return to his original size, using two animation files inside res/anim/ folder
anim_grow.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>
anim_shrink.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator">
<scale
android:fromXScale="2.0"
android:toXScale="1.0"
android:fromYScale="2.0"
android:toYScale="1.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>
Send an ImageView to my method setAnimationGrowShrink()
ImageView img1 = (ImageView)findViewById(R.id.image1);
setAnimationGrowShrink(img1);
setAnimationGrowShrink() method:
private void setAnimationGrowShrink(final ImageView imgV){
final Animation animationEnlarge = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_grow);
final Animation animationShrink = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_shrink);
imgV.startAnimation(animationEnlarge);
animationEnlarge.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationShrink);
}
});
animationShrink.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationEnlarge);
}
});
}
This is a proper working solution, I have tested it:
Exapnd:
private void expand(View v) {
v.setVisibility(View.VISIBLE);
v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
final int targetHeight = v.getMeasuredHeight();
mAnimator = slideAnimator(0, targetHeight);
mAnimator.setDuration(800);
mAnimator.start();
}
Collapse:
private void collapse(View v) {
int finalHeight = v.getHeight();
mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
llDescp.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
Value Animator:
private ValueAnimator slideAnimator(int start, int end) {
ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
layoutParams.height = value;
v.setLayoutParams(layoutParams);
}
});
return mAnimator;
}
View v is the view to be animated, PARENT_VIEW is the container view containing the view.
Based on solutions by #Tom Esterez and #Seth Nelson (top 2) I simlified them. As well as original solutions it doesn't depend on Developer options (animation settings).
private void resizeWithAnimation(final View view, int duration, final int targetHeight) {
final int initialHeight = view.getMeasuredHeight();
final int distance = targetHeight - initialHeight;
view.setVisibility(View.VISIBLE);
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1 && targetHeight == 0) {
view.setVisibility(View.GONE);
}
view.getLayoutParams().height = (int) (initialHeight + distance * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration(duration);
view.startAnimation(a);
}
This is really simple with droidQuery. For starts, consider this layout:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="#+id/v1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 1" />
</LinearLayout>
<LinearLayout
android:id="#+id/v2"
android:layout_width="wrap_content"
android:layout_height="0dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View 3" />
</LinearLayout>
</LinearLayout>
We can animate the height to the desired value - say 100dp - using the following code:
//convert 100dp to pixel value
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
Then use droidQuery to animate. The simplest way is with this:
$.animate("{ height: " + height + "}", new AnimationOptions());
To make the animation more appealing, consider adding an easing:
$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE));
You can also change the duration on AnimationOptions using the duration() method, or handle what happens when the animation ends. For a complex example, try:
$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE)
.duration(1000)
.complete(new Function() {
#Override
public void invoke($ d, Object... args) {
$.toast(context, "finished", Toast.LENGTH_SHORT);
}
}));
Best solution for expand/collapse view's:
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
View view = buttonView.getId() == R.id.tb_search ? fSearch : layoutSettings;
transform(view, 200, isChecked
? ViewGroup.LayoutParams.WRAP_CONTENT
: 0);
}
public static void transform(final View v, int duration, int targetHeight) {
int prevHeight = v.getHeight();
v.setVisibility(View.VISIBLE);
ValueAnimator animator;
if (targetHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
animator = ValueAnimator.ofInt(prevHeight, v.getMeasuredHeight());
} else {
animator = ValueAnimator.ofInt(prevHeight, targetHeight);
}
animator.addUpdateListener(animation -> {
v.getLayoutParams().height = (animation.getAnimatedFraction() == 1.0f)
? targetHeight
: (int) animation.getAnimatedValue();
v.requestLayout();
});
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(duration);
animator.start();
}
You can use a ViewPropertyAnimator with a slight twist. To collapse, scale the view to a height of 1 pixel, then hide it. To expand, show it, then expand it to its height.
private void collapse(final View view) {
view.setPivotY(0);
view.animate().scaleY(1/view.getHeight()).setDuration(1000).withEndAction(new Runnable() {
#Override public void run() {
view.setVisibility(GONE);
}
});
}
private void expand(View view, int height) {
float scaleFactor = height / view.getHeight();
view.setVisibility(VISIBLE);
view.setPivotY(0);
view.animate().scaleY(scaleFactor).setDuration(1000);
}
The pivot tells the view where to scale from, default is in the middle. The duration is optional (default = 1000). You can also set the interpolator to use, like .setInterpolator(new AccelerateDecelerateInterpolator())
I created version in which you don't need to specify layout height, hence it's a lot easier and cleaner to use. The solution is to get the height in the first frame of the animation (it's available at that moment, at least during my tests). This way you can provide a View with an arbitrary height and bottom margin.
There's also one little hack in the constructor - the bottom margin is set to -10000 so that the view stays hidden before the transformation (prevents flicker).
public class ExpandAnimation extends Animation {
private View mAnimatedView;
private ViewGroup.MarginLayoutParams mViewLayoutParams;
private int mMarginStart, mMarginEnd;
public ExpandAnimation(View view) {
mAnimatedView = view;
mViewLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
mMarginEnd = mViewLayoutParams.bottomMargin;
mMarginStart = -10000; //hide before viewing by settings very high negative bottom margin (hack, but works nicely)
mViewLayoutParams.bottomMargin = mMarginStart;
mAnimatedView.setLayoutParams(mViewLayoutParams);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//view height is already known when the animation starts
if(interpolatedTime==0){
mMarginStart = -mAnimatedView.getHeight();
}
mViewLayoutParams.bottomMargin = (int)((mMarginEnd-mMarginStart) * interpolatedTime)+mMarginStart;
mAnimatedView.setLayoutParams(mViewLayoutParams);
}
}
Use ValueAnimator:
ValueAnimator expandAnimation = ValueAnimator.ofInt(mainView.getHeight(), 400);
expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animation) {
int height = (Integer) animation.getAnimatedValue();
RelativeLayout.LayoutParams lp = (LayoutParams) mainView.getLayoutParams();
lp.height = height;
}
});
expandAnimation.setDuration(500);
expandAnimation.start();
public static void slide(View v, int speed, int pos) {
v.animate().setDuration(speed);
v.animate().translationY(pos);
v.animate().start();
}
// slide down
slide(yourView, 250, yourViewHeight);
// slide up
slide(yourView, 250, 0);

How to expand a layout height with animation?

I couldn't find a good example for how to do this.
I have a RelativeLayout set with x height.
I want to add a button which expands the height to x+y height.
can someone refer me to a good example on how to do it programmatically?
You marked the solution that was closest. This is the exact solution. I had the same problem. Hopefully this answer will help others.
InstantiateResizeAnimation
ResizeAnimation resizeAnimation = new ResizeAnimation(
view,
targetHeight,
startHeight
);
resizeAnimation.setDuration(duration);
view.startAnimation(resizeAnimation);
ResizeAnimation class should look like this
public class ResizeAnimation extends Animation {
final int targetHeight;
View view;
int startHeight;
public ResizeAnimation(View view, int targetHeight, int startHeight) {
this.view = view;
this.targetHeight = targetHeight;
this.startHeight = startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (startHeight + targetHeight * interpolatedTime);
//to support decent animation, change new heigt as Nico S. recommended in comments
//int newHeight = (int) (startHeight+(targetHeight - startHeight) * interpolatedTime);
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
You need a scale animation here is the official documentation
this is in code
private void animate() {
ImageView imageView = (ImageView) findViewById(R.id.ImageView01);
ScaleAnimation scale = new ScaleAnimation((float)1.0, (float)1.5, (float)1.0, (float)1.5);
scale.setFillAfter(true);
scale.setDuration(500);
imageView.startAnimation(scale);
}
Please check below new edited answer as below. But here you need to know the exact new height.
public class LayoutAnimationActivity extends Activity {
RelativeLayout ril1;
Button btn;
int initialHeight;
int actualHeight;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
ril1 = (RelativeLayout) findViewById(R.id.relativeLayout1);
btn = new Button(this);
btn.setWidth(100);
btn.setHeight(200);
btn.setText("Button");
actualHeight = 210;
Ani a = new Ani();
a.setDuration(2000);
ril1.startAnimation(a);
}
class Ani extends Animation {
public Ani() {}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
newHeight = (int) (initialHeight * interpolatedTime);
ril1.removeAllViews();
btn.setWidth(100);
btn.setHeight(300);
btn.setText("as");
ril1.addView(btn);
ril1.getLayoutParams().height = newHeight;
ril1.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
initialHeight = actualHeight;
}
#Override
public boolean willChangeBounds() {
return true;
}
};
}
Two simple ways to do this without an Animation class:
1) Set android:animateLayoutChanges="true" in you xml layout file
2) Use a ViewProperty animator
layout.setPivot(0);
layout.animate().scaleY(scaleFactor).setDuration(500);
The pivot tells the view where to scale from, default is in the middle, which in my experience is almost never what you want. The duration is optional (default = 1000).
final Button button1 = (Button) view.findViewById(R.id.button);
final CollapseAnimator animator = new CollapseAnimator(topLayout);
final ViewTreeObserver.OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int mHeight = button1.getMeasuredHeight();
KLog.i("onGlobalLayout() mHeight:" + mHeight);
animator.setValues(mHeight*2, mHeight);
}
};
button1.getViewTreeObserver().addOnGlobalLayoutListener(listener);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
view.post(new Runnable() {
#Override
public void run() {
button1.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
animator.collapse();
}
});
}
});
and class
public class CollapseAnimator {
private View view;
private boolean collapse=true;
private int duation=300;
private int destHeight=300;
private ValueAnimator animator;
private int originHeight=0;
private int from=0;
private int to=0;
public CollapseAnimator(View view ) {
this.view = view;
}
public void setValues(int destHeight,int originHeight){
this.destHeight = destHeight;
this.originHeight=originHeight;
from=originHeight;
to=originHeight;
}
public void collapse(){
from=to;
if(collapse){
to=destHeight;
collapse=false;
}else{
to=originHeight;
collapse=true;
}
KLog.i("from:" + from+",to:"+to);
animator = ValueAnimator.ofInt(from, to);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = val;
view.setLayoutParams(layoutParams);
}
});
animator.setDuration(duation);
animator.start();
}
}

Categories

Resources