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.
Related
I just wrote a custom View class that, among other things, allows a developer to easily set borders (e.g. setBorderWidth, setBorderColor, setBorderWidthLeft, etc.). I did this by overriding onMeasure/onDraw and I want to test that the View properly draws these borders.
Ideally, I want something at a higher level than a unit test: basically I want to enforce that if I set borders, they are drawn as expected; if I don't set them they aren't drawn; if I change them, the new borders are drawn and the old ones are no longer visible. This allows me to know that my view is working at a high level.
Things I've considered:
Taking the view in isolation with Robolectric and calling onDraw manually with a mock Canvas (doesn't test invalidation though)
Making an Activity test case and somehow saving a screenshot of the Activity and analyzing it programmatically.
Neither of these seem great to me, but I'm leaning towards 2). Any other ideas?
For testing onDraw, onLayout and onMeasurescreenshots-tests-for-android is considerable option.
I need update a small portion of a custom view in order to display a small animation. The rest portion of the view has only static image. The most straightforward would be to obtain the canvas of the view and update only that particular portion directly. But I can't really find anyway to get the view's canvas object outside of the view::onDraw method.
The only alternative I know is this: call view::invalidate() with a specified rectangle to minimize the drawing flicker. I have the code to update the entire view within onDraw. So the best thing to do is to detect the clipping rect and only run the code to update the specified area, in order to minimize CPU usage as well?
I guess I will try to answer this question myself to the best my knowledge so far.
There is no direct access to the canvas outside of the onDraw method.
Although we can detect the clipping rect with the function Canvas.getClipBounds(), the getClipBounds function always return the entire view area if GPU is enabled. When GPU is not used, getClipBounds() returns the actual dirty area. Since there is a GPU in most phones, it makes the function getClipBounds pretty much useless.
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've got a custom View in my application which fills the entire activity.
In most cases, when I want to refresh the control I call invalidate() without any parameters.
However, in certain circumstances, where I'm only changing a small area of the control, I call invalidate(Rect) to avoid redrawing the entire screen. This is important because in these situations I
need the update to be as fast as possible.
This seems to work fine, however, when I enable hardware acceleration in Honeycomb (i.e. set android:hardwareAccelerated="true"in AndroidManifest.xml), the partial redraw does not seem to work.
This can be seen if I do Log.d("FOO", canvas.getClipBounds()) in my onDraw() method; the output simply shows that the whole control is being updated, whereas with hardware acceleration disabled, I get the correct region being output.
Is there any way to make partial invalidation work when using hardware acceleraton?
Many thanks, Matt
Partial redraw works just fine, only the specified region of the screen will get redrawn. What it won't do however is change the clip bounds on the Canvas. All the drawing operations will be recorded but only the ones intersecting with the dirty region will actually be executed.
Update: as of Lollipop (API 21), partial invalidation happens at the View level (i.e. you cannot invalidate less than an entire View).
When HA is enabled, the rendering pipeline will use DisplayList to store the drawing commands. Even if you specify the dirty region in View.invalidate, the whole displaylist will be rebuilt (just think that how can we only update a small set of DisplayList, that's impossible right?). Eventually, as #Romain
says, only the real dirty region will be redrawn.
I am reading about setDrawingCacheEnabled and getDrawingCache and I was wondering when is it good to use it or when its not good.
Basically in my case I have an HorizontalScrollView with many things inside it so its scrolls left/right and most of the things are not visible.
If I use setDrawingCacheEnabled(true) on the views, does it help? or this is only when I use custom views and I call getDrawingCache()?
Is there any other 'cache' way to use in a HorizontalScrollView?
TouchInterceptor.java - This is class responsible for reordering your playlist in the default music player. It uses setDrawingCacheEnabled when you start dragging the current view. Basically, it creates a bitmap from the ListView item and drag it. Take a closer look at onInterceptTouchEvent method.
It's definitely useful for screenshots as Marcel said. It is also very useful performance-wise, as that is what it was created for. It does use up more memory, as you render the view into a bitmap first.
What you do is, you setDrawingCacheEnabled to true, call getDrawingCache which returns a bitmap and store this bitmap. In onDraw, you do draw the bitmap you got if the cache is on, or the view otherwise. This can be very nice when scrolling.