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 animat
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()
}
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 !
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);
}
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);
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);
}
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();