I've got a LinearLayout with a background bitmap. I'm adding 2-3 images to this layout with an animation. The problem is: everytime im adding images the FIRST image animation starts from the top left corner, for every other image directly after that animation it works just fine from the center. Im struggling for days to find a solution for this little annoying problem. Do u have any advise what could cause this?
Here my problem in a gif I made: http://imgur.com/FCPgof1
my animation:`
<scale
android:duration="750"
android:fillAfter="false"
android:fromXScale="0"
android:fromYScale="0.0"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0" />
`
my layout:
<LinearLayout
android:id="#+id/layoutCompareUppoints"
android:layout_width="140dp"
android:layout_height="28dp"
android:background="#drawable/uppballsbgnew" >
</LinearLayout>
my code:
`private void animUppointsBefore() {
if (k < Integer.valueOf(uppoints)) {
Animation a = AnimationUtils.loadAnimation(this,
R.anim.debug_grow_fast_animation);
ImageView upp = new ImageView(this);
upp.setImageResource(R.drawable.uppballlightnew);
upp.setAdjustViewBounds(true);
uppLayout.addView(upp);
upp.startAnimation(a);
a.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation a) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationRepeat(Animation a) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation a) {
// TODO Auto-generated method stub
k++;
animUppointsBefore();
}
});
}
}`
The issue you're most likely seeing is the image is getting added to the layout before any animation actually takes place. The image is going to start full size, shrink down to 0, then grow up to 1.
What you want to do is make it invisible before you add it then make it appear off screen. A couple of methods you can do, the usual way is to set the ImageView to View#INVISIBLE then with an Animation#AnimationListener, set the view to View#VISIBLE on animation start.
If you can, using Animators are way easier (There are some libraries in the support library that don't allow this). You can set the View's Alpha to 0 before adding it, then just set it to 1 instantly as part of the animation. Just make two ObjectAnimators, one for alpha and one for scale, then combine them in to a single AnimatorSet.
EDIT:
To put the final answer from comments to here.
Rather than adding the views just before animating, it may help to put the image views already laid out in the layout but invisible. That way, there doesn't need to be a new layout and measure pass that could be screwing up the measurements to the pivot point.
Related
I am attempting to use XML Animations to scale a view up from 0% size to 150% size (50% bigger than "real size"). Then scale back down from 150% size to "real size" of 100%. The intention is a bit of a "rebound" effect where the view will grow up from nothing to bigger than it's supposed to be and then "rebound" or "snap back" to the proper size.
Here is an image that illustrates of timeline of what I would like to accomplish.
I am using this XML animation code:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
>
<scale android:fromXScale="0"
android:fromYScale="0"
android:toXScale="1.5"
android:toYScale="1.5"
android:duration="200"
android:pivotX="50%"
android:pivotY="100%"
android:fillAfter="false"
/>
<scale android:fromXScale="1.5"
android:fromYScale="1.5"
android:toXScale="1.0"
android:toYScale="1.0"
android:duration="100"
android:pivotX="50%"
android:pivotY="100%"
android:startOffset="200"
/>
</set>
The problem that I am running into is that after the first scale animation completes the view is correctly at 150% size, but when it runs the second animation it is scaling relative to the size that we finished the first animation at (150%) as the "real size" 100%, or 1.0.
So when the second animation runs instead of my view scaling back down from 150% to the real size of 100% it is actually scaling from 150% of 150% (225%) back down to 150%. Which means that at the end of the animation the view is still too large, sized at 150% of the real size in my case.
Is there some way to instruct the second Animation or the Animation set to ignore the ending value from the first animation and instead just use the "real" size of the view so that the code would be more literally interpreted and would scale from 150% of real size down to 100% of real size.
I have tried every combination of fillBefore, fillAfter, and fillEnabled that I can think of on each scale animation and on the set itself, but thus far I've been unable to find a combination of those settings that will make it behave how I intend for it to.
I found This Article which seems like it may be relevant to my situation, and this led me to test out some more combinations of the various fill properties. But so far I'm still not able to get the outcome I am hoping for.
shouldnt a simple Interpolator for the animation work, I mean you do not have as much fine control over the animation with the exact timings but something like this should do a similar trick without that much work:
<scale android:fromXScale="0"
android:fromYScale="0"
android:toXScale="1.0"
android:toYScale="1.0"
android:duration="300"
android:pivotX="50%"
android:pivotY="100%"
android:fillAfter="true"
android:interpolator="#android:anim/overshoot_interpolator"
/>
Facebook has a java library called Rebound that models spring dynamics and adds real world physics. Example code:
import com.facebook.rebound.BaseSpringSystem;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;
/**
* This Activity presents an ImageView that scales down when pressed and returns to full size when
* released. This demonstrates a very simple integrates a very Simple integration of a Rebound
* Spring model to drive a bouncy animation as the photo scales up and down. You can control the
* Spring configuration by tapping on the blue nub at the bottom of the screen to reveal the
* SpringConfiguratorView. From this view you can adjust the tension and friction of the animation
* spring and observe the effect these values have on the animation.
*/
public class MainActivity extends Activity {
private final BaseSpringSystem mSpringSystem = SpringSystem.create();
private final ExampleSpringListener mSpringListener = new ExampleSpringListener();
private FrameLayout mRootView;
private Spring mScaleSpring;
private View mImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mRootView = (FrameLayout) findViewById(R.id.root_view);
mImageView = mRootView.findViewById(R.id.image_view);
// Create the animation spring.
mScaleSpring = mSpringSystem.createSpring();
// Add an OnTouchListener to the root view.
mRootView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// When pressed start solving the spring to 1.
mScaleSpring.setEndValue(1);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// When released start solving the spring to 0.
mScaleSpring.setEndValue(0);
break;
}
return true;
}
});
}
#Override
public void onResume() {
super.onResume();
// Add a listener to the spring when the Activity resumes.
mScaleSpring.addListener(mSpringListener);
}
#Override
public void onPause() {
super.onPause();
// Remove the listener to the spring when the Activity pauses.
mScaleSpring.removeListener(mSpringListener);
}
private class ExampleSpringListener extends SimpleSpringListener {
#Override
public void onSpringUpdate(Spring spring) {
// On each update of the spring value, we adjust the scale of the image view to match the
// springs new value. We use the SpringUtil linear interpolation function mapValueFromRangeToRange
// to translate the spring's 0 to 1 scale to a 100% to 50% scale range and apply that to the View
// with setScaleX/Y. Note that rendering is an implementation detail of the application and not
// Rebound itself. If you need Gingerbread compatibility consider using NineOldAndroids to update
// your view properties in a backwards compatible manner.
float mappedValue = (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 0, 1, 1, 0.5);
mImageView.setScaleX(mappedValue);
mImageView.setScaleY(mappedValue);
}
}
}
I have a test Image: Width: 136px and Height: 168px.
It's location on the screen is: x:102 and y:768.
I'm using Animation to rotate this Image:
Animation rotate_animation = AnimationUtils.loadAnimation(this, R.anim.rotate);
rotate_animation.reset();
tile_1.startAnimation(rotate_animation);
The associated xml is rotate.xml:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:toYScale="0.0"
android:pivotX="100%"
android:pivotY="100%"
android:duration="2000" />
It rotates, but completely off the screen, and ends up back where it started.
It appears to be rotating around x:0 and y:0.
I want to rotate it in place, around it's center.
I've played around with the numbers in the xml, but it always makes a giant circle.
Thanks for any help :)
Try removing the toYScale and setting the pivots to 50%. This way it should rotate from the center of the image
Use this code instead in rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="2000"
android:interpolator="#android:anim/cycle_interpolator"/>
</set>
Found a easy way to do a simple rotation.
My image is tile_1:
tile_1.animate().rotation(360);
HOWEVER, it will only work once unless you set up a Listener first, so full code for a quick 360 degree rotation with a reset to be able to do it again:
tile_1.animate().setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
tile_1.setRotation(0);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
tile_1.animate().rotation(360);
// You must reset the rotation to 0. I found out that clearAnimation doesn't work here. When you go to rotate a second time, it does nothing because the Image is already rotated 360, even though it APPEARS to be reset; so you must set the rotation back to zero. Then it works perfectly. Hope this helps someone else :)
You can also add .setDuration here as well, so 'Last Line':
tile_1.animate().rotation(360).setDuration(1000);
One last thing: If you use this in conjunction with TranslateAnimation, and you set the duration on both to the same amount of time, it will move from point XY to point XY while completing 1 rotation. Very smooth looking. Thanks for input from all :)
I have 4 clickable images surounding the center of my screen. Upon clicking one of the images I want the image to slide from it's current position to the center of the screen.
Once the image reaches the center of the screen I would like the image to flip and then have my fragment load.
How do I obtain this full sequence? I have my fragment loading but I am unsure of how to create an animation that slides my image from it's current position to the center. The layout of the page is know and is as follows.
1 2 3
4 5 6
7 8 9
where 2, 4, 6, 8 are my images that I want to slide and 5 is the position that I want them to slide to on click (then flip and show my fragment).
Thanks,
Dman
EDIT :
One thing I cannot get to occur is the events to play in sequence. Currently when i click the image it waits the offset that I have set on the flip_image.xml animation and then plays them all at once in the duration given to the flip_image.xml animation. Any help on this would be much appreciated.
slide_left.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%"
android:toXDelta="-112%"
android:duration="1000"/>
slide_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.8"
android:toAlpha="1.0"
android:duration="1000" />
flip_image.xml
<?xml version="1.0" encoding="utf-8"?>
<scale
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.0"
android:toXScale="1.0"
android:pivotX="50%"
android:fromYScale="1.0"
android:toYScale="1.0"
android:startOffset="1000"
android:duration="200" />
Calling Code:
private void loadFragmentAnimation(final ImageView view, final int slideDirection) {
AnimationSet animSet = new AnimationSet(true);
animSet.addAnimation(AnimationUtils.loadAnimation(getActivity(), slideDirection));
animSet.addAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_alpha));
animSet.addAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.flip_image));
animSet.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
// mCallBack.categorySelected(view.getId());
}
});
view.startAnimation(animSet);
}
Use View animation.
Create an AnimationSet having a TranslateAnimation
followed by a ScaleAnimation to simulate a flip (you may need
to mirror the image on the other side of the flip. Perhaps there's
an easier way?).
Then set the animation to the ImageView using
setAnimation().
I guess you should be able to specify the animation in XML too.
Edit:
For the flip animation, you may refer to the following links:
Android Animations 3D flip
Android Animation - Flip
I'm trying to fade out an ImageView using an animation. The animation runs, fading out the ImageView, but then the image goes back to fully opaque. Why does this happen?
private void fadeOutImage() {
final ImageView imageView = (ImageView) activity.findViewById(R.id.imageView);
imageView.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.fadeout));
}
I tried setting the alpha to zero as below, but then the image never showed up period. Why not?
private void fadeOutImage() {
final ImageView imageView = (ImageView) activity.findViewById(R.id.imageView);
imageView.setAlpha(0);
imageView.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.fadeout));
}
To get this to work, I had to create an AnimationListener, overload the onAnimationEnd, and set the alpha to be zero there. Why?
final ImageView imageView = (ImageView) activity.findViewById(id);
Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fadeout);
animation.setAnimationListener(new AnimationListener() {
#Override public void onAnimationStart(Animation animation) {
imageView.setAlpha(255);
}
#Override public void onAnimationRepeat(Animation animation) { }
#Override public void onAnimationEnd(Animation animation) {
imageView.setAlpha(0);
}
});
imageView.startAnimation(animation);
Animation XML:
<alpha
android:duration="1500"
android:fromAlpha="1.0"
android:interpolator="#android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
When you start an animation, it literally takes whatever the view currently looks like, animates it according to your supplied animation, but doesn't ever actually change the state of the view.
This can get confusing, but in your first case, after the animation ends, the ImageView just goes back to whatever it looked like before the animation - in your case, the normal opaque.
In your second example, you are setting the alpha to zero before the animation starts, and so when the animation begins playing, the image is already invisible, and so it animates this invisible ImageView - which you can't see.
Therefore, the required sequence is to start the animation before changing the ImageView at all, let it play out, and then the moment the animation ends, change the state of the ImageView so that it disappears before it gets redrawn in it's original state. This is what overriding onAnimationEnd() does.
So, to recap, in the proper implementation, there are two things that affect how the ImageView gets drawn:
The animation, which affects how the ImageView looks only while the animation is playing.
The imageView.setAlpha(0); method call, to keep the image invisible once the animation finishes.
Note: to possibly increase performance, I'd suggest using
imageView.setVisibility(View.INVISIBLE);
instead of imageView.setAlpha(0);
I'm trying to fade out an ImageView using an animation. The animation runs, fading out the ImageView, but then the image goes
back to fully opaque. Why does this happen?
The animation's effects are temporary. Once the animation has completed, the view returns to its state before the animation began.
I tried setting the alpha to zero as below, but then the image never
showed up period. Why not?
Though apparently undocumented, the fromAlpha and toAlpha parameters appear to act in a multiplicative fashion. 100% of zero is still zero. I verified this by starting the alpha to a very low value, and I could see that the animation caused the alpha to start at the low value and fade to transparent.
To get this to work, I had to create an AnimationListener, overload
the onAnimationEnd, and set the alpha to be zero there. Why?
Since the animation's effects are temporary, you need to set the object to be transparent after the animation. You also have to make sure you reset the object to be opaque before you start the animation.
It looks like you can use an ObjectAnimator starting in API 11 to set the alpha (or any other property's) values directly using rather than multiplicatively.
I'm trying to animate a simple ImageView in my application and I want it to slide in from the bottom of the screen and come to a resting position where the top 50px of the view is off the top of the screen (e.g. the final position of the ImageView should be -50px in X). I've tried to use the AbsoluteLayout to do this, but this actually cuts off the top 50px of the ImageView such that the top 50px is never rendered. I need to have the top 50px of the ImageView visible/rendered while it's animating and then simply have it come to a rest slightly off-screen. I hope I've explained that well enough.
Here is what I'm currently using as a layout and the slide-in animation (this currently doesn't render the top 50px of the ImageView):
Layout:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="#+id/QuickPlayClipLayout">
<ImageView android:id="#+id/Clip"
android:background="#drawable/clip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_y="-50dp">
</ImageView>
</AbsoluteLayout>
Animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="1000"/>
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
</set>
Instead we can simple give negative values to layout_margin(Top/left/right/bottom)
eg: If you want your view to be off from top of the screen you can specify
android:layout_marginTop="-40dp"
I figured out a solution to this that should be easy to implement. It involves modifying the layout and the Activity inflating the layout... see below:
Activity (QuickPlay.java):
public class QuickPlay extends Activity implements AnimationListener
{
private ImageView myImageView;
private LinearLayout LL;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.quick_play_screen);
myImageView = (ImageView) this.findViewById(R.id.Clip);
LL = (LinearLayout) this.findViewById(R.id.QuickPlayClipLayout);
//finally
Animation anim = AnimationUtils.loadAnimation(this, R.anim.slide_in_quickplay);
anim.setAnimationListener(this);
LL.startAnimation(anim);
}
#Override
public void onAnimationEnd(Animation animation){}
#Override
public void onAnimationRepeat(Animation animation){}
#Override
public void onAnimationStart(Animation animation)
{
// This is the key...
//set the coordinates for the bounds (left, top, right, bottom) based on the offset value (50px) in a resource XML
LL.layout(0, -(int)this.getResources().getDimension(R.dimen.quickplay_offset),
LL.getWidth(), LL.getHeight() + (int)this.getResources().getDimension(R.dimen.quickplay_offset));
}
}
New LinearLayout (CustomLinearLayout.java):
public class CustomLinearLayout extends LinearLayout
{
private Context myContext;
public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
myContext = context;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec+((int)myContext.getResources().getDimension(R.dimen.quickplay_offset)));
}
}
Layout (/res/layout/quick_play_screen.xml):
<?xml version="1.0" encoding="utf-8"?>
<com.games.mygame.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="#+id/QuickPlayClipLayout">
<ImageView android:id="#+id/Clip"
android:background="#drawable/clip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ImageView>
</com.games.mygame.CustomLinearLayout>
Resource (/res/values/constants.xml):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="quickplay_offset">50dp</dimen>
</resources>
Animation (/res/anim/slide_in_quickplay.xml):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="1000"/>
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
</set>
The program now does exactly what I need it to do. The entire layout starts off screen at the bottom, slides in in 1 sec and comes to a rest where the top of the layout is actually 50px off the top of the screen (i.e. LL.getTop() = -50) and the bottom of the layout is resting at the bottom of the screen (i.e. LL.getBottom() = 530 = 480 + 50).
To position my view offscreen I used the following code:
View myView = /* view you want to position offscreen */
int amountOffscreen = (int)(myView.getWidth() * 0.8); /* or whatever */
boolean offscreen = /* true or false */
int xOffset = (offscreen) ? amountOffscreen : 0;
RelativeLayout.LayoutParams rlParams =
(RelativeLayout.LayoutParams)myView.getLayoutParams();
rlParams.setMargins(-1*xOffset, 0, xOffset, 0);
myView.setLayoutParams(rlParams);
This code will position myView offscreen by amountOffscreen, which in this case puts 80% of the view offscreen leaving only 20% onscreen.
Do not use the layout() method directly - Android will make subsequent calls to invalidate your view for random reasons and only layoutParams are persisted across invalidate calls. If you are curious, check out lines 904 to 912 of this file to see why you have to modify the layoutParams.
This is easy to do if you make to leap to using a Canvas; those support drawing off the screen without any trouble. However, it will be a more complicated to implement. You'd need to implement a custom View and write your own animation in code. Essentially this comes down to simple 2D graphics handling rather than Views using built-in XML animations.
There may be a way to do it with XML, but I'm much more familiar with canvases. A very good place to see how this is handled in code is the Lunar Lander example game that comes with the SDK.
Roughly the steps you'll need to follow are:
Put a custom view in the XML file, using something like <your.package.AnimatingView>, setting its size to fill-parent.
Then define a AnimatingView class, which extends SurfaceView and implements SurfaceHolder.Callback. (This gives you access to the drawing Canvas immediately rather than by using the invalidate() method. This is important because invalidate() only refreshes when the thread is idle, e.g. at the end of the loop. To implement your animation, you need to have it drawing immediately.)
You can then implement a loop which draws your moving image across the screen. The loop needs to start by drawing the whole background (because the canvas doesn't automatically get erased) and then draw the image at its new position based on the time that has passed. For example, if you want your animation to take 1 second to do, then you know that if 200ms have passed, the view should only have moved 200/1000, or 1/5, of the way from its starting position to the final position.
You can see some examples of what I mean in my other answers to animation questions: basic reply about the usefulness of using SurfaceView and and example of the loop to use. Note: the second question was about rotating, and hence some of the difficulties I talked about won't be relevant to you. Good luck!
With android:translationX and android:translationY
<RelativeLayout
android:translationX="-600dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
I had a viewgroup with children and was dragging the view into the screen from the bottom - kind of like a drawer. I then would let go and if the viewgroup top margin were in the top half of the screen I would animate it to the top after the user released the touch.
When this happened the images in the viewgroup's children would get cropped during the animation but would then get shown after the animation.
The problem: viewgroup's height was wrap_content. I solved this by setting the height to a value that stretched off the screen before the animation started.