I want to make an Animation for when a View gets it's visibility set to GONE. Instead of just dissapearing, the View should 'collapse'. I tried this with a ScaleAnimation but then the View is collapse, but the layout will only resize it's space after (or before) the Animation stops (or starts).
How can I make the Animation so that, while animating, the lower Views will stay directly below the content, instead of having a blank space?
Put the view in a layout if it's not and set android:animateLayoutChanges="true" for that layout.
There doesn't seem to be an easy way to do this through the API, because the animation just changes the rendering matrix of the view, not the actual size. But we can set a negative margin to fool LinearLayout into thinking that the view is getting smaller.
So I'd recommend creating your own Animation class, based on ScaleAnimation, and overriding the "applyTransformation" method to set new margins and update the layout. Like this...
public class Q2634073 extends Activity implements OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.q2634073);
findViewById(R.id.item1).setOnClickListener(this);
}
#Override
public void onClick(View view) {
view.startAnimation(new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, view, true));
}
public class MyScaler extends ScaleAnimation {
private View mView;
private LayoutParams mLayoutParams;
private int mMarginBottomFromY, mMarginBottomToY;
private boolean mVanishAfter = false;
public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
boolean vanishAfter) {
super(fromX, toX, fromY, toY);
setDuration(duration);
mView = view;
mVanishAfter = vanishAfter;
mLayoutParams = (LayoutParams) view.getLayoutParams();
int height = mView.getHeight();
mMarginBottomFromY = (int) (height * fromY) + mLayoutParams.bottomMargin - height;
mMarginBottomToY = (int) (0 - ((height * toY) + mLayoutParams.bottomMargin)) - height;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
int newMarginBottom = mMarginBottomFromY
+ (int) ((mMarginBottomToY - mMarginBottomFromY) * interpolatedTime);
mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
mLayoutParams.rightMargin, newMarginBottom);
mView.getParent().requestLayout();
} else if (mVanishAfter) {
mView.setVisibility(View.GONE);
}
}
}
}
The usual caveat applies: because we are overriding a protected method (applyTransformation), this is not guaranteed to work in future versions of Android.
I used the same technique as Andy here has presented. I wrote my own Animation class for that, that animate the margin's value, causing the effect of the item to disappear/appear.
It looks like this:
public class ExpandAnimation extends Animation {
// Initializations...
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Calculating the new bottom margin, and setting it
mViewLayoutParams.bottomMargin = mMarginStart
+ (int) ((mMarginEnd - mMarginStart) * interpolatedTime);
// Invalidating the layout, making us seeing the changes we made
mAnimatedView.requestLayout();
}
}
}
I have a full example that works on my blog post
http://udinic.wordpress.com/2011/09/03/expanding-listview-items/
I used the same technique as Andy here, and refined it so that it can be used for expanding and collapsing without glitches, also using a technique described here: https://stackoverflow.com/a/11426510/1317564
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
class LinearLayoutVerticalScaleAnimation extends ScaleAnimation {
private final LinearLayout view;
private final LinearLayout.LayoutParams layoutParams;
private final float beginY;
private final float endY;
private final int originalBottomMargin;
private int expandedHeight;
private boolean marginsInitialized = false;
private int marginBottomBegin;
private int marginBottomEnd;
private ViewTreeObserver.OnPreDrawListener preDrawListener;
LinearLayoutVerticalScaleAnimation(float beginY, float endY,
LinearLayout linearLayout) {
super(1f, 1f, beginY, endY);
this.view = linearLayout;
this.layoutParams = (LinearLayout.LayoutParams) linearLayout.getLayoutParams();
this.beginY = beginY;
this.endY = endY;
this.originalBottomMargin = layoutParams.bottomMargin;
if (view.getHeight() != 0) {
expandedHeight = view.getHeight();
initializeMargins();
}
}
private void initializeMargins() {
final int beginHeight = (int) (expandedHeight * beginY);
final int endHeight = (int) (expandedHeight * endY);
marginBottomBegin = beginHeight + originalBottomMargin - expandedHeight;
marginBottomEnd = endHeight + originalBottomMargin - expandedHeight;
marginsInitialized = true;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (!marginsInitialized && preDrawListener == null) {
// To avoid glitches, don't draw until we've initialized everything.
preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (view.getHeight() != 0) {
expandedHeight = view.getHeight();
initializeMargins();
adjustViewBounds(0f);
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
return false;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
if (interpolatedTime < 1.0f && view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
if (marginsInitialized) {
if (interpolatedTime < 1.0f) {
adjustViewBounds(interpolatedTime);
} else if (endY <= 0f && view.getVisibility() != View.GONE) {
view.setVisibility(View.GONE);
}
}
}
private void adjustViewBounds(float interpolatedTime) {
layoutParams.bottomMargin =
marginBottomBegin + (int) ((marginBottomEnd - marginBottomBegin) * interpolatedTime);
view.getParent().requestLayout();
}
}
Related
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.
I implemented an ExpandAnimation on view like this:
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
LayoutParams lp =
(LayoutParams) view.getLayoutParams();
if(rightAnimation){
lp.width = (int) (mStartWidth + mDeltaWidth *
interpolatedTime);
view.setLayoutParams(lp);
}else{
}
}
With this animation my view expands and shrinks.
What i want to do is to implement this animation to the other side (to -x side) as well. But i get confused a little bit, since shrinking the width will not work.
(Since minus widths or heights are not allowed)
Does anyone know a better way to implement expand animation to the left (-x) or up(-y) side as well?
Or maybe mirroring the view?
Finally I achieved to make a left hand side expand animation. Here is the code:
private class ExpandAnimation extends Animation implements Animation.AnimationListener {
private final int mStartWidth;
private final int mDeltaWidth;
int delta;
public ExpandAnimation(int startWidth, int endWidth) {
mStartWidth = startWidth;
mDeltaWidth = endWidth - startWidth;
RelativeLayout.LayoutParams param = (RelativeLayout.LayoutParams) getLayoutParams();
delta = param.width;
setAnimationListener(this);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams param = (RelativeLayout.LayoutParams) getLayoutParams();
delta = param.width;
if(param.width <= 0){
delta = 0;
}
param.width = (int) (mStartWidth + mDeltaWidth *
interpolatedTime);
if(delta != 0){
delta = (int) (mStartWidth + mDeltaWidth *
interpolatedTime) - delta;
}
param.leftMargin -=delta;
setLayoutParams(param);
}
#Override
public boolean willChangeBounds() {
return true;
}
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
setText(text);
requestLayout();
mExpanded = !mExpanded;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
}
For a reason that I don't know, width comes as -2 in the start. So i filter out that by an if. Apply Transformation method does the job.
This is my custom ScaleAnimation:
public class MyScaler extends ScaleAnimation {
private static final String TAG = "myScaler";
private View mView;
private LayoutParams mLayoutParams;
private int oldMargin, newMargin;
private boolean mVanishAfter = false;
public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
boolean vanishAfter) {
super(fromX, toX, fromY, toY);
setDuration(duration);
setFillAfter(true);
setFillBefore(true);
setFillEnabled(true);
mView=view;
int height = 200; // fiktive hoehe
mLayoutParams = (LayoutParams) view.getLayoutParams();
oldMargin = toY<fromY ? mLayoutParams.bottomMargin : -214;
Log.d(TAG, "old Margin "+oldMargin);
newMargin = toY<fromY ? oldMargin-height : height;
Log.d(TAG, "new Margin "+newMargin);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
Log.d(TAG, "apply transofrmation");
if (interpolatedTime < 1.0f) {
int newBottomMargin=(int)(newMargin*interpolatedTime) + oldMargin;
mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
mLayoutParams.rightMargin, newBottomMargin);
Log.d(TAG,"margin change "+newBottomMargin);
mView.getParent().requestLayout();
} else if (mVanishAfter) {
mView.setVisibility(View.GONE);
}
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I am calling it like this, to get a expand and hide it via a toggle button:
toggle.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
Log.d(TAG, "onClick first "+first);
if(first){
myScale = new MyScaler(1.0f, 1.0f, 0.0f, 1.0f, 500, dp, false);
((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.expander_ic_minimized, 0);
}
else{
myScale = new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, dp, false);
((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.expander_ic_maximized, 0);
}
first=!first;
myScale.setAnimationListener(new AnimationListener(){
#Override
public void onAnimationEnd(Animation animation) {
first= !first;
Log.d(TAG, "change First"+first);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationStart(Animation animation) {
Log.d(TAG, "started ani");
}
});
dp.startAnimation(myScale);
View parent = (View)dp.getParent();
parent.invalidate();
}
});
It hides the View at the first attempt the right way. But everytime I want to expand it via the Button, it does not happen. If touch the over views, the animation starts. What do I have to change? Thanks in advance.
I had the same issue but invalidate didn't work in my case. This solved my issue:
dp.requestLayout();
For those who calls to invalidate() and requestLayout() do not work, try
((View) view.getParent()).invalidate()
as described here: https://stackoverflow.com/a/15657563/752781
Callig the invalidate() method on a visible View object just after the startAnimation(Animation) method helped me.
I have a list of buttons. When I press a button, a View should slide in a downwards motion out of the button, like this:
Start:
Halfway:
End:
How would I go about this? The View that should slide out is bigger than the button, so first hiding the View behind the button and then sliding it downwards causes the View to be visible above the button. That should not happen.
Any ideas or examples on how to approach this?
I believe the simplest approach is to extend Animation class and override applyTransformation() to change the view's height as follows:
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
public class MyCustomAnimation extends Animation {
public final static int COLLAPSE = 1;
public final static int EXPAND = 0;
private View mView;
private int mEndHeight;
private int mType;
private LinearLayout.LayoutParams mLayoutParams;
public MyCustomAnimation(View view, int duration, int type) {
setDuration(duration);
mView = view;
mEndHeight = mView.getHeight();
mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
mType = type;
if(mType == EXPAND) {
mLayoutParams.height = 0;
} else {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
}
view.setVisibility(View.VISIBLE);
}
public int getHeight(){
return mView.getHeight();
}
public void setHeight(int height){
mEndHeight = height;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
if(mType == EXPAND) {
mLayoutParams.height = (int)(mEndHeight * interpolatedTime);
} else {
mLayoutParams.height = (int) (mEndHeight * (1 - interpolatedTime));
}
mView.requestLayout();
} else {
if(mType == EXPAND) {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
mView.requestLayout();
}else{
mView.setVisibility(View.GONE);
}
}
}
}
To use it, set your onclick() as follows:
int height;
#Override
public void onClick(View v) {
if(view2.getVisibility() == View.VISIBLE){
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
view2.startAnimation(a);
}else{
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
view2.startAnimation(a);
}
}
Regards.
Use something like:
Animation a = new ScaleAnimation(1, 1, 0, 1, Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0);
a.setFillAfter(true);
view.setAnimation(a);
a.setDuration(1000);
view.startAnimation(a);
Here is simple example of hand-made animation, that provide what you want. It works in test app, but I'm not sure that there is no bugs:
public class MainActivity extends Activity implements OnClickListener {
private Timer timer;
private TimerTask animationTask;
private View view1;
private View view2;
boolean animating;
boolean increasing = true;
int initHeight = -1;
private LayoutParams params;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timer = new Timer();
view1 = findViewById(R.id.view1);// clickable view
view1.setOnClickListener(this);
view2 = findViewById(R.id.view2);// animated view
params = view2.getLayoutParams();
}
#Override
protected void onDestroy() {
super.onDestroy();
timer.cancel();
}
#Override
public void onClick(View v) {
Toast.makeText(this, "start animating...", Toast.LENGTH_SHORT).show();
animationTask = new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (animationFinished()) {
animating = false;
cancel();//canceling animating task
return;
}
params.height += increasing ? 1 : -1;
view2.setLayoutParams(params);
}
});
}
private boolean animationFinished() {
int viewHeight = view2.getHeight();
if (increasing && viewHeight >= initHeight) {
return true;
}
if (!increasing && viewHeight <= 0) {
return true;
}
return false;
}
};
//if we already animating - we just change direction of animation
increasing = !increasing;
if (!animating) {
animating = true;
int height = view2.getHeight();
params.height = height;
view2.setLayoutParams(params);//change param "height" from "wrap_conent" to real height
if (initHeight < 0) {//height of view - we setup it only once
initHeight = height;
}
timer.schedule(animationTask, 0, 10);//changing repeat time here will fasten or slow down animation
}
}
}
Maybe you can set the height to 0 and gradually increase the height. But then you will have the problem that you have to be sure your text is aligned at the bottom of the view. And also to know what the maximal height of the view should be.
use a sliding list adapter so much easier than messing around with animations
https://github.com/tjerkw/Android-SlideExpandableListView
Simply pass android:animateLayoutChanges to LinearLayout that holds all the views, you will achieve your desired result.
I would do it like that. First the layout for the whole collapsible panel component: (pseudo xml)
RelativeLayout (id=panel, clip)
LinearLayout (id=content, alignParentBottom=true)
LinearLayout (id=handle, above=content)
This should ensure that the content is always below the handle.
Then when you need to collapse:
Animate the top margin of content from 0 to -content.height
Animate the height of the panel from current to current-content.height
In android how can i add animation to a view when its added to mainview, for example slowly growing and occupying the mainview. I think its possible but where to start, I need it for my ListView to expand when other view are removed.
Thanks
I have one demo. It moves up and down very smoothly when add or hide view
public class ExpandAnimation extends Animation {
private View mAnimatedView;
private LayoutParams mViewLayoutParams;
private int mMarginStart, mMarginEnd;
private boolean mIsVisibleAfter = false;
private boolean mWasEndedAlready = false;
ImageButton mImageButton;
/**
* Initialize the animation
* #param view The layout we want to animate
* #param duration The duration of the animation, in ms
*/
public ExpandAnimation(View view, int duration, ImageButton button) {
this.mImageButton = button;
setDuration(duration);
mAnimatedView = view;
mViewLayoutParams = (LayoutParams) view.getLayoutParams();
// if the bottom margin is 0,
// then after the animation will end it'll be negative, and invisible.
mIsVisibleAfter = (mViewLayoutParams.bottomMargin == 0);
mMarginStart = mViewLayoutParams.bottomMargin;
mMarginEnd = (mMarginStart == 0 ? (0- view.getHeight()) : 0);
view.setVisibility(View.VISIBLE);
button.setImageResource(R.drawable.bar_down);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Calculating the new bottom margin, and setting it
mViewLayoutParams.bottomMargin = mMarginStart
+ (int) ((mMarginEnd - mMarginStart) * interpolatedTime);
// Invalidating the layout, making us seeing the changes we made
mAnimatedView.requestLayout();
// Making sure we didn't run the ending before (it happens!)
} else if (!mWasEndedAlready) {
mViewLayoutParams.bottomMargin = mMarginEnd;
mAnimatedView.requestLayout();
if (mIsVisibleAfter) {
mAnimatedView.setVisibility(View.GONE);
mImageButton.setImageResource(R.drawable.bar_up);
}
mWasEndedAlready = true;
}
}
}
btnShowBookmarkBar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
animation = new ExpandAnimation(bookmarkControlView,
CommConstant.DEFAULT_SHOW_UP_TIME, btnShowBookmarkBar);
btnShowBookmarkBar.startAnimation(animation);
}
});