Here's the animation:
public class WidthAnimation extends Animation {
protected final int originalWidth;
protected final View view;
protected float perValue;
public WidthAnimation(View view, int fromWidth, int toWidth) {
this.view = view;
this.originalWidth = fromWidth;
this.perValue = (toWidth - fromWidth);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().width = (int) (originalWidth + perValue * interpolatedTime);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
When called by this (animating the View to be zero width), it works fine:
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, getWindowWidthInPixels(), 0);
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
But when called by this (animating the View to being displayed), applyTransform is not called, and the animation is not shown:
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, 0, getWindowWidthInPixels());
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
Both animations are being triggered by screen clicks. The getWindowWidthInPixels() method works correctly. I've seen several other questions on SO that suggest calling invalidate() or requestLayout() on the View, or its parent, can resolve this, but for me those solutions do not work.
Wow, that is weird, that it would work to shrink but not grow. Maybe Android is discarding the startAnimation call because the view is width 0. I suppose one quick fix would be to set the width to 1 before starting the grow animation.
dashboardContainerView.getLayoutParams().width = 1;
WidthAnimation widthAnim = new WidthAnimation(dashboardContainerView, 0, getWindowWidthInPixels());
widthAnim.setDuration(500);
dashboardContainerView.startAnimation(widthAnim);
Another option would be to switch to ObjectAnimator. My animations got a lot more straightforward when I did that. Yours would look something like this:
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dashboardWidth", fromWidth, toWidth);
anim.setDuration(500);
dashboardContainerView.startAnimation(anim);
public void setDashboardWidth(int width) {
ViewGroup.LayoutParams params = dashboardContainerView.getLayoutParams();
params.width = width;
dashboardContainerView.setLayoutParams(params);
}
That is the pattern I usually use with success. I don't have to call requestLayout in that case, but maybe setLayoutParams is doing that. Maybe just the getLayoutParams and requestLayout that you are using would be fine too.
Anyway, I really like the ObjectAnimator because it is so simple. One thing you do have to watch out for is if you are using ProGuard, you have to make sure it doesn't clean out the setDashboardWidth method, because it is not directly called in your code. It is called from within ObjectAnimator, which finds the method using reflection. Thus the exact name and signature of the method has to match the "dashboardWidth" property name in the call to ofInt.
Related
I am using following code to animate expandable layout:
class ExpandAnimation extends Animation {
private View _view;
private int _startHeight;
private int _finishHeight;
public ExpandAnimation( View view, int startHeight, int finishHeight ) {
_view = view;
_startHeight = startHeight;
_finishHeight = finishHeight;
setDuration(500);
System.out.println(_startHeight);
System.out.println(_finishHeight);
}
#Override
protected void applyTransformation( float interpolatedTime, Transformation t ) {
int newHeight = (int)((_finishHeight - _startHeight) * interpolatedTime + _startHeight);
_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;
}
};
This animation is created every time I click a button. It is called properly (checked System.out.println and it prints correct values) however in emulator animation runs only like once of 15 times. To be exact hiding it works great but expanding works only once a few times (on emulator, cant get it working on phone).
What could be the problem?
Thanks in forward
EDIT: layout I am trying to animate is FrameLayout. It has TextView as child and finishHeight is measured by textView measure height. The values are correct. I have also tried calling textView.requestLayout() in apply transformation to redraw layout but it is not working. It still expands only sometimes. If you need any more code feel free to ask.
Calling
((View) toExpand.getParent()).invalidate();
just after startAnimation solved my problem. Must check it on other devices but I think it will work.
I need to implement this things, a height of item in ListView will be reduced slowly, when its height reduce to 0, it's gone. On this process, the remain views that below the item, should move up slowly.
At First, I use the ObjectAnimator to change the 'scaleY', but the size of item occupy was not changed, when Animator ends, ListView refresh, the empty rectangle was gone.
ObjectAnimator oa = ObjectAnimator.ofFloat(itemView, 'scaleY', 1f, 0f);
And I found another way to do this, I write a Runnable to change the height of the item, but there are some child view in my item, like a ImageView, with the height changing, the ImageView changing too, I think this is not look well.
LayoutParams lp = itemView.getLayoutParams();
lp.height = newHeight;
itemView.setLayoutParams(lp);
At last, I found third way to do, change the 'bottom' value of the item, yes, It looks like a window with reducing height, but height not changed, the remain views that below the item didn't move until Runnable ends and ListView refresh.
itemView.setBottom(itemView.getTop() + newHeight);
How to solve this?
I do something similar to this and work fine:
public void collapse(final View v, final int toHeight, final int toWidth) {
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = (int) (toHeight * interpolatedTime);
v.getLayoutParams().width = (int) (toWidth * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
a.setDuration(ANIMATION_DURATION);
a.setInterpolator(new AccelerateInterpolator());
v.startAnimation(a);
}
the height set in the layout can not be fixed (put wrap_content). Hope it help you!!
I have 2 LinearLayouts with views within them held in a container LinearLayout that is using layout_weight to determine their sizes. I am trying to shrink the top view when the user clicks inside the bottom view with an animation.
I extended Animation with a class:
public class ShrinkTopViewAnimation extends Animation {
protected int mOriginalHeight;
protected final LinearLayout mView;
public ShrinkTopViewAnimation(LinearLayout view) {
this.mView = view;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mOriginalHeight = height;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation transformation
) {
int shrinkAmount = (int)(mOriginalHeight * interpolatedTime);
ViewGroup.LayoutParams layoutParams = mView.getLayoutParams();
layoutParams.height = mOriginalHeight - shrinkAmount;
mView.setLayoutParams(layoutParams);
mView.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
The BottomView onClick calls:
ShrinkTopViewAnimation shrinkTopViewAnimation = new ShrinkTopViewAnimation(mTopLinearLayout);
shrinkTopViewAnimation.setDuration(1000);
mTopLinearLayout.startAnimation(shrinkTopViewAnimation);
I am very confused as to what is happening. What I am seeing is that the first time through the applyTransform the interpolatedTime is 0 so the .height is set to the exact same number that it was before. But the next call to the applyTransformation a getHeight call to the mView gives a number way bigger than the starting height and at the end of the transform when interpolatedTime is 1 the view is back to the original size. The visual effect is the top view jumps larger, then shrinks back to original size.
Both getHeight() and the initialize Height are listed as px in the documentation, and the LayoutParams.Height is listed as px. But it seems like there is some translation to dp possibly going on?
Any ideas?
To keep the animated result, use Animation.setFillAfter(true).
When set to true, the animation transformation is applied after the animation is over.
What you used here is View Animation. This kind of animation only modify rendering transformation, so the actual size of view is not changed.
If you need to changed the actual size, you have to set LayoutParams via a AnimationListener, or use a Property Animation.
I have a RelativeLayout with a ImageView inside it, it is aligned to the right of the screen with
android:layout_alignParentRight="true"
I then apply an animation to the layout, that will scale it to twice it size (on X-axis), but for some reason the alignment is broken and the layout (and image within it) stretches outside of the screen to the right. I was expecting it to grow to the left since it is aligned to the parent right.
I guess I could apply a translate to -X at the same time, but there are problems with this as well (1. it´s a bit complicated to compute, 2. the fillAfter never seems to work when using an AnimationSet).
Does anyone know how to solve this problem smoothly? :)
Apparently, Androids scaling is doing all sorts of bad stuff, for example it does not scale a 9-patch correct. Here is a custom animation for scaling that will solve the above and is compatible with 9-patch images. I also got some info here for the basics, this is just a rewrite of his solution: How to implement expandable panels in Android?
public class ExpandAnimation extends Animation
{
private final int mStartWidth;
private final int mDeltaWidth;
private View view;
public ExpandAnimation(View view, int startWidth, int endWidth)
{
mStartWidth = startWidth;
mDeltaWidth = endWidth - startWidth;
this.view = view;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
android.view.ViewGroup.LayoutParams lp = view.getLayoutParams();
lp.width = (int) (mStartWidth + mDeltaWidth * interpolatedTime);
view.setLayoutParams(lp);
view.invalidate();
}
#Override
public boolean willChangeBounds()
{
return true;
}
}
I'm implementing an Animation for one menu in my project.
The animation itself is ok, the menu enters and exit just as I wanted: Slide from left to right and right to left, however...
If the entire view is OUT of the screen, then it NEVER comes back egain! If, at least one pixel is still inside the screen, then it comes back normally.
I belive that Android is disposing the layout, and not caring about it after out of the screen bounds. I tried to place a setVisibility(VISIBLE) but it also didn't worked.
Here is the code:
public class ChwaziMenuAnimation extends Animation{
float posStart = 0;
float posTarget = 100;
int getCurrentPosition(){
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
return rootParam.leftMargin;
}
public void setTarget(float target){
// Save current position
posStart = getCurrentPosition();
posTarget = target;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
// Calculate current position
rootParam.leftMargin = (int) ((posTarget - posStart) * interpolatedTime + posStart);
rootView.setLayoutParams(rootParam);
}
/*
* Since we will be animating the margin, the bounds will always change
*/
public boolean willChangeBounds() {
return true;
};
};
And how I initialize the animation:
public void appear(){
Log.i(TAG, "appear");
menuAnimation.setTarget(0);
menuAnimation.setDuration(750);
rootView.clearAnimation();
rootView.startAnimation(menuAnimation);
}
public void disapear(){
Log.i(TAG, "disapear");
menuAnimation.setTarget(-400);
menuAnimation.setDuration(750);
rootView.startAnimation(menuAnimation);
}
I encountered the same problem, my workaround so far is to extend the view bounds to at least one pixel on the displayable area, and make that part transparent. Ugly, but for me it seems to work.
To make it even more weird: the view did not disappear, when shifted out the right side of the screen, only when shifted out the left side of the screen. But that might be device dependent.