Making smooth animations - android

I have a property animation, but it's not smooth, i.e I'm getting jumps in the animation. I tried to play with the parameters for the animator, such as the interpolator, the duration, and the frame delay, but can't get a smooth effect. Does anyone have some tricks / examples? Here's my current code for setting the animator:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
rotationAnimator.setInterpolator(new LinearInterpolator());
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
(There's also a call to setFloatValues, but it comes later in the code, before I start the animation)
EDIT: I was asked to show what I'm animating, so here it is:
That's the setter function that gets the value from the animation:
public void setMapRotation(float rotationDegrees)
{
if ((rotationAnimator!=null)&&(rotationAnimator.isRunning()))
rotationAnimator.cancel();
rotationDegrees%=360;
if (rotationDegrees<0) rotationDegrees+=360;
rotationView.setRotationDegrees(rotationDegrees);
if (rotationDegrees!=oldRotationDegrees)
{
notifyRotationListeners();
oldRotationDegrees=rotationDegrees;
}
}
If you look at the code, you'll see there's another function that gets called (setRotationDegrees), so here it is:
public void setRotationDegrees(float rotationDegrees)
{
this.rotationDegrees=rotationDegrees%360;
if (this.rotationDegrees<0) this.rotationDegrees+=360;
Log.i("MapView","View degrees: " + this.rotationDegrees);
invalidate();
}
And that's what happens after the invalidation:
#Override protected void dispatchDraw(Canvas canvas)
{
Log.i("MapView","Redrawing");
int saveCount=canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(rotationDegrees,getWidth()/2,getHeight()/2);
canvas.setDrawFilter(drawFilter);
canvas.getMatrix(rotationMatrix);
super.dispatchDraw(canvas);
canvas.restoreToCount(saveCount);
}
I don't know if there's something particularly heavy here, but I may be wrong...

Defining an AnimatorSet and set the Interpolator on it may solve your issue:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.getChildAnimations().add(rotationAnimator);
animatorSet.setInterpolator(new LinearInterpolator());
...
animatorSet.start();

I've resolved by adding HW acceleration directly to my animated view and view container (myViewContainer - in my case a RelativeLayout):
if(!myViewContainer.isHardwareAccelerated())
{
myViewContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView1.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView2.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}

Jumps in the middle of the animation are more likely to be caused by either something in your rotationController class, that is receiving the animated value "mapRotation". Or it could be something to do with your layout.
I would imagine that there is something quite intense, memory or cpu wise, that is causing the juttering.
If your animating something that has lots of layouts then that could be the cause. If in your code you have lots of findViewByID that is getting called as a result of your animation then that to can cause a slow down.
If your just animating a simple image, then im not entirely sure what the issue would be.
Personally i think your code looks fine, perhaps show/describe what your animating.
[edit]
I have limited knowledge of animating with the canvas so i took a quick look at how you were using canvas.save() and canvas.restoreToCount() which i hadn't seen before.
It all looks good though i do find it odd that several tutorials rotate the canvas rather than the ball image to create the rotation. Still, the only reference i found to improving the animating was a comment in the following link that suggested using postInvalidateOnAnimation(). Not sure where though, perhaps instead of invalidate().

Related

Trying to reset values from Property Animator to be used in recycler view

Been doing some animation inside of a row in RecyclerView (not the row itself. Imagine expanding text) and there are times when the animation leaks to other recycled views which should not have that animation in them.
Since I use property animation the scale action resizes the inner view and the leak can be seen in 2 aspects:
1) The animation will go on (This I could overcome with some guards)
2) The view has been resized and stopped in its tracks and so it will be reflected in the recycled view.
How can I reset the views to their original state? I have tried many approaches from posts but none solved it. The closest definition I got was in this unanswered post:
How to reset view to original state after using animators to animates its some properties?
Here is a sample of how I set up my animation in onBind (this one has an attempt to use onAnimationEnd which I found in one post but did not work)
ObjectAnimator scaleXUp = ObjectAnimator.ofFloat(mView, View.SCALE_X, 10f);
scaleXUp.setRepeatCount(ValueAnimator.INFINITE);
scaleXUp.setRepeatMode(ValueAnimator.REVERSE);
scaleXUp.setDuration(700);
ObjectAnimator scaleYUp = ObjectAnimator.ofFloat(mView, View.SCALE_Y, 10f);
scaleYUp.setRepeatCount(ValueAnimator.INFINITE);
scaleYUp.setRepeatMode(ValueAnimator.REVERSE);
scaleYUp.setDuration(700);
mTotalAnimation = new AnimatorSet();
mTotalAnimation.play(scaleXUp).with(scaleYUp);
mTotalAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mTotalAnimation.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
animation.removeListener(this);
animation.setDuration(0);
for(Animator va : ((AnimatorSet)animation).getChildAnimations()) {
((ValueAnimator)va).reverse();
}
}
});
mTotalAnimation.start();
And here is what I do in the onUnbindData:
if (mTotalAnimation != null) {
mTotalAnimation.end();
mTotalAnimation = null;
}
And as I saw many people like the clearAnimation approach - tried and did not work either.
4 days and not one response but meanwhile I solved it myself.
So the approach was close, just wrongly placed.
I added this method:
private void stopAnimation() {
for (Animator anim : mTotalAnimation.getChildAnimations()) {
((ObjectAnimator) anim).reverse();
anim.end();
}
}
and I call that when I want to reset the view.
Here I am getting the animations from the AnimatorSet and reversing and ending each. I did not understand why I had to do it manually but it looks like this ability will be added in Android O:
https://developer.android.com/preview/api-overview.html#aset
Starting in Android O, the AnimatorSet API now supports seeking and playing in reverse. Seeking lets you set the position of the animation set to a specific point in time. Playing in reverse is useful if your app includes animations for actions that can be undone. Instead of defining two separate animation sets, you can play the same one in reverse.

Android RippleForeground bounded mode implementation

I'm reading through the implementation of RippleDrawable and RippleForeground (the software rendering part), and I already knew that being bounded means that the ripple has a mask with it.
But I'm still confused on some points of the implementation:
Why did the implementation say "Bounded ripples don't have enter animations" and simply skip the enter animation for it? How can the ripple animation be started in this case (if user did not release his touch so no exit is fired)?
#Override
protected Animator createSoftwareEnter(boolean fast) {
// Bounded ripples don't have enter animations.
if (mIsBounded) {
return null;
}
...
}
Why did the implementation pick a nearly constant value (and why is that random()) for mBoundedRadius and mTargetRadius? What if the view masked with ColorDrawable is larger than that size, will it work correctly?
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
boolean isBounded) {
...
if (isBounded) {
mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
+ (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
}
...
}
...
private void computeBoundedTargetValues() {
...
mTargetRadius = mBoundedRadius;
}
For the first question, I've found the answer myself by digging into commit history and trying the new Marshmallow image. The answer is simple:
They removed the (foreground) ripple on touch for bounded RippleDrawable, but not for unbounded, leaving this inconsistency deliberately.
I just tested on the Marshmallow image from Android SDK. It is removed, and even worse, they left the exiting ripple in the place user first touched the screen instead of where their finger lifted from the screen.
I cannot understand this design decision since it seems like a regression much more than an improvement to me, but as in the commit log, they believe they did implement bounded ripple animation, instead of removing it.
But for the second question, I still haven't got an answer yet.

Options for endlessly repeating function GUI calls while remaining responsive

I've got an animation to perform which consists of some arrow heads aligned horizontally where the alpha values of the arrows will change to achieve an animation effect (i.e. first arrow has alpha 1.0, then the second will get a value 1.0 etc.).
So if I have a function like this:
void highlightFirstArrow()
{
mArrow1.setAlpha(1.0f);
mArrow2.setAlpha(0.75f);
mArrow3.setAlpha(0.50f);
mArrow4.setAlpha(0.20f);
}
Then I'd want to start, repeat numerous times, then stop a function such as this:
void animateArrows()
{
highlightFirstArray();
pause;
highlightSecondArray();
pause;
etc.
}
Obviously this would lock up the GUI thread if it were performed in a for look for example. What are the options for achieving the desired animiation:
- run a for loop in a separate thread
- don't use a loop, instead constantly execute the functions individually via a timer
- use built in specific android animation mechanisms. If so which is most appropriate? Would AnimatorSet() be good for this scenario, or something else
You definitely shouldn't use any loops or timers. There're lots of built in classes which could help to animate your views. For instance you can use ValueAnimator:
ValueAnimator.ofFloat(1f, 0.2f).setDuration(1000).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
arrow1.setAlpha(value);
arrow2.setAlpha(value);
}
});

Using animation on a ViewPager and setFillAfter

I have a ViewPager which I need to move as a whole on button press. I use an animation for this.
When I press it, I translate the 'x' for it. I use setFillAfter(true) to keep the new position.
But when I change the page of the ViewPager, it jumps back to the original x-position!
I only saw this issue on Android 4.1, with Android 4.0 there is no problem! So it looks like some kind of regression in Android.
I attached a testproject where I could reproduce the issue without all my other stuff around it. I think it is best if you want to help me figure this out to import the project in your Eclipse and see it for yourself.
I also added to video's, one on my HTC One X where I see the issue, and the other on a tablet with Android 4.0, where the issue is not there.
I have been desperately looking to fix this ugly side effect, but no luck till now...
(Sorry for the big movie files...)
Video of Android 4.0 without the side effect
Video Android 4.1 with the side effect
the project where you can reproduce the issue with
Edit:
I added the following:
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!i)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
}
After that it stays at the correct position, but it 'flickers' quickly, like the animation is still showing at the end and when I change the margin, it still shows the offset it had after animation. Then it jumps to the correct position.
The main problem seems to be incorrect choice of animation type. You see, View Animation as a tool is not intended to be used with complex interactive objects like ViewPager. It offers only low-cost animation of the drawing place of views. The visual behaivior of the animated ViewPager in response to user-actions is undefined and should not be relied on.
Ugly flicks, when you substitute a "gost" with the real object are only natural.
The mechanism, that is intended to use in your case since API 11 is specialized property animator built in Views for optimized performance: ViewPropertyAnimator, or not specialized, but more versatile ObjectAnimator and AnimatorSet.
Property animation makes the View to really change its place and function normally there.
To make project, to use, say, ViewPropertyAnimator, change your listener setting to this:
btn.setOnClickListener(new OnClickListener() {
boolean b = false;
#Override
public void onClick(View v) {
if(b) {
myViewPager.animate().translationX(0f).setDuration(700);
}
else {
myViewPager.animate().translationX(300f).setDuration(700);
}
b=!b;
}
});
If you want to use xml configuration only, stick to |ObjectAnimator and AnimatorSet. Read through the above link for further information.
In case, you are anxious to support pre-Honeycomb devices, you can use Jake Warton's NineOldAndroids project. Hope that helps.
That's because the Animation's setFillAfter(true) doesn't actually change the position or any attributes of the View; all it does is create a Bitmap of the view's drawing cache and leaves it where the animation ends. Once the screen is invalidated again (ie. changing the page in the ViewPager), the bitmap will be removed and it will appear as if the View is returning to it's original position, when in fact it was already there.
If you want the View to retain it's position after the animation has finished, you need to actually adjust the View's LayoutParams to match your desired effect. To achieve this, you can override the onAnimationEnd method of the Animation, and adjust the LayoutParams of the View inside there.
Once you adjust the LayoutParams, you can remove your call to setFillAfter(true) and your View will actually stay where you expect it to.
Regarding the flicker issue:
I have encountered this issue before, and it stems from the possibility of the onAnimationEnd() call not syncing up with the next layout pass. Animation works by applying a transformation to a View, drawing it relative to its current position.
However, it is possible for a View to be rendered after you have moved it in your onAnimationEnd() method. In this case, the Animation's transformation is still being applied correctly, but the Animation thinks the View has not changed its original position, which means it will be drawn relative to its ENDING position instead of its STARTING position.
My solution was to create a custom subclass of Animation and add a method, changeYOffset(int change), which modifies the y translation that is applied during the Animation's applyTransformation method. I call this new method in my View's onLayout() method, and pass the new y-offset.
Here is some of my code from my Animation, MenuAnimation:
/**
* Signal to this animation that a layout pass has caused the View on which this animation is
* running to have its "top" coordinate changed.
*
* #param change
* the difference in pixels
*/
public void changeYOffset(int change) {
fromY -= change;
toY -= change;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float reverseTime = 1f - interpolatedTime;
float dy = (interpolatedTime * toY) + (reverseTime * fromY);
float alpha = (interpolatedTime * toAlpha) + (reverseTime * fromAlpha);
if (alpha > 1f) {
alpha = 1f;
}
else if (alpha < 0f) {
alpha = 0f;
}
t.setAlpha(alpha);
t.getMatrix().setTranslate(0f, dy);
}
And from the View class:
private int lastTop;
// ...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// the animation is expecting that its View will not be moved by the container
// during its time period. if this does happen, we need to inform it of the change.
Animation anim = getAnimation();
if (anim != null && anim instanceof MenuAnimation) {
MenuAnimation animation = (MenuAnimation) anim;
animation.changeYOffset(top - lastTop);
}
// ...
lastTop = top;
super.onLayout(changed, left, top, right, bottom);
}
Crucero has it right about setFillAfter not adjusting params post invalidation. When the view is re-layed out (which'll happen the pass after it's invalidated), its layout params will be the ones that always applied, so it should go back to the original position.
And Jschools is right about onAnimationEnd. Strongly encourage you to step through the source code with a debugger, where you'll instructively discover that an update is made that affects the drawn position of the view after onAnimationEnd is fired, at which point you've actually applied the layout params, hence the flicker caused by doubled up offset.
But this can be solved quite simply by making sure you relayout at the right time. You want to put your re-positioning logic at the end of the ui message queue at the time of animation end so that it is polled after the animation but before laying out. There's nowhere that suggests doing this, annoyingly, but I've yet find a reason in any release of the SDK reason why (when doing this just once and not incorrectly using ui thread) this shouldn't work.
Also clear the animation due to another issue we found on some older devices.
So, try:
#Override
public void onAnimationEnd(final Animation animation) {
myViewPager.post(new Runnable() {
#Override
public public void run() {
final RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!someBooleanIPresume)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
myViewPager.clearAnimation();
}
}

Why is my animation leaving a trail?

I'm animating a an ImageView from the left to the right of the screen using a translate animation. The ImageView is place inside a RelativeLayout over the top of my main layout using FrameLayout.
When I run the animation on the emulator everything works pretty well but when I use run it on my G1 it leaves visual artifacts behind and effects the rendering of the text component behind it.
Is this a performance issue and I'm being too ambitious or is it a bug I can overcome?
If it is a performance issue is there anything I can do to improve things?
I was also experiencing the same issue on 2.3.
Invalidating the container of the moving view ( the layout in which the moving view resides ) in Animation.applyTransformation fixed it for me.
See:
Android - Artifacts using Animation
I now this may be a little old, but I just found this:
http://groups.google.com/group/android-developers/browse_thread/thread/5481450f8b71a26c/e750730b9953d9a8?lnk=gst&q=animation+leaves+trails#e750730b9953d9a8
Not sure what android version your using, but it may be a bug in the android libraries!
Looks like that's what the problem is for me! :)
... Dontcha just love it when its not your fault! :D
Here's a workaround I found that solved this for me: "An easy workaround would be to pad your image with a small (1 pixel should do it) transparent region on the right/bottom - this would have no effect on how it would look, but it would force an invalidation of a region slightly larger than the actual image and thus compensate for the bug."
http://code.google.com/p/android/issues/detail?id=22151
Without actually seeing the problem is sounds like you're not clearing the display buffer before writing the next frame. It doesn't sound like a performance issue to me.
Do you have control over whether the device does double buffering or not?
Given that it works on the emulator this could point to either a problem with the emulator or a bug in your code that isn't showing up on the emulator (which I suppose is technically a problem with the emulator!) rather than a performance issue.
I would suggest using a SurfaceView for animation. It is double-buffered, so it should eliminate flickering if you use it properly. If you want an example, the LunarLander demo included in the sdk shows this really well. Also, if you have a more specific question with code, ask away.
As for general Android performance, it is very possible to have reasonably high frame rates, so you aren't expecting too much.
This is happening to me as well. I'm using an emulator using 1.6 with the Google APIs, and I just confirmed that it happens on a Nexus One running FRF83. Here's the relevant code:
Animation a = new TranslateAnimation(0.0f, 0.0f, 100.0f, 0.0f);
a.setDuration(2000);
this.myView.startAnimation(a);
Here's the relevant code for instantiating the view:
View v = new View(this.getApplication());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 80);
v.setLayoutParams(params);
v.setBackgroundColor(0xFFFF0000);
//
LinearLayout layout = (LinearLayout)this.findViewById(R.id.theLayout);
layout.addView(v);
//
v.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
// TODO Auto-generated method stub
doAnimation();
}
});
//
myView = v;
So basically, the double buffering etc, is being handled by the OS, and I have no control over it at all.
I had a similar problem on Android 2.3, so the bug may still in exist.
I was using an ImageView with a PNG which had some transparent parts. This imageview was leaving trails when animated with TranslateAnimation. Using a fake background drawable for the imageview elimanated the trail (I used a drawable as background).
I figured this out on Jelly bean i was experiencing this in a gallery view while performing some animation. It looks more like a drawing issue not cpu ....
make your activity implement the interface AnimatorListener ..... override the below method and pick which one you want to redraw your view in
#Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
mView.requestLayout();
Toast.makeText(this, "animation ended", 0).show();
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
mView.requestLayout();
}
#Override
public void onAnimationStart(Animator animation) {
// TODO Auto-generated method stub
mView.requestLayout();
}

Categories

Resources