I am working on animating custom Views for my Android app. I have accomplished this via Property Animations and calling invalidate() on the View in the onAnimationUpdate() callback, as per https://developer.android.com/guide/topics/graphics/prop-animation.html:
Depending on what property or object you are animating, you might need to call the invalidate() method on a View to force the screen to redraw itself with the updated animated values. You do this in the onAnimationUpdate() callback.
My problem is that when these animations are run at the beginning of a newly launched Activity, they skip frames at the beginning, causing them to jump very noticeably. I have tried both:
Starting the animation immediately from the Activity's onCreate() method
Starting the animation upon the OnGlobalLayout() callback using the ViewTreeObserver of the Activity's root view.
I did the latter since I thought maybe the animation was invoked before the layout had been finalized, but the result is the same.
With logs, I determined that the onAnimationUpdate() callback is called consistently throughout the animation (that is, every 10-20 ms or so, from start to end). onAnimationUpdate() simply calls invalidate(), which should force the View to redraw itself, ideally immediately (but the documentation only claims this happens "at some point in the future"). That seems to be precisely the problem: onDraw() is only called once or twice at the very start before not being called for about 250 ms. After this, it resumes being called every 10-20 ms, as it should have the whole time. But that block of time causes very noticeable lag in the animation.
To be clear, this problem only happens at the beginning of the Activity. If I simply set a 300 ms delay before starting the animation, it runs smoothly all the way through. But I don't like that solution, since it's hacky. It seems the problem is that onDraw() is not called immediately upon invalidate() near the beginning of the Activity. But, I can't figure out why this is, what's blocking onDraw(), or how to fix it at all.
I found only this StackOverFlow thread: Animation at the beginning of activity skips frames where the poster has the identical problem. The basic code is there and the videos make the problem clear. I can post my code too, but I think that the fact that the problem appears in the most basic test app shows that there's something else going on.
Since there are no codes attached, I'm assuming you have some transition animation to the activity. If this is the case, it might cause the problem.Since there are two animations running simultaneously. Disable the transition and give it try.
startActivity(intent);
getActivity().overridePendingTransition(0, 0);
It sounds like your draw loop UI thread is getting starved.
I'd use traceview to be sure that there aren't any methods blocking your draw calls. http://tools.android.com/tips/traceview
This should help you determine what's being invoked instead of the onDraw method.
Related
I am working on a connect 5 game. I have the board as a custom view. When a player playing against the engine makes a move the board does show the move until the engine makes its move. The engine moves relatively quickly so it. is not a big deal, but it is nonetheless noticeable.
I call invalidate() after the player's move is made, then I call the method to make the engine move on a different thread(AsyncTask). I was under the impression that after invalidate() is called the view refreshes itself, then the method to make the engine move would be called.
Thanks in advance for any help.
You're introducing race condition, invalidate() is propagated "upwards" in view hierarchy to notify your layout wants to redraw during next draw cycle. It is not guaranteed to trigger redraw instantly.
Whether you start your engine before or after invalidate() call doesn't matter, in either case it cannot be determined if it finishes before or after draw cycle.
If you want to have your layout refreshed the moment your AsyncTask finishes you should call invalidate() during result callback.
I have an activity which starts with a number of invisible views. I then use handler.postDelayed to make every view visible again with a slide animation and the BeginDelayedTransition method. Normally all the views should slide in, one after another. Anyway i noticed that depending on the time interval between each runnable and even depending on the device i test the app on, some of the animations are totally skipped and the view just appears without any transition. I think it may be related to the time it takes to beginDelayedTransition to calculate the changes in the visibility thus instantiating the correct animation.
Is anyone experiencing the same problem? Is there any workaround for this, other than calling a TranslateAnimation every time?
Thanks in advance!
I have this bizarre behaviour when calling invalidate.
I have made a custom viewgroup containing a bunch of imageviews.
I set up a onTouch listener with the on touch method to scroll the view.
I know all the code is correct in terms of distances to scroll etc. by using logcat.
Now, for some reason the onLayout method is not being called after I call this.invalidate(). When I try to scroll, nothing changes on the screen. However, after I pause the activity and resume it, the screen will have shifted by the amount I scrolled.
I am not blocking the UI thread because there is nothing to block it with. In any case, I have tried postInvalidate() and nothing has worked.
Anybody have any bloody idea what's going on?
invalidate() does not relayout the view, it only causes the view to be redrawn. If you want to relayout it (which will invoke onLayout()) you also have to make a call to requestLayout().
I put a Log.d() call into the onDraw() of my extended View, so I could see how often and when it's getting called. It gets called upon instantiation of the view, which is not surprising. But then I notice, it gets called on every tap that is handled by onTouchEvent(), even though my code there isn't doing anything remotely related to graphics. However, in the documentation for Views, I can't seem to find anything about when onDraw() is actually called. I'm not really concerned about my particular project here (this doesn't cause a problem for me), I would just like to know if there is a list somewhere or something that shows the order of operations for a View, particularly what causes onDraw() to get called.
AFAIK, a View's onDraw() is called when:
The view is initially drawn
Whenever invalidate() is called on the view
Invalidate can be called by you or the system whenever needed. For example, a lot of Views change how they look onTouch, like an EditText getting an outline and cursor, or a button being in the pressed state. Due to this, Views are redrawn on touch.
I agree that it would be nice to have a document that detailed the working of Views, and if one exists and somebody knows where to find it, please let us know.
onDraw() is called when invalidate() is called.
But you should know for ViewGroups: onDraw() will not be called like you expect. Rather, onDispatchDraw().
However, in a ViewGroup you can call setWillNotDraw(false) in the constructor to make onDraw() to be called on invalidate().
Take a look at this answer
If you set a background drawable for a View, then the View will draw
it for you before calling back to its onDraw() method.
onAttachedToWindow () is called when the view is attached to a window.
At this point it has a Surface and will start drawing. Note that this
function is guaranteed to be called before
onDraw(android.graphics.Canvas), however it may be called any time
before the first onDraw -- including before or after onMeasure(int,
int).
invalidate() mark the area defined by dirty as needing to be drawn. If
the view is visible, onDraw(android.graphics.Canvas) will be called at
some point in the future.
One important thing to keep in mind is that try to minimize calling of invalidate() function with no arguments.
Instead try to maximize the invalidate() function with four arguments.As drawing whole view is very expensive.The second variant refreshes only the part of view.
Additional to the above: The soft keyboard causes a View.invalidate()-->View.onDraw() sequence after resizing the Window to sensibly accommodate the 'keyboard'. A custom View.onDraw() must leave itself in a state that anticipates this possibility.
Such phenomenum explains why the app you developed and tested on a tablet with a bluetooth keyboard went to the dogs once it reached the real world (-:
When is onDraw called (check this for more details)
The onDraw method is called whenever android thinks that your view
should be redrawn. This can be tha case when your view is animated, in
which case onDraw is called for every frame in the animation. It is
also called when the layout changes and your view is re-positioned on
the screen.
But what if some data inside your view has changed and you want to
make sure that the view is redrawn. You can’t call onDraw directly.
Instead you should call invalidate to tell the view that it needs to
redraw itself.
I have an AnimationSet that does 3 different animations.
After the animations end, I would like to pause the app for 3 secs, before everything snaps back to start position.
How do I do that ?
Well, there is a animation.hasEnded() function you could use to test if the animation has finished. When that returns true, you could use a java timer to schedule a task after however many milliseconds you want to wait. AnimationSet also has a function to get the duration of the Animations.
Take a look at...
Animation
and AnimationSet
I think you could probably achieve this effect somehow with an AnimationListener Set one to get a callback when your animation ends and add a delayed runnable that will move everything back to the proper position for you. However I think it might get somewhat convoluted to do it this way since it would require setting fillAfter to true, then manually placing your Views in their original position inside your Runnable thats gets posted with a delay.
I think a more straightforward solution to get the same effect is to simply add a 4th animation to your set that has a delay to make it start after all of the others are complete. Make this 4th animation last for 3 seconds, and make it have no visible effect (i.e. grow by 0%). That should build in a 3 second pause for you and still handle moving all of your Views back to their original position (without the need to do it manually). With this solution you'd leave fillAfter set to false, and that would cause your Views to "snap back". By adding another animation that lasts for 3 seconds but has no visible effect it will seem like a pause to the user.