i am implementing a custom launcher in Android, displaying 3rd party apps using ActivityViews.
I want to clip the content of these ActivityViews to a custom shape (other than Circle, Rectangle, RoundedRectangle, Ring...).
I already tried to call clipPath/drawPath on the dispatchDraw canvas of a parent viewgroup element which works fine for all children except the ActivityView. Even the ActivityView itself and its referenced SurfaceView seem to be clipped according to my given path (if i add a solid color for testing). But the rendered content remains unchanged. Manipulating the SurfaceView canvas (which you receive by calling getHolder().lockCanvas()) doesnt have any effect, too.
I think this has something to do with the virtualdisplay and/or various SurfaceControls which are used by the ActivityView, but i dont have any clue how to set clipping areas/paths for those classes.
Does anyone have an idea how to solve this?
Hint: i cannot paint over the ActivityViews content as i want to display the system wallpaper in the transparent areas.
finally setting up the PorterDuffXfermode correctly solved the issue. Just overwrite the dispatchDraw method of the parent viewgroup and erase the required areas using PorterDuff.
Sometimes it looks like the direction does matter how the path for drawPath is created (CW or CCW). So maybe try both.
Unfortunately this approach does not work if the hosted activity uses a SurfaceView on its own.
protected void dispatchDraw(Canvas canvas) {
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(getContext().getColor(android.R.color.white));
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
int save = canvas.save();
super.dispatchDraw(canvas);
canvas.drawPath(path, p);
canvas.restoreToCount(save);
}
Related
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
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());
}
Something weird is happening on android 4.4 with one of my apps. I am using a custom view in which I set a custom typeface with Typeface.createFromAsset. The rendering works fine on the device/emulator as you can see in the image above. The problem appears when I save the content of the custom View as a bitmap:
Bitmap currentBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(currentBitmap);
view.draw(canvas);
Everything works fine on android < 4.4. The saved image looks the same as the one visible on the screen. However, on 4.4 I only see the font borders and the inside of text is transparent.
How can I fix this ?
When drawing text on a canvas in API 19 and above, you have to be mindful of the stroke and fill for the Paint object that you're using. In prior versions of Android, text was always drawn filled, even when using the stroke style. In KitKat, setting the style to stroke will do exactly that; stroke the text rather than fill it. You will need to manually set the paint style to fill when rendering text and then re-set it to stroke for drawing other things (if that's what you're trying to do).
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTextPaint.setStyle(Style.STROKE);
// do other setup on Paint object
// Draw non-text stuff
mTextPaint.setStyle(Style.FILL);
// do other setup on Paint object
// Draw all the text stuff
}
I'm currently using the latest osmdroid library (3.0.10) to show my custom markers on a map with an ItemizedOverlay. I've extended the Drawable class to implement my own drawing. However my drawables are not drawn correctly. I draw a bitmap image, a circle and a text on top of each other. The bitmap is drawn, but the circle and the text is not visible. When I use the same drawable in an imageview, everything is OK.
Here is the code of my drawable's onDraw method:
#Override
public void draw(Canvas canvas) {
canvas.save();
canvas.translate(getBounds().left, getBounds().top);
//this draws fine
canvas.drawBitmap(bitmap, new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()), new Rect(0,0,mWidth,mWidth), mPaint);
//this is not
canvas.drawCircle(mHeight/2, mHeight/2, mHeight/2, mPaint);
//neither
canvas.drawText("X", mHeight/2, mHeight/2, mPaint2);
canvas.restore();
}
I've tried drawing the circle and the text on a bitmap, and drawing that on the provided canvas. It's working but that kills the whole point.
Any help is appreciated.
After a day of going trough the osmdroid sources, i managed to resolve my problems, but haven't found why are they there in the first place. Osmdroid uses an ISafeCanvas interface (and SafeTranslatedCanvas implementation) to wrap a canvas to deal with some high zoom level translate glitches. While I haven't found any straightforward errors in the code, I bypassed the mechanics when drawing my overlays by overriding the ItemizedOverlay's draw method and making my own ISafeCanvas implementation, which only returns the encapsulated canvas in it's getSafeCanvas method, and now everything looks fine.
#Override
protected void draw(Canvas canvas, MapView mv, boolean shadow) {
drawSafe(new MyCanvas(canvas),mv,shadow);
}
This is far from a best solution, the SafeCanvas is there for a reason, so I suggest a thorough testing if using this method.
Note: I used only Android 4.1.1, and don't know if this issue is present on other devices.
Please take a look at Issue 427. Essentially you want to use:
canvas.getUnsafeCanvas(new UnsafeCanvasHandler() {
// Draw circle and text here
// (but not drawBitmap)
});
Keep an eye on the ticket for a more permanent solution.
I try to do circle menu like in this app.
In "expanded" mode i draw this component like follows:
<RelativeLayout android:id="#+id/bigCircle">
<!--color full borders-->
<my.custom.component android:id="#+id/middleCircle">
<!--circle for buttons-->
<RelativeLayout android:id="#+id/smallCircle">
<!--minus button-->
</RelativeLayout>
</my.custom.component>
</RelativeLayout>
In onDraw method of my.custom.component i divide circle on 8 parts by using android.graphics.Path with android.graphics.Paint and some math.
Visually i have exactly as shown in the screenshot. But when i press on part of circle, i need redraw this part in another color to show user what something going on.
How i can redraw part of component's canvas cutting off from another part of canvas by android.graphics.Path for example. In another word i know what redraw canvas i should do in onDraw method, i know that i can show some bitmap from drawables painted in photoshop and have some "multiscreen trouble", i know how i can determine part which user pressed. But i don't know how i can select part of canvas and redraw it.
Developer of Catch here. If I'm understanding your issue, you're having trouble understanding how to specifically draw the highlight/selection indicator on a section of your circular menu.
While there are plenty of different ways one could implement it, what you're leaning towards (using android.graphics.Path) is how we did it. In the view hierarchy of our capture button, there's an element that serves as the canvas on which the selection highlight color (if there is an active selection) is drawn.
If you had a similar custom View in your layout, you could duplicate this behavior like so. First, you'll need the Path that defines the selection for a particular circle segment. Using Path.addArc(RectF, float, float) we can get the pizza-slice-shaped path we need:
private Path getPathForSegment(float startAngle, float sweep) {
Point center = new Point(getWidth() / 2, getHeight() / 2);
RectF rect = new RectF(0f, 0f, getWidth(), getHeight());
Path selection = new Path();
selection.addArc(rect, startAngle, sweep);
selection.lineTo(center.x, center.y);
selection.close();
return selection;
}
The getWidth() and getHeight() above are for the enclosing custom view object, so they define the bounding box that contains the circle on which the selection is drawn.
Then, in your custom view's onDraw(Canvas), if your code has determined a selection should be drawn for a segment:
#Override
protected void onDraw(Canvas canvas) {
// Assume one has the rest of these simple helper functions defined
if (shouldDrawSelection()) {
float startAngle = getStartAngleOfSelectedSegment();
float sweep = getSweepAngle();
Paint paint = getPaintStyleForSelectedSegment();
Path path = getPathForSegment(startAngle, sweep);
canvas.drawPath(path, paint);
}
// ...
super.onDraw(canvas);
}
In the other areas of your code that are tracking touches, just call invalidate() on the custom view so that it will redraw (or not) the selection path based on changes in input or state.
Remember that it's good practice to avoid newing objects in onDraw(), so most of these building blocks (Paths, Paints, etc.) can be constructed ahead of time (or once, on first occurrence) and reused.
Hope this is close to what you were asking!