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);
}
}
}
Related
How I can reach such behavior with a smooth slider?
I want that first ImageView to increase his width and cover over second ImageView.
I tried three different approaches to this purpose.
First I drew bitmap (two bitmaps combined inside one canvas) inside the canvas of one ImageView. New position generated with ValueAnimation but it gives values with not equals intervals. During rendering the transition do jumps noticeably enough. On a good phone especially.
Second I did my own method for generating sequence numbers without intervals and but occurs another problem: phone cant render new bitmaps a few hundred times per second. If to do intervals jumps occur again.
Then I tried another way: to create two ImageView and first must smooth roll out over the second. But I can't reach the proper effect than the first picture must not stretch (image inside ImageView increased width together) but need a roll-out like on GIF.
Code of my third the attempt:
In fragment
val animation: Animation = AnimationUtils.loadAnimation(ctx, R.anim.scale_right)
imageView.startAnimation(animation)
imageView.visibility = View.VISIBLE
scale_right.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="6000"
android:fromXScale="0"
android:fromYScale="1.0"
android:pivotX="0%"
android:pivotY="0%"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
What do I need to do?
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'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.
Is it possible to hide image using scale animation in xml. What I mean is that I have an image on the screen and I want to hide it using scale animation in the direction from 0.0 -> 1.0. So the image is on the screen and I want to start scaling it from left to right until it disappear. Is it possible?
This is not what I want. You see my app is suporting devices below 3.0. And I don't want to move an image. I want to scale it, but in opposite direction. The scale animation works with the coords of the screen, so the image will be shown if you start from 0.0 to 1.0. But I want to hide it from 0.0 to 1.0. Is it possible?
First scaling means changing the size of the image which is not what you want! you want to change the position of an image. With property animation (supported in android 3.0+) you can animate any property of an object (even non visual properties).
use this code to animate any property
ValueAnimator move = ValueAnimator.ofFloat(0,100); //start and ending value
scaleUp.setDuration(300);
scaleUp.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float newValue = (Float) valueAnimator.getAnimatedValue();
myObject.setX(newValue); //in pixcels
}
});
move.start();
This code changes the value of "x" property from 0 to 100 over the period of 300ms. x and y are position of your view in pixels. of course you need to first calculate start and ending point in pixels.
findout more about "x" property here http://developer.android.com/reference/android/view/View.html#setX(float)
you can also animate setTranslationX istead http://developer.android.com/reference/android/view/View.html#setTranslationX(float)
As i said earlier with property animation you can animate any property you want and it's really easy to work with, to learn more about it read this http://developer.android.com/guide/topics/graphics/prop-animation.html
I managed to do what I wanted. I used scale animation with the tag pivotX. With pivotX or pivotY you can set from what point of the image to start the animation and in what direction. You have to play a little bit with it until you recive your desired animation. Here is my code:
<scale
android:duration="4000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="450"
android:interpolator="#android:anim/linear_interpolator"
android:startOffset="300"
android:toXScale="0.0"
android:toYScale="1.0" />
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.