The app I work on animates map markers after they are added to the screen using ObjectAnimators. The animation replaces the icon with progressively larger or smaller bitmaps. About 20% of the time, it will randomly cancel the animation, leaving it only partially enlarged or shrunk. I know it's getting canceled because a) the animation doesn't complete and b) if I add an AnimatorListener, then onAnimationCancel() is called in those times.
It doesn't seem to be related to garbage collection, to other pins (it can happen when only one pin is placed on the screen), to other code that runs while the animation is in progress.
They are not set to autoCancel, and even if they were, it wouldn't be triggered because multiple identical animations are not being started.
Any idea at all why this would happen? Has anyone ever seen animations being canceled randomly and unexpectedly?
Well, I feel like an idiot--I finally realized why this was happening!
ObjectAnimators only have a weak reference to their target object.
If you don't maintain a separate reference to the object being animated in some other variable, then sometimes it will be garbage collected before the animation is finished. The next iteration of the ObjectAnimator will attempt to update the object, fail because the target object is null, and then it will cancel the animation.
The solution, then, is simply to ensure you have a variable referencing that object, at least until the animation is complete.
Hope this helps--I'm sure I can't be the only one that's run into this issue!
Related
I have a pretty complex android-application. Already flattened view-hierarchies as far as possible, but I still have lags in the application. For example there is a menu with entries that collapse/expand by having their height set by a ValueAnimator. Typically the animation runs with a bit of a lag the first time, and smooth after this first pass.
I noticed that when i call "requestLayout()" on the Menu-Item, Android seems to do a layout-pass and multiple measure-passes through the entire hierarchy.
Since i know that although the Menu-Item(View) changes height, the Menu(View) itself doesn't, is there some way to tell this to the application?
Can i somehow perform this first pass that seems to lag myself so that it occurs after application start-up and not at the first touch-input?
Here's a sketch of the animation I'm doing:
I am not sure why a layout is being triggered in your animation but I am going to answer your question abstractly.
If you are calling requestLayout (either directly or indirectly) in your animation you are doing it WRONG.
requestLayout, for correctness and safety, does a full view traversal on the view hierarchy b/c conceptually changing bounding box of a node in the view hierarchy can result in change in the bounds of any other node. Not always the case but in general it could, thats why requestLayout is a full traversal.
All of this is just another way of saying requestLayout will eat away time from your 16.6 ms frame time slot and make your animation choppy. This is especially bad for deep and complex hierarchies with many RelativeLayouts which internally does two passes per level (thus potentially causing exponential passes on a subtree)
Now, if you want to animate change in dimension use setScale in a hardware layer. And at the end of the animation merrily call requestlayout and also destroy the layer (to free up memory).
Because its a layer, repeatedly calling setScale in your animation results in change of the texture on the GPU and as a result totally bypass the traversal mechanism of the view hierarchy. This should make it buttery smooth.
Your question looks like mine: Only relayout children and not all the tree
First, you can try to avoid complex view hierarchy for your view. If possible, explode views on views that doesn't depend on another one.
When an animation is performed, avoid any layout request. Start your animation with a delay if a layout request is pending.
Use hardware layers for animation if possible (maybe Android use it by default with ValueAnimator)
I'm currently trying to work out how to properly use hardware layers when animating views.
I'm implementing a ViewGroup that enables the user to drag a child view, after which I animate it to a position when they release (like how ViewPager will settle on a page). This article states that you should only enable hardware layers for the duration of the animation.
The issue I'm having is that after hardware layers are enabled Android has to create the layers, which takes 70-100ms on a Galaxy Nexus. This means I can't do it immediately before starting the animation, as having the delay between the drag and the animation would be very noticeable. I also can't enable it when starting a drag for the same reason.
Now, this delay is only present the first time hardware layers are created, so ideally I would want them to be created as soon as the views are added to the layout. I've been pointed to View.buildLayer(), but I'm unsure how to approach this.
What would be the correct way to achieve this?
Are there any methods in my ViewGroup I can override and call buildLayer() on the child views?
Can the hardware layer be invalidated in some way, causing another 70-100ms delay? And how do I handle this?
The delay happens when there's no layer in the cache, you should not see this delay for subsequent calls to setLayerType(NONE)/setLayerType(HARDWARE). You could call buildLayer() from onSizeChanged() to force a layer to be built and then put in the cache (call setLayerType(NONE) to move the layer to the cache.)
Note that the delay you are seeing depends greatly on the device you are running on.
The reason why you shouldn't keep layers enabled is that it doubles the amount of drawing work every time the view update. For instance, if you move a ListView into a layer and then scroll the list, each frame update during the scroll animation will cause: (a) the list to repaint into the layer (b) the layer to be drawn on screen. It's extremely wasteful and may cause performance issues depending on the complexity of your UI.
I've noticed my whiteboard app becomes 'dormant' after a few seconds of no interaction, so that when something is newly drawn, there is horrible lag. This is without switching away from the app at all - I run it, it's there, I can draw and undo. But if I don't use it for 10 seconds and then try and draw a circle, there can be a second or so of wait before it updates and I get a straight line from start point to current position. So the code to start drawing is instant when the view is touched, but the updates aren't happening for a good while yet.
The code is effectively the Fingerpaint demo drawing onto a custom bitmap, and I copy this bitmap to the view. I undo by copying the bitmap just before drawing starts. I have disabled the undo code to see if that was the issue, but the delay is still there, only far less pronounced. I notice the same lag sometimes happens in the Fingerpaint API demo.
Anyone know why this is? Is there some odd setting I don't know about which causes an active app to go to sleep?
I have several ImageViews each with an indefinitely throbbing ScaleAnimation and three attempts that don't make sense to me.
If I set the animation immediately after inflating the animation the animation runs perfectly. This also tells me my helper function to create the animation (used in all three cases) is working fine.
If I set the animation immediately, but with a start time of Long.MAX, and later call icon.getAnimation().start(), that animation starts but the speed is faster than it should be and the scale is not what I set. No matter what I set the animation scale and duration parameters to, it always looks the same, unlike the previous case.
If I call icon.startAnimation(..) with a new Animation object, I get no animation. Digging into Android source, startAnimation(..) calls invalidate(), unlike setAnimation(..). If I call icon.getAnimation().start() followed by icon.invalidate() I get the same (lack of) behavior. This hints to me that the invalidate somehow breaks the animation.
So, given all of the above, is this symptomatic of a mistake I might have made? Is there something I didn't describe that I should be doing?
Finally, I've tried this with both a scale pivot specified both relatively and aboslutely, thinking there might be a bug in event order with respect to View.invalidate() and Animation.reset().
A lot of people are having problems with animation. Working with animation on anything before Honeycomb is a real pain.
I recommend downloading the NineOldAndroids library and using it as imports, that way you can use the Honeycomb animation API on all versions of android (even up to 1.0).
In my app I am drawing bus routes on top of a MapView. The routes have anywhere between a dozen and a few hundred GPS coordinates that describe the route that the bus takes.
The problem I'm having is that once I draw out all these lines panning/zooming the MapView is incredibly slow (even clicking the 'Back' button takes a minute to happen).
I'm not sure how relevant it is, but I put in some debug code then checked the logcat output and the MapView is repeatedly calling the draw() method of the Overlay whether anything has changed or not. This is happening several times a second and is causing a massive amount of garbage collection to happen (2-3 MB every second).
Does anyone have any ideas/suggestions for a method to try and speed this up?
I have only used ItemizedOverlay, not Overlay, so these suggestions are pure conjecture. Heck, I haven't even done much with the 2D graphics API.
Obviously, the best answer is to get it to stop calling draw() all the time. Try logging some stack traces and see if you can figure out what is triggering all of the draw() calls. For example, in the Android Google Groups recently, somebody noticed that Chronometer causes widgets in the same UI to be redrawn every second. While I can see you don't have a Chronometer, you might be able to figure out some root cause to the draw() calls that you can correct.
Assuming that does not help, I am guessing that the test for "whether anything has changed or not" is some combination of getLatitudeSpan(), getLongitudeSpan(), getZoomLevel(), and maybe other MapView methods. And, I am assuming that on every draw() you are iterating over your GPS points and drawing the route. If so, you could try:
When you really do draw, draw first to a Canvas backed by a Bitmap, then apply the Bitmap on the Canvas you are handed in draw(), and cache that Bitmap.
Track what combination of values were used in the last draw(), and if the next draw() is the same, just reuse the existing Bitmap. Else, go to step #1, making sure to release the Bitmap (or reuse it, if that's possible).
I am guessing that with graphics acceleration, blasting a Bitmap onto the Canvas is cheaper than iterating over the coordinates and drawing lines. And, by caching the Bitmap, you will save on garbage generation.
Anyway, just a thought.
There are two draw methods in the overlay class. One with 3 arguments and one with 4 arguments. You have to override the draw method with 3 arguments.
Overriding the method with 4 arguments will slow down your application. This is exactly, what happened to me. It seems, where are examples around in the internet with the same error.