20

Background

I'm working on a library that has a lot of canvas drawing instead of multiple views (available here).

The problem

As I work to improve it and make it work for our needs of the app (need some customization), I've noticed there are some lines that are marked as deprecated:

canvas.clipRect(0f, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, height.toFloat(), Region.Op.REPLACE)

Thing is, I don't think there is a good candidate to replace this line of code with the newer APIs

What I've found

Looking at the docs, this is what's written:

This method was deprecated in API level 26. Region.Op values other than INTERSECT and DIFFERENCE have the ability to expand the clip. The canvas clipping APIs are intended to only expand the clip as a result of a restore operation. This enables a view parent to clip a canvas to clearly define the maximal drawing area of its children. The recommended alternative calls are clipRect(RectF) and clipOutRect(RectF);

So I tried using either of those functions, yet both of them caused issues with the drawing of how it used to be.

Looking at the deprecation, it seems that the function itself is marked, but not Region.Op.REPLACE :

enter image description here

So maybe it doesn't really have an alternative...

The questions

  1. What is the best alternative in this case?
  2. Why exactly was it deprecated?
  3. As opposed to some deprecated functions, I assume this one should be safe to still use in case I can't find an alternative, right?
android developer
  • 114,585
  • 152
  • 739
  • 1,270

1 Answers1

43

1: All methods that use custom Region.Op are deprecated now, so one can use only two method variants now: clipRect/clipPath (which represents Region.Op.INTERSECT) and clipOutRect/clipOutPath (which represents Region.Op.DIFFERENCE). To achieve function similar to Region.Op.REPLACE one must use save() and restore() methods.

So previously (with Op.REPLACE) you would call just:

canvas.clipRect(0, 0, 100, 100); // do some clipping
canvas.drawLine(...); // do some clipped drawing

canvas.clipRect(200, 200, 400, 400, Region.Op.REPLACE); // replace clipping region to completely different one
canvas.drawLine(...); // and some other drawing

But now you have to save and restore previous canvas state manually:

canvas.save();        // IMPORTANT: save current state of clip and matrix (i.e. unclipped state) (let's say it's state #1)
canvas.clipRect(0, 0, 100, 100); // do some clipping
canvas.drawLine(...); // do some clipped drawing
canvas.restore();     // IMPORTANT: get back to previously saved (unclipped) state of the canvas (restores state #1)

canvas.save(); // now save again the current state of canvas (clip and matrix) (it's state #2)
canvas.clipRect(200, 200, 400, 400); // now we can do some other clipping (as we would do with Region.Op.REPLACE before)
canvas.drawLine(...); // and some other drawing
canvas.restore(); // get back go previously saved state (to state #2)

Note Canvas is internally using stack, so you can even call save() multiple times at different moments. You just can't call canvas.restore() more times than the canvas.save() was called.

Also important note is that call to canvas.restore() changes the clip rect (to the same value it was when canvas.save() was called). So you must carefully place the restore() call after all the drawing methods that needs the applied clipping.

2: Probably because of some performance optimizations. I think I read somewhere (I couldn't find it now) that for hardware acceleration on GPU they can use only INTERSECT / DIFFERENCE clip operations and other ones must fall-back to CPU processing. That might be the reason.

EDIT: Here is some related answer, that since ICS with enabled HW acceleration some ClipRect ops are not supported.

3: As they say in documentation, it will stop working in Android P (probably only when targeting Android P):

As of API Level API level Build.VERSION_CODES.P only INTERSECT and DIFFERENCE are valid Region.Op parameters.

Robyer
  • 4,632
  • 2
  • 23
  • 21
  • 1
    1.As I wrote, "clipRect" was tested and caused bad drawing in the library I've tested. 2. OK. 3. Well at least on Android P DP2 it still works just fine. Maybe when targeting to P ? – android developer May 09 '18 at 16:54
  • 1
    1. Important part is not the `clipRect` method, but wrapping it with `canvas.save()` and `canvas.restore()`. 3. Yes, it makes more sense that they enforce it only when targeting to P, otherwise many apps would just start working incorrectly. – Robyer May 10 '18 at 14:15
  • 1
    I've tried what you wrote as a replacement in the library for the `drawTimeColumnAndAxes` function (it draws the times on the side), and it didn't work. Instead it didn't draw anything at all. Please check it out, and let me know of what you think. – android developer May 10 '18 at 14:26
  • 1
    I updated answer with bigger importance on the save() / restore() calls. What is the library you are talking about? Where is the source code? EDIT: I see it now, I'll look at it. – Robyer May 10 '18 at 14:32
  • 1
    The link is available in the question. Here it is : https://github.com/Quivr/Android-Week-View . If you prefer Kotlin over Java, I've made a fork (with a lot of fixes and improvements), here: https://github.com/AndroidDeveloperLB/Android-Week-View – android developer May 10 '18 at 14:35
  • 1
    Note that the library has multiple calls to clipRect. – android developer May 10 '18 at 14:44
  • 5
    Here is my edited file, I haven't tested it but that's the way it should work. See where I added the lines with canvas.save() and canvas.restore(). https://gist.github.com/Robyer/17fa5d479c49497d369dda2678578012 – Robyer May 10 '18 at 14:44
  • 1
    Seems to work well. But what was the problem with what I've tried? And, does it really helps with performance? – android developer May 10 '18 at 18:36
  • 3
    I'm glad it worked. Problem was that the real solution is not straightforward as just changing one line (orginal clipRect with Op.Replace) by wrapping it into 2 more lines (save/restore), but you must understand what is going on under way. Calling clipRect() affects the all drawing methods called after that, so you must place save/restore calls at the correct places. Important point is that restore() call CHANGES the clipRect to original value (to the value at the time save() was called). That's probably why you had the problem. I also updated the answer (+ probable performance reason). – Robyer May 11 '18 at 06:49
  • 1
    I see. Why was there a need to have so many options for it? I thought all it does it to clip the drawing, so that if you draw outside of the bounds, it won't really draw, just like in image-editors (the rectangle selection). Right? – android developer May 11 '18 at 13:20
  • 1
    That method marks area to be clipped. And different options were meant for logical operations (AND/OR/XOR/...) so you could modify the clipped area further - that is useful when you want area that is not purely rectangle, but it may be composed of different shapes. Or area with holes, etc. But then they realized / said that it shouldn't have been used for enlarging the clipped area, only for making it smaller (probably because of some HW acceleration limitations) - so they enforced it by deprecating the operations and providing only these 2 new methods. – Robyer May 12 '18 at 17:36
  • 1
    How could it be about making things larger? It's a clipped area out of a limited resolution size... – android developer May 12 '18 at 23:11
  • 1
    Making that CLIP AREA larger than it was before. If you clip it to 100x100 and then you clip it again (e.g. using Region.Op.UNION with another square 100x100 positioned right next to it) so resulting clipped area will be 200x100 (which is larger than previous clipped area). Same with Region.Op.Replace - first clip 100x100, then use Region.Op.Replace with clip 500x500, so your clipped area is now bigger than it was before. – Robyer May 13 '18 at 11:35
  • 1
    I don't understand. What exactly is being enlarged? If you choose a clipping area and then choose a larger/smaller one, doesn't it just change the state so that you now work on the newer clipping area? – android developer May 13 '18 at 14:55
  • 1
    Yes. Size of clipping area is bigger. That's the whole thing. From your point of view it's just change of some variable, but I don't know what they have to do under the hood to make that work - but obviously making clipping area bigger (if some is already set) is not wanted behavior by them. – Robyer May 14 '18 at 18:20
  • 1
    Well thank you for the help. Sadly it didn't boost the library performance as I've hoped. I think the library has a lot more performance issues (like constant creations of Calendar instances). I hope I can fix them. – android developer May 15 '18 at 06:39
  • @Robyer, what is the alternative for `'Canvas.ClipRect(RectF, Region.Op)' is obsolete: 'deprecated' (CS0618) ` – Joy Rex Oct 03 '18 at 06:32
  • 2
    @JoyRex My whole answer and all comments are dealing with the deprecated clipRect method. Did you even read it? It's all there ;) – Robyer Oct 03 '18 at 15:26
  • https://imgur.com/vfETuSH didnt worked here , I must need Region.Op.Replace, to REPLACE the clip that canvas cames with... when I touch the view, with REPLACE clip, the trunked text pop out over view below.. but didnt work with this fuc*ing INTERSECT – Arthur Melo Mar 01 '19 at 16:58
  • 1
    @ArthurMelo You can't enlarge the canvas you've got. You should enlarge the view itself; or you might want to set `android:clipChildren=false` on the parent ViewGroup and `android:clipToPadding=false` on the view itself. – Robyer Mar 02 '19 at 23:04
  • @Robyer solved with clipChildren and clipToPadding ;) thx dude – Arthur Melo Mar 18 '19 at 22:19