I'm using a custom View to draw rectangles(which will have text inside them at a certain point) and I want to highlight each rectangle when selected, which will happen every few seconds when user selects a rectangle. Should I implement highlighted rectangle in onDraw or is there a way just to redraw each rectangle without redrawing the whole View? I was thinking of using "invalidate(rect)" but it's been deprecated.
I'm trying to be considerate of the cost of invalidating the whole View compared to just redrawing a rectangle.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(CellCoordinates cellCoordinate : mCoordinateCells) {
canvas.drawText(" ", cellCoordinate.getRect().exactCenterX(), cellCoordinate.getRect().exactCenterY(), cellPaint);
}
}
Using invalidate() and onDraw() is fine. Dirty rect is not really have effect on API21+
... In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead. ...
Dirty rect is deprecated because of different drawing model in hardware accelerated views. Checkout this link for more information
Also, it seems your rectangles can be implemented as custom Drawables with states (selected and normal). It will not give you extra performance, but might help to divide and structure code for drawing. This might help
Related
So on my canvas I have few rectangular areas where I draw different bitmaps.
Sometimes bitmaps get outside of their respective areas. I want these bitmaps to be cut off, so only the portion of bitmap inside of it's area is drawn.
I sure can calculate it manually (srcRect and dstRect)...
But isn't there a simpler solution?
I found clipBounds, but it doesn't seem to restrict drawing area in any way.
What you want is Canvas.clipRect(). You will need to know the bounds of the clipping rectangle, of course, but you don't need to worry about calculating a custom srcRect.
To use, save() the Canvas, do a single drawBitmap(), then restore() it to get your original clipping state back:
canvas.save();
canvas.clipRect(...);
canvas.drawBitmap(...);
canvas.restore();
I am working on a program that lets you draw highlights, pen and text comment mark-up. I have a working version where I recycle a bmp 3 times then draw it to the canvas.
The issue is my app has 3 of these views in memory, so that is a lot of bmps being created.
In my current solution, I am trying to avoid bmps. I have extended an ImageView and added the following to the Draw event:
`
public override void Draw (Canvas canvas)
{
base.Draw (canvas);
HighlightLayer.Draw(canvas, Width, Height);
PenLayer.Draw(canvas, Width, Height);
TextLayer.Draw(canvas, Width, Height);
}
`
Note that I am using monodroid so it's C# (but nearly the same as Java) and all the "Layer" objects are a subclass of Drawable. Just assume they have draw path commands etc.
Anyway, it sort of works except all the highlights / pen have black boxes around them.
When I draw a line (that's not an eraser), I use PorterDuff.Mode.Src.
I tried to combine the 3 into one bmp instead of recycling it but the issue was that if I had an eraser vector it would erase anything that is already drawn. So an erased pen line would also erase the highlights ...
Is it possible to "freeze" a drawing so that as you continue to draw paths on the canvas and change the PorterDuff mode to clear it will not effect anything marked as freeze?
I know enough to know that the black box I am seeing around my lines on my Drawables is due to the background bmp not being set. The problem is I want them to be layers on top of the ImageView. If they each retained a background then you could not see the Drawables underneath. How can I use a drawable with the background of the "parent ImageView" and avoid the black background it assumes to be there.
Somehow I need the background of the "parent" ImageView to be applied to the Drawables when it's choosing the transparent fill to avoid the default black.
SOLUTION:
Using the suggestion below I created a LayerDrawable and added Drawables for each mark-up tool. In the Drawable, before drawing the lines I added the following code:
public override void Draw (Canvas canvas)
{
canvas.SaveLayer(new RectF(0,0,Bounds.Width(),Bounds.Height()), null, SaveFlags.All);
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
GraphicsUtil.DrawVector(canvas, Lines, ToolID, Bounds.Width(), Bounds.Height());
}
Right now, I have it so that my layout consists of a Fragment that takes up the entire screen and displays an image. I want to make it so that an additional View exists on top of it, also taking up the entire screen. On that top layer, I want to be able to color it all black initially, and then create certain spots that are transparent (alpha?) and reveal the image displayed on the fragment behind it. So basically the screen will be all black except for a few spots where the image behind is showing through, which I would determine programmatically. I've looked into a bunch of the graphics and views that Android provides, but have no clue where to start. Is this suited for a SurfaceView if I just want it to be all black with some spots of alpha?
Once I select the correct view to use, I'm assuming that I just override the onDraw() method and then do something like canvas.setBody(black) and then add shapes of alpha to it? Will the shapes correctly affect the background color?
For your masking View, you can make a custom view that can keep track of which areas to unmask (as Rects or something similar) and then draw them in onDraw(Canvas) like this:
public class MaskView extends View {
private Set<Rect> mRects = new HashSet<Rect>();
private Paint mUnmaskPaint = new Paint();
{
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
/**
* Add an unmasking rectangle to this view's background.
*
* #param rect
* a rectangle used to unmask the background
*/
public void addUnmaskRect(Rect rect) {
mRects.add(rect);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
for (Rect r : mRects) {
canvas.drawRect(r, mUnmaskPaint);
}
}
}
Your Fragment (or whatever is keeping track of the unmask areas) passes Rects to MaskView via addUnmaskRect(Rect). Whenever the view is redrawn (remember to call invalidate() each time you are done passing Rects) it is first filled with black, and then has any rectangles unmask the black background. The coordinates for your rectangles must be set to the coordinate space of the view, but if the underlying image occupies the exact same area it should be relatively simple (you can also look at the View.getLocationInWindow(int[]) method to help you with this as well).
I'm not sure I'm doing this the "right" way, so I'm open to other options as well. Here's what I'm trying to accomplish:
I want a view which contains a graph. The graph should be dynamically created by the app itself. The graph should be zoom-able, and will probably start out larger than the screen (800x600 or so)
I'm planning on starting out simple, just a scatter plot. Eventually, I want a scatter plot with a fit line and error bars with axis that stay on the screen while the graph is zoomed ... so that probably means three images overlaid with zoom functions tied together.
I've already built a view that can take a drawable, can use focused pinch-zoom and drag, can auto-scale images, can switch images dynamically, and takes images larger than the screen. Tying the images together shouldn't be an issue.
I can't, however, figure out how to dynamically draw simple images.
For instance: Do I get a BitMap object and draw on it pixel by pixel? I wanted to work with some of the ShapeDrawables, but it seems they can only draw a shape onto a canvas ... how then do I get a bitmap of all those shapes into my view? Or alternately, do I have to dynamically redraw /all/ of the image I want to portray in the "onDraw" routine of my view every time it moves or zooms?
I think the "perfect" solution would be to use the ShapeDrawable (or something like it to draw lines and label them) to draw the axis with the onDraw method of the view ... keep them current and at the right level ... then overlay a pre-produced image of the data points / fit curve / etc that can be zoomed and moved. That should be possible with white set to an alpha on the graph image.
PS: The graph image shouldn't actually /change/ while on the view. It's just zooming and being dragged. The axis will probably actually change with movement. So pre-producing the graph before (or immediately upon) entering the view would be optimal. But I've also noticed that scaling works really well with vector images ... which also sounds appropriate (rather than a bitmap?).
So I'm looking for some general guidance. Tried reading up on the BitMap, ShapeDrawable, Drawable, etc classes and just can't seem to find the right fit. That makes me think I'm barking up the wrong tree and someone with some more experience can point me in the right direction. Hopefully I didn't waste my time building the zoom-able view I put together yesterday :).
First off, it is never a waste of time writing code if you learned something from it. :-)
There is unfortunately still no support for drawing vector images in Android. So bitmap is what you get.
I think the bit you are missing is that you can create a Canvas any time you want to draw on a bitmap. You don't have to wait for onDraw to give you one.
So at some point (from onCreate, when data changes etc), create your own Bitmap of whatever size you want.
Here is some psuedo code (not tested)
Bitmap mGraph;
void init() {
// look at Bitmap.Config to determine config type
mGraph = new Bitmap(width, height, config);
Canvas c = new Canvas(mybits);
// use Canvas draw routines to draw your graph
}
// Then in onDraw you can draw to the on screen Canvas from your bitmap.
protected void onDraw(Canvas canvas) {
Rect dstRect = new Rect(0,0,viewWidth, viewHeight);
Rect sourceRect = new Rect();
// do something creative here to pick the source rect from your graph bitmap
// based on zoom and pan
sourceRect.set(10,10,100,100);
// draw to the screen
canvas.drawBitmap(mGraph, sourceRect, dstRect, graphPaint);
}
Hope that helps a bit.
Ok, here's the deal. I want to move items in my extended gallery class to change the order of images. The way i'm doing it now:
on long press remove the current selected item,
use onDraw() to draw the same image so i can move it around using onTouchEvent()
on release add the item again
This works fine but the problem is that when using the onDraw() method it will draw the image behind the gallery items. Is there a way to change the priority of what is drawn?
Well i found this out after going into a totally different direction =/
Here's the solution for people that have the same problem:
In constructor (or anywhere else you initialize the component) set setWillNotDraw(false) and override dispatchDraw(). dispatchDraw() draws the ViewGroup children so you can decide yourself if you want to draw behind or a top of the other views.
Example taken from Custom drawing on top of Gallery view (and it's child views)
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// do your drawing stuff here
canvas.drawPath(mPath,mPaint);
}