My project is based on surfaceView and up until now I've had all of my rendering in onDraw which I am overriding. All seemed to be OK.
However, I've just updated my SDK and now it gives me an error telling me:
Suspicious method call; should probably call "draw" rather than "onDraw"
Could someone please explain the difference between these two?
I've read some similar questions around the net but I've not found an explanation that I understand.
Thanks
I tried cleaning my project and it did solve the problem. Try it.
SurfaceView.draw() basically calls View.draw(); If you want to implement your drawing, you should do it in View.onDraw() which is for you to implement which even says in the source code comments.
This method is called by ViewGroup.drawChild() to have each child view
draw itself. This draw() method is an implementation detail and is not
intended to be overridden or to be called from anywhere else other
than ViewGroup.drawChild().
As for difference between them:
draw():
13416 /*
13417 * Draw traversal performs several drawing steps which must be executed
13418 * in the appropriate order:
13419 *
13420 * 1. Draw the background
13421 * 2. If necessary, save the canvas' layers to prepare for fading
13422 * 3. Draw view's content
13423 * 4. Draw children
13424 * 5. If necessary, draw the fading edges and restore layers
13425 * 6. Draw decorations (scrollbars for instance)
13426 */
onDraw() is empty. Its for you to implement.
I have the problem since ever.
I handle it like this:
1) Declare a method like the following.
#SuppressLint("WrongCall")
public void drawTheView() {
theCanvas = null;
try{
theCanvas = getHolder().lockCanvas();
if(theCanvas != null) {
onDraw(theCanvas);
}
} finally {
getHolder().unlockCanvasAndPost(theCanvas);
}
}
2) Now you can modify the onDraw() Method:
#Override
public void onDraw(Canvas canvas) {
//Do some drawing
}
You can call the drawTheView() method from everywhere you want and call the onDraw() method this way without getting the error...
I think this is a practical way.
Note that in the case of drawing, overriding draw() and calling super.draw is often used when a ViewGroup wants to draw content over its child views. Content drawn in onDraw will appear under children.
As friiky said, #SuppressLint("WrongCall") fixed my problem. However it must be in front of the method name, not the above.
What I did is put mouse over the error code, right click and select Add #SuppressLint("WrongCall")
onDraw gives you a canvas to draw to the screen.
draw() allows you to manually draw a canvas to the screen (you have to make the canvas yourself).
Related
I've set global variables x,y in the Activity class.
I start a thread "t0" that continually update globals x and y.
I have onDraw pseudocode as follows (all on the UI thread):-
View.onDraw(){
if (x,y changed value) {
x0=x;
y0=y;
loop (x0-- until x0==0){
canvas.drawBitmap(bmp, x0, y0, bitmapPaint);
invalidate();
}
}
}
I was hoping that I'd see an animation of the bitmap moving across the screen on the x-axis, with each invalidate() re-drawing the new position. Instead I see it 'jump' to the last x position 0 (no intermediate stages).
I'm making the assumption that although x and y are updating via t0, I'm not too concerned since the loop is busy with the original x,y values (assigned to x0,y0).
I observe x,y updating and code is executed inside the 'if loop' (I see this via debug).
I tried adding a delay, but it didn't seem to make any difference. I can get it to re-draw directly to a new x,y position, but I need a smooth 'transition' via the loop to happen from one-x0-coord to another.
Any hints or tips would be appreciated.
Thanks in advance.
Steve
I think you can't cause an onDraw within an onDraw. calling invalidate() only causes that onDraw will be called after the current onDraw finishes. I am not sure how you really implemented it since it's pseudocode but i imagine you don't want the loop in the onDraw but do somethinglike this:
View.onDraw(){
if (x,y changed value) {
x0=x;
y0=y;
if (x0!=0){
x0--;
canvas.drawBitmap(bmp, x0, y0, bitmapPaint);
invalidate();
}
}
}
What is happening here I guess is, intermediates are getting drawn. But there is not enough time to show those. you have to set some thread.sleep inside it. It will work.
I have some methods in my View that modify some of the shapes that are drawn when called. In Java in order to make sure the component is updated I would call repaint(). Is there something that will make sure my view is updated correctly?
I had read somewhere that calling invalidate() in the onDraw() method would keep things up to date and therefore I wouldn't need to have something like repaint() in my methods that modify that shapes that are drawn.
Is this correct, or is there something else I have to do?
EDIT
To add in an example, a method I call in my view is:
public void setLineThickness(int thickness) {
aLineThickness = thickness;
if(aLineThicness > 1)
//repaint(); - Okay in Java but not in Android
}
Calling invalidate() will tell the view it needs to redraw itself (call onDraw) sometime in the future. So if you change something in your view, like the line thickness, call invalidate() after it. That way you know your view will eventually be updated.
All your drawing code should be implemented in onDraw() and your other methods should just change the state of your view, which will then be used to draw it, after you call invalidate().
I have a view (custom drawn) added with getWindowManager().addView() and later I'm modifiying the LayoutParameters of it (changing x & width) and call getWindowManager().updateViewLayout(). This works but I am getting two screen refreshes, first one only moves the whole thing according to the new x and later one scales it according to the new width. Any ideas about why is this happening even though I only call updateViewLayout just one time with the new layout parameters?
FYI: onDraw method of the custom drawn view mentioned here is also called only one time by the system during this process.
Try:
runOnUiThread(new Runnable() {
public void run() {
view.updateViewLayout();
}
});
http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)
If it doesn't work, check this:
How to move a view in Android?
Are you doing this?
try to do :
view.post(new Runnable() {
public void run() {
view.updateViewLayout();
}
});
updateViewLayout is an a method that can be overriden by your custom ViewGroup and in this overrided method you can implement all what your want to change.
Maybe you do something wrong in it?
Or also maybe you have to implement this code in UiThread like in other questions. - In this case when you change your parameters asynchronously with first call of drawing function by system you method maybe can change only one parameter and on second call the second parameter will be also changed.
I'm trying to move a ball on canvas. a and b are similar to x,y coordinate positions. Any way from my code im trying to get different values dynamically. The a,b are global variables. But it seems that "invalidate()" or the refreshing of screen only happens afer end of the whole loop. Do you know why?. And if i have to build this on another thread please suggest me with some simple codes.
private void shootBall(){
while (a>b){
a = getPositionX();
b = getPositionY();
invalidate();
}
}
}
I think it's more correct to say that you can call invalidate() from within a loop, but that that invalidation will not be handled (the canvas won't be redrawn) until after your loop is complete. The problem is that you are calling invalidate on the same thread (the UI toolkit thread) as the one that would call your onDraw() method. So unless/until you hand control back to the toolkit, it cannot possibly do the rendering. So your invalidate() call does actually invalidate the view ... but the view won't be redrawn until after your loop completes and your function returns.
It is more correct to change the position in some function that is called via some timer (which is essentially what the animation classes do). In that function, you would change the values, invalidate() appropriately, and return. Then the toolkit re-renders the scene and your function will get future callbacks and update the position accordingly.
do it like this, and use postInvalidate() instead:
private void shootBall(){
new Thread(new Runnable() {
public void run() {
while (a>b){
a = getPositionX();
b = getPositionY();
postInvalidate();
}
}
}).start();
}
edit: but as mentioned before, don't assume that the invalidate redraws the screen, it marks it as to-be-redrawn and the UI thread will get around to it.
You can put invalidate() at the end of onDraw() like in this example: How can I use the animation framework inside the canvas?
However this works well on some devices while bottlenecks and slows down on other.
To use a thread and SurfaceView go through all of these tutorials: http://www.droidnova.com/playing-with-graphics-in-android-part-i,147.html
UI cant be modified form any new thread..you should use invalidate() in the same thread where your view
I have a complex custom view - photo collage.
What is observed is whenever any UI interaction happens, the view is redrawn.
How can I avoid complete redrawing (for example, use a cached UI) of the view specially when I click the "back" button to go back to previous activity because that also causes redrawing of the view.
While exploring the API and web, I found a method - getDrawingCache() - but don't know how to use it effectively.
How do I use it effectively?
I've had other issues with Custom Views that I outline here.
I found a better way than using getDrawingCache.
In the method onDraw, apart from drawing in the natural canvas, I also draw on an memory-only canvas.
Bitmap cacheBmp = Bitmap.Create(....);
Canvas cacheCanvas = new Canvas(cacheBmp);
void onDraw(Canvas c)
{
if(updateDueToInteraction)
{
c.drawXXX(...);
cacheCanvas.drawXXX(...);
} else
{
c.drawBitmap(cacheBmp, 0, 0);
}
}
First of all you will have to use the setDrawingCacheEnabled(true) method, so that you're View is cache-enabled. Then, you can use the getDrawingCache(boolean) method which returns a Bitmap representing the View. Then, you can draw that bitmap manually.
If you don't enable caching by calling the setDrawingCacheEnabled(true) method, you will have to call buildDrawingCache() before (and call destroyDrawingCache() when you're done).
Bye!