Partial invalidation in custom Android view with hardware acceleration - android

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.

Related

What is a good way to test the onMeasure/onLayout/onDraw methods of a custom View?

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.

Android partial invalidation doesn't work as expected with hardware acceleration

I know this is quite the same question as partial-invalidation-in-custom-android-view-with-hardware-acceleration but I thought I will explain my problem more detailled and hope someone will have an answer on how to solve the problem.
I wrote a simple TicTacToe app which was working fine on the emulator. But wasn't working on the device since I found out it was working as expected on the device when I disabled hardware acceleration.
All the fields are drawn with Canvas.drawBitmap(), also the "empty" fields with an empty white image. The lines are drawn with Canvas.drawLine()
I'm using partial invalidation with invalidate(Rect) because I want to redraw only the area which was choosen to set a cross/circle with the according image using again Canvas.drawBitmap(). But then the whole area is invalidated, means I see the whole area/screen is gray (the white images and lines disappeared) and only the image for the cross/circle is set in the choosen area.
When I print out the invalidated field rect with Canvas.getClipBounds() in the method onDraw(), with hardware acceleration it is the whole area (example "0,0,320,407") and without hardware acceleration the same rect which I invalidated with invalidate(Rect) (e.g. "106,135,106,135").
At the moment as workaround I redraw all the fields whith the according image and lines. How can I prevent to have the whole area invalidated with hardware acceleration?
If this matters: I'm using Android version 4.1.2 on Samsung Galaxy Young Duos S6312.
Regards
Sandro
I have post my answer in your given link. To understand the problem , you should know the difference between software rendering and hardware rendering. That's really a big topic which I won't cover here. I think the best approach is to read the source code(I did the same thing a few days ago). The key question is "what is DisplayList and when it is used"
. Repeat my answer here, the Canvas in onDraw is simply used to rebuild the DisplayList. That doesn't mean everything in your View will be redrawn.

Updating SurfaceView with multiple dirty rectangles

In the project I am working on I decided to use SurfaceView instead of a custom double buffered medium. It provides everything I need and it's already double buffered.
The problem is that it wont let me specify multiple dirty rectangles to redraw.
SurfaceView.lockCanvas(Rect) only allows single rectangle and without parameter it's pretty expensive to redraw whole thing. Another solution to call lockCanvas(Rect) for each Rect causes eye-bleeding blinking in the screen, obviously.
Do you have any solution giving the opportunity staying inside Android API field, if not do you have any external alternatives I can use?
If you know the dirty areas before you need to call lockCanvas (sounds like you might), you could calculate a "super rectangle" that locks an area that contains all of your rectangle. For example if your rectangles are (using l,r,t,b coordinates) [0,10,0,20] and [15,30,10,35], your super rectangle would be [0,30,0,35].

Android Custom View Performance

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.

Android view animation and hardware layers

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.

Categories

Resources