I am experimenting with building a custom game engine based on standard Canvas drawing that resembles the Android View class in that drawable objects have an onDraw method. The idea is that the game elements are given a Canvas on which to draw themselves and that is then incorporated into the main view. I assume that there is something similar going on behind the scenes with the standard Android Views.
How can I create a "master view" that can hand small Canvases to child objects and then incorporate those canvases into it's own drawing?
NOTE: the child objects are not subclasses of View, but the "master" view can be a View
After reading the Android Source Code for View and ViewGroup I discovered how this is done. When the system draws views to the screen, it generates a Canvas which is a rastor for the entire screen. That Canvas is not "cut up" and given to subviews to draw on, but rather it is translated and clipped to accomodate each view.
Before a particular view is drawn, its parent translates the Canvas to the child's position, clips the Canvas to the child's size, and then calls .draw(Canvas) on that view. The view does not know that the canvas has been altered, each view draws as if it were at (0,0). After the child has finished drawing, the Canvas is translated back to its previous position. Abstractly, every view thinks it is the only view, when in reality each parent is repositioning and resizing the canvas to be in the right place.
So for my situation, all I needed to do was iterate each of my child objects like this:
for(MyObject mob : mobjects) {
final int restoreTo = canvas.save();
canvas.translate(mob.getX(), mob.getY());
canvas.clipRect(0, 0, mob.getWidth(), mob.getHeight());
mob.onDraw(canvas);
canvas.restoreToCount(restoreTo);
}
Related
When I have a custom Android View (e.g. it extends FrameLayout) and I can draw on Canvas and I can add views with addView. How one relates to the other, e.g. when I draw sth on Canvas and I add some children which is at the top and which is at the bottom?
Android View has three versions of invalidate(): one that invalidates the whole view, and two that invalidate only a portion of it. But it only has one onDraw(), which draws the entire canvas. There must be some use that the system makes of the hint that I only want to invalidate part of the view, but I'm unclear on what it is.
I have a view that does custom drawing in onDraw(). Do I have a way to find out which parts of the canvas are invalid, so I only draw those?
When Android gets ready to render changes to the screen, it does so by creating a union of all of the individual rectangle areas of the screen that need to be redrawn (all the regions that have been invalidated.)
When your view's onDraw(Canvas canvas) method is called, you can check to see if the Canvas has a clip bounds.
If there is a non-empty clip bounds, you can use this information to determine what you will and won't need to draw thus saving time.
If the clip bounds is empty, you should assume Android wants you to draw the entire area of your view.
Something like this:
private Rect clipBounds = new Rect();
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
boolean isClipped = canvas.getClipBounds(clipBounds);
// If isClipped == false, assume you have to draw everything
// If isClipped == true, check to see if the thing you are going to draw is within clipBounds, else don't draw it
}
There must be some use that the system makes of the hint that I only want to
invalidate part of the view, but I'm unclear on what it is.
Yes, it is. The method invalidate (int l, int t, int r, int b) has four parameters which are used by the View's parent View to calculate the mLocalDirtyRect which is a filed of the View class. And the mLocalDirtyRect is used by the getHardwareLayer() method in the View class, here is its description:
/**
* <p>Returns a hardware layer that can be used to draw this view again
* without executing its draw method.</p>
*
* #return A HardwareLayer ready to render, or null if an error occurred.
*/
HardwareLayer getHardwareLayer() {
Means that Android can refresh part of your view without call the View's onDraw() method. So you don't need to try drawing part of the View yourself, because Android will do it for you when you tell it the dirty part of your View.
Finally, I think you can refer to the source code of View and ViewGroup for more details, here is the link that you can read them online:
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/View.java
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/ViewGroup.java
I'm designing a custom view that displays an expression (You can think about it as a complex TextView).
As the expression grows, I want my custom view to expand to accommodate it (Just like a wrap_content TextView).
when the content of the view is changed I buffer a canvas that is later drawn by onDraw()
The buffering is done using
Bitmap canvasBitmap = Bitmap.createBitmap(width, height , Config.RGB);
Canvas canvas = new Canvas(canvasBitmap);
canvas.drawText(pos,text, paint);
pos += paint.MesureText(text);
// and so on...
...
...
Only at the end of this process I know what width of canvas I really need - The value of pos, but I need it prior to it's calculation in the Bitmap.createBitmap().
Is there a way to create a canvas without specifies it's boundaries and then cut it to the right size? I don't want to do the process first to find out which width I need and then run it again to create an a view in the right size.
Any suggestions would be appreciated.
Use Views's onSizeChanged callback to create and populate the buffer.
This provides a good balance between eagerly buffering the data and deferring it to only when the view has a proper size.
I'm trying to override onDraw in an EditText subclass, to show a custom subtitle.
I've got it working, but there are a few bugs.
Basicall, all I need to do is draw StaticLayout at a certain offset from top left corner of the view.
Unfortunantly, all I get in my onDraw method is canvas. The size of the canvas is equal to the size of the whole screen (320x480 on a device with 320x480 display) and its clip bounds can be pretty much anything - it can be the whole view; it can be only top or bottom part of the view if the view is inside scrollview and partially visible; it can even be same arbitrary rect inside the view, probably because superclass invalidates only some of its region.
So if I have this view with size 320x48, I can get canvas with size 320x480 and clipping rect (200, 200, 300, 230) (left, top, right, bottom). I don't understand how this clipping rect maps to my view coordinates.
I need to know where is top left corner of the clipping rect relative to the top left corner of the view. Unfortunantly, I cannot figure out how to get this.
Added:
This code will work on all os versions that I've tested:
private int[] coordinates = new int[2];
private Matrix identityMatrix = new Matrix();
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
getLocationInWindow(coordinates);
canvas.setMatrix(identityMatrix);
canvas.translate(coordinates[0], coordinates[1]);
//do the drawing in EditText coordinate space
canvas.restore();
}
However, I still have one question: why does it work?
I've trying overriding View class and it's onDraw method will always recieve a canvas which size matches that of the View itself. Canvas for direct View subclass will have no clipping rect. Same for TextView (direct ancestor of the EditText class). But it's not the same for EditText. Canvas that gets passed to onDraw method of EditText will always (or not?) have the size of the screen, and a custom clipping rect. This whole "translate by view coordinates in window thing" seems very hacky. I don't understand why I should translate the coordinate space.
I've tried hacking android source code for the answers, but found none. EditText has no onDraw of its own. Theoretically, there should be no difference between overriding TextView onDraw and EditText onDraw. But there is a difference. The canvas object passed to onDraw method will be different depending on whether its TextView or EditText. Why? How do I know when I should apply transformation to matrix, and when I shouldn't?
you can have it with the View.getLocationOnScreen method
I want to use clipboard effect on android. I've seen some source codes about it. But all of them are simply flipping over some static pictures(Bitmap).I want to flip a view, and the view changes dynamically. How can I flip over this sort of view? I've tried to take a snapshot of the view, then use the snapshot to flip. But it seems that I can only snapshot a view after it presents on screen. How can I get the Bitmap of a view without present the view on screen?
One way is to draw your view hierarchy onto a bitmap and then use that for the animation. This blog post contains a few simple instructions. Basically it boils down to:
Bitmap returnedBitmap = Bitmap.createBitmap(screenWidth, screenHeight,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(auxBitmap);
myview.draw(canvas);
Where myview is the view hierarchy you wish to draw (either created in code or an inflated layout) and returnedBitmap is your bitmap with the view on it.