I would like to apply successive animations (say ScaleAnimation) to an ImageView showing a resource image. The animation is triggered by a button. For example, I would like to incrementally enlarge an image upon each button click.
I've set fillAfter="true" on the animation. However, all the animations start from the original state of the ImageView. It seems as if the ImageView resets its state and the animation is always the same, instead of starting from the final state of the previous animation.
What am I doing wrong?
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button)findViewById(R.id.Button01);
button.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
animate();
}});
}
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);
}
It seems as if the ImageView resets
its state and the animation is always
the same, instead of starting from the
final state of the previous animation.
Precisely! I'm sure there's a use for fillAfter="true", but I haven't figured out the point for it yet.
What you need to do is set up an AnimationListener on each Animation of relevance, and do something in the listener's onAnimationEnd() to actually persist the end state of your animation. I haven't played with ScaleAnimation so I'm not quite sure what the way to "persist the end state" would be. If this were an AlphaAnimation, going from 1.0 to 0.0, you would make the widget INVISIBLE or GONE in onAnimationEnd(), for example.
I've had the same problem and created the following code to easily use different animations. It only supports translation and alpha levels for now as I haven't used scaling, but could easily be extended to support more features.
I reset the scroll and the visibility before starting the animation, but that's just because I needed on/off animations.
And the "doEnd" boolean is there to avoid a stack overflow on the recursion (scrollTo calls onAnimationEnd for some obscure reason...)
private void setViewPos(View view, Animation anim, long time){
// Get the transformation
Transformation trans = new Transformation();
anim.getTransformation(time, trans);
// Get the matrix values
float[] values = new float[9];
Matrix m = trans.getMatrix();
m.getValues(values);
// Get the position and apply the scroll
final float x = values[Matrix.MTRANS_X];
final float y = values[Matrix.MTRANS_Y];
view.scrollTo(-(int)x, -(int)y);
// Show/hide depending on final alpha level
if (trans.getAlpha() > 0.5){
view.setVisibility(VISIBLE);
} else {
view.setVisibility(INVISIBLE);
}
}
private void applyAnimation(final View view, final Animation anim){
view.scrollTo(0, 0);
view.setVisibility(VISIBLE);
anim.setAnimationListener(new AnimationListener(){
private boolean doEnd = true;
#Override
public void onAnimationEnd(Animation animation) {
if (doEnd){
doEnd = false;
setViewPos(view, animation, anim.getStartTime() + anim.getDuration());
}
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationStart(Animation animation) {
}
});
view.startAnimation(anim);
}
Related
Using Rotate Animation in android, but no rotation in image after clicking the button.
public class MainActivity extends AppCompatActivity {
ImageView spinImage;
Button buton;
Random r;
int degree =0 , degreeold= 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buton = (Button) findViewById(R.id.spinbutton);
spinImage= (ImageView) findViewById(R.id.spinimage);
r= new Random();
buton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
degreeold = degree % 360;
degree = r.nextInt(3600)+720;
degree= 4400;
RotateAnimation rotate = new RotateAnimation( degreeold, degree,
RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF , 0.5f);
rotate.setDuration(3600);
rotate.setFillAfter(true);
rotate.setInterpolator( new DecelerateInterpolator());
rotate.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
spinImage.setAnimation(rotate);
}
});
}
i am not able to find the mistake why there is no rotation in image. there is no error while running and app open without any delay but there is no animation when clicking the button.
You haven't started the animation. Try to use spinImage.startAnimation(rotate);
setAnimation is meant to give you more fine grained control if you want to start the animation delayed or together with other animations.
From the documentation:
Sets the next animation to play for this view. If you want the animation to play immediately, use startAnimation(android.view.animation.Animation) instead. This method provides allows fine-grained control over the start time and invalidation, but you must make sure that 1) the animation has a start time set, and 2) the view's parent (which controls animations on its children) will be invalidated when the animation is supposed to start.
Replace spinImage.setAnimation(rotate); with spinImage.startAnimation(rotate);
I'm building an app and i wanted to animate some imageView element. The problem is that at the end of the animation, the element gets an unexpected boost in the y axis, instead the defined one.
I know that at the end of the animation process the animated element returns to its original x,y. I manually added the animated offset at the onAnimationEnd method. For some odd reason the animated element jumps about 40 pixels up, and I know I didnt add it anywhere by mistake. Whats wrong here?
public class MyActivity extends Activity {
private ImageView logo,moto;
private int[] imageView_XY;
public float logoXpos,logoYpos;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.verification_activity);
logo = (ImageView) findViewById(R.id.imageView1);
//animates the logo
animate(logo,30);
}
public void animate(final ImageView imageView, int amount){
//gets the current x,y of the widget
imageView_XY = new int[2];
imageViewElement.getLocationInWindow(imageView_XY);
logoXpos = (int) imageViewElement.getX();
logoYpos = imageView_XY[1];
Log.d("Animation XY: "," current location "+"("+logoXpos+","+logoYpos+")"); //prints out 0,0
//animates the widget 100 px up
TranslateAnimation anim = new TranslateAnimation(0,0,0,-amount);
logoAnim.setDuration(1000);
logoAnim.setFillAfter(false);
logoAnim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Log.d("Animation: ","animation ended");
logoXpos = imageView_XY[0];
logoYpos = imageView_XY[1];
Log.d("Animation ended XY: "," current location "+"("+logoXpos+","+logoYpos+")"); //prints out 0,0 again
imageViewElement.setY(logoYpos+30);
}
#Override
public void onAnimationRepeat(Animation animation) {
Log.d("Animation: ","animation repeat");
}
});
imageViewElement.startAnimation(anim);
}
OK I got this working. For future googlers:
I ended up setting the logoAnim.setFillAfter(false); to true and got rid of the manual shift at the end of the animation imageViewElement.setY(logoYpos+30);.
I'm trying to slide in a view on a RelativeLayout via an animation. Once the view is in place I have to alter the layout params of layout so that the new view doesn't block existing content. I call the code to update the layout after the animation but it occurs before the animation causing a block of area to be blank during the animation.
The following screens are before animation, during and after:
Here is the code. The relative layout is adjusted in onShowAd.
float fromY = getAdHeight();
float toY = 0;
TranslateAnimation slide = new TranslateAnimation(0,0,fromY,toY);
slide.setDuration(ANIMATION_RATE);
slide.setFillAfter(true);
mAdLayout.startAnimation(slide);
onShowAd();
mAdLayout.setVisibility(View.VISIBLE);
I tried differing orders of calling visible and other things as well as using a handler to postDelayed onShowAd() to wait the same amount of time as the ANIMATION_RATE however what happened you'd stare at the black box for the time it was post delayed and then have to wait for the animation so it was worse. In windows there's an API call LOCKWINDOWUPDATE that I could pass in a window handle to prevent updating...is there anything equivalent for a view in Android? Or any other ideas?
Incidentally when sliding the view OUT it works fine. Here is the code going the other way. onHideAd is where the layoutparams are modified in this one.
float toY = ((View) mAdLayout.getParent()).getBottom();
float fromY = 0;//toY - mAdLayout.getHeight();
TranslateAnimation slide = new TranslateAnimation(0,0,fromY,toY);
slide.setDuration(ANIMATION_RATE);
slide.setFillAfter(true);
mAdLayout.startAnimation(slide);
mAdLayout.setVisibility(View.GONE);
onHideAd();
Thanks for any suggestions!
EDIT: I added the listener and that doesn't take care of it. I have isolated it to the following code which in in the "onShowAd()" routine:
RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) mainFragmentContainer.getLayoutParams();
params.addRule(RelativeLayout.ABOVE, mAdLayout.getId());
mainFragmentContainer.setLayoutParams(params);
I've tried to further separate it by calling it with a handler.postDelayed and it seems like anywhere I place it in the chain of events causes it to occur BEFORE the animation itself shows. Very odd! :-(
This is the updated showAd routine. I tried reordering some of the calls as well.
float fromY = getAdHeight();
float toY = 0;
TranslateAnimation slide = new TranslateAnimation(0,0,fromY,toY);
slide.setDuration(ANIMATION_RATE);
slide.setFillAfter(true);
slide.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
onShowAd();
}
});
mAdLayout.setVisibility(View.VISIBLE);
mAdLayout.startAnimation(slide);
You should use an AnimationListener to accomplish what you are looking for. The problem you are running into is caused because the call to start animation returns instantly even though the animation doesn't complete for the time set to ANIMATION_RATE.
Ex for slide in:
float fromY = getAdHeight();
float toY = 0;
TranslateAnimation slide = new TranslateAnimation(0,0,fromY,toY);
slide.setDuration(ANIMATION_RATE);
slide.setFillAfter(true);
slide.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
onShowAd();
}
});
mAdLayout.setVisibility(View.VISIBLE);
mAdLayout.startAnimation(slide);
I know multiple ways to get location values of a View.
getLocationOnScreen()
getLocationInWindow()
getLeft()
However, none of them actually returns the current location of the View I moved by startAnimation() method, but only the original location.
So, now let's make a View that moves to the right by 10 pixels on each Click (I'm omitting the layout, since you can just place whatever view in your main XML and give it onClickListener).
public class AndroidTestActivity extends Activity implements OnClickListener {
LinearLayout testView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testView = (LinearLayout) this.findViewById(R.id.test);
testView.setOnClickListener(this);
}
public void onClick(View v) {
int[] pLoS = new int[2];
testView.getLocationOnScreen(pLoS);
TranslateAnimation move = new TranslateAnimation(pLoS[0], pLoS[0] + 10, 0f, 0f);
move.setFillAfter(true);
move.setFillEnabled(true);
testView.startAnimation(move);
}
}
As you see, this doesn't work as I intended, since getLocationOnScreen() always returns the same value (in my case, 0), and doen't reflect the value I used in TranslateAnimation...
Any idea?
Assuming you're using Android < 3.0 then your question may be in a similar vein to mine I asked here. Basically Animations are separate from the View itself i.e. Android animates a copy of your View. That is why getLocationOnScreen() always returns 0. It's not the view that has moved (animated) it was the copy that moved (animated). If you see the answers to my question this issue has been addressed in later versions of Android.
Well, if you are trying to see how many pixels the view has shifted during/after its animation, then you can open up the Transformation object on the animation.
Here's an example using the AnimationListener:
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Transformation trans = new Transformation();
// startTime + duration = end of animation
int endTime = animation.getStartTime()+animation.getDuration();
animation.getTransformation(endTime, trans);
Matrix transformationMatrix = trans.getMatrix();
float[] matrixVals = new float[9];
transformationMatrix.getValues(matrixVals);
float xTraveled = matrixVals[2];
float yTraveled = matrixVals[5];
// do something with them here...
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
I may not be exactly correct in the array indices for these, definitely use Matrix.toString() and look at the values yourself. xTraveled and yTraveled will give you the amount of distance traveled by the TranslateAnimation at the indicated time (in this case, at the end of the animation).
animations on android gingerbread and below do not really change the view in any way , only change the way it is shown.
the only way to get the new position is by calculating it.
Here is my scenario.
At first I do an animation set to my view (View.startAnimation(animationSet)) where animationSet consists of Translate + Rotate + Scale all at the same time. It works fine.
on that animationSet I have fillAfter(true).
After some time user click on a button and onClick I must start new ScaleAnimation on that same View. So if I do something like:
#Override
public void onClick(View v) {
ScaleAnimation scaleAnimation = new ScaleAnimation(mOldScaleFactor, mScaleFactor, mOldScaleFactor, mScaleFactor, mPivotX, mPivotY);
scaleAnimation.setDuration(ANIMATION_DURATION_MILIS);
scaleAnimation.setStartOffset(ANIMATION_OFFSET_MILIS);
scaleAnimation.setFillAfter(true);
v.startAnimation(scaleAnimation);
}
All the previous animation (Translate + Rotete + Scale) is forgotten.
How to start new animation from where old animation ends?
You could try to implement a listener and possible capture the information from when the previous animation left off, however I'm not sure you'll be able to query the animation for its state. Another option is to make sure the animation stops into a known state. That way you can know how you need to start the next animation.
public float param1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
//load text view to add animation
ImageView image1 = (ImageView) findViewById(R.id.splash_imageView1);
Animation fade1 = AnimationUtils.loadAnimation(this, R.anim.fade_anim);
image1.startAnimation(fade1);
fade1.setAnimationListener(new AnimationListener() {
public void onAnimationEnd(Animation animation) {
//interrogate animation here
}