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)
Related
I have a base layout that holds several views. I need to translate them along the x axis. Basically there are 2 types of translations happening the one that follows the finger all the way and the one that stops after certain threshold which gives a nice parallax-like effect.
So is it better to put the group of views that share same translation logic inside another container and translate the container or translate each view individually?
I know this might sound weird but Google always recommends to use flat view hierarchy because nested layouts are expensive to redraw, so does it mean that keeping just one layer of nesting and translating each view by hand is better?
Also this translation is applied to all the views in the RecyclerView, not just one.
I think you should keep a flat layout as Google recommends, and translate each view individually. I would create a custom view were you can set a threshold. And once set, you can simply pass the x position of the finger to them all, and they'll decide if they should move or not.
How to implement it requires more input or requirements before giving any suggestion. Besides performance problem, code maintainable should be considered too.
Base on my experience. With high-end phone and high API level (about >= 21), they have better hardware, effective way to manage memory and background task. The problem expensive to redraw you mentioned seems not too important with some simple animation. In this situation, code maintainability has higher priority, I will decide to have an extra FrameLayout container, wrap the View which have the same animation because you have less code => less logic. 0 or 2 FrameLayout have no noticeable difference in performance in this case
About low-end devices, if you want to target lower API users, performance becomes a top priority. Now both cases you mentioned have the effect on performance, one requires more memory to store more View and one requires more CPU to run animation. It's time for a trade-off. In your case when you have 2 translate animations run on any item in RecyclerView, I prefer to create 2 separate animations run on 2 View. This way I can save a bunch of extra containers => save memory, the animation is not run for all of the items so it only affects the CPU for a small amount of time during animation.
So to sum up, You have a different approach for each case, choose a top-priority for the case you choose, improve it, sacrifice the others that have less effect on the overall problem. No solution is 100% perfect, trade-off situation always happens when coding
I am working on a game for the Android platform. The layout consist elements which should move without stopping from top to bottom. I decided to use an Handler and I set handler.postDelayed(this, 10).
the animation "step" is 5px (It's actually not px but doesn't matter for the discussion).
The thing is, the animation frequently twitching and isn't smooth. With every call of the handler, I make some simple checks and use setX(), setY() to change the elements position.
What should I do to make the animation smooth? Should I tweak the numbers mention above? Also, I've understood the method setWillNotDraw(boolean b) might be useful - How to use it wisely?
I'd be glad if you could guide me what should I do (Kind of "Do and Don't").
Thank you!
As pskink suggests ViewPropertyAnimator is much friendlier than animating Views yourself. If you want/need to manage it manually, consider View.postOnAnimation instead of Handler.postDelayed to run your update with new animation frames.
Regardless of how you move objects, be wary of how many views you have on the screen. Moving one view can force the entire view hierarchy to redo its layout -- this can definitely kill animation speeds. Try animating a single view in an otherwise empty layout.
Profiling is very helpful for tracking down performance issues.
This is the situation
- My root layout is scrollview
- A custom view with multiple drawing operations is embedded in the layout
My problem is that if the layout is scrolled then customview.ondraw() is being called multiple times. Since there are many operations in the ondraw() I am getting a performance hit.
What is the best strategy out of this?
See the docs on View.setDrawingCacheEnabled(). Note you have to call getDrawingCache() in your onDraw implementation, and draw the returned bitmap if getDrawingCache() returns a non-null value. I'm not sure whether ScrollView will take care of calling setDrawingCacheEnabled automatically. You'll have to try it and find out. I think it does; but you still need to call getDrawingCache() in your onDraw routine.
That takes care of the non-hardware accelerated cases.
For hardware accelerated views, see the docs for setLayerType. It's far from a given that caching hardware accelerated views actually provides a performance improvement, so do this cautiously.
Forgot the name of the methods in Android, but there is a Swing demo with a split pane.
If there is a flag set to true ( update continuously) than the split panel is drawing wile you move the spit components. The same effect is at scroll too. You can use a draw, continuously and another only when the scrolling finished. The same thing is at iOS too.
You need to find which are those methods and embed your custom view drawing when is needed.
I have two views in a ViewFlipper and have a fast/complex animation between them. The second view contains a list, so at the moment that view become visibile half way through the animation, getView() is called a bunch of times and causes a very noticeable stutter (usually stalling for the entire second half of the animation)
Ideally I would like to pre-render (measure, layout, draw) the second view before starting the animation, but I have not found a simple way to do this.
I have also explored using the drawing cache, off-screen canvases, etc - but I cannot find a simple way to achieve this either. Seems to be a problem anyone animating between two views would have. Any help?
You can do that by specifying a layer type and calling buildLayer() before you start your animation, which forces rendering the view.
newView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
newView.buildLayer();
You could also use LAYER_TYPE_SOFTWARE. The default value is LAYER_TYPE_NONE, which prevents buildLayer() to do anything.
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.