Android Custom View with a Path drawn hit test - android

I am creating an Android application that allows free drawing.
The drawing area is represented by a FrameLayout that contains a set of DrawingView (derived from View).
Each DawingView has a transparent background and their size match the size of the FrameLayout.
The DrawingView draws a Path (built by calling path.moveTo followed by multiple path.lineTo)
Path _penPath;
Paint _paint;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(_penPath, _paint);
}
I need to perform hit test that is true only if the touch pointer is on the actual path and false if it is on the transparent background.
Currently the onTouch function gets invoked for every DrawingView in the Z order sequence.
I know I could calculate the distance between the touch pointer and each point in the path and compare it with a threshold value to implement hit test but I hope there is something in the framework that I can use.
For example in C# WPF transparent areas don't raise touch events if not configured explicitly.
Thanks.

Related

Clip content of ActivityView

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);
}

android.graphics.View invalidate(int, int, int, int) part of a canvas, but onRedraw() the entire canvas?

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

Creating a "cut out" layout

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).

Large image with clickable sections

I've an large image in my android app, which represents a real pictured device.
Now I would like to know, whats the best way to do the following:
Mark some specific image sectors as clickable
Visual preview the click event (like a normal android button)
I've thought about something like the "imagemap" similar solution of
http://catchthecows.com/?p=113.
But it doesn't highlight the clicked section (neccessary for me).
It's not possible, or lets say very complicated, to create "n" different pictures which handle the highlighting effect (for example: section 1 clicked - so highlighting it, section 2 clicked - so higlighting it ....).
Any suggestions would be great.
Thanks a lot.
Btw:
My image looks like the following (the red sections should be clickable).
I had quick look at the ImageMap source you provided.
Here's the onDraw() method for ImageMap:
/**
* Paint the view
* image first, location decorations next, bubbles on top
*/
#Override
protected void onDraw(Canvas canvas) {
drawMap(canvas);
drawLocations(canvas);
drawBubbles(canvas);
}
So, as the comment states, the full image is drawn first, followed by the 'location decorations' (which would seem to be an area of particular interest), and finally the area 'bubbles', which basically seem to be tooltips for the selectable areas and might not be applicable to you.
Taking a look at the drawing method for the 'location decorations':
protected void drawLocations(Canvas canvas) {
for (Area a : mAreaList) {
a.onDraw(canvas);
}
}
And finally the draw method for the Area objects (which are the definable, selectable image areas):
// an onDraw is set up to provide an extensible way to
// decorate an area. When drawing remember to take the
// scaling and translation into account
public void onDraw(Canvas canvas) {
if (_decoration != null) {
float x = (getOriginX() * mResizeFactorX) + mScrollLeft - 17;
float y = (getOriginY() * mResizeFactorY) + mScrollTop - 17;
canvas.drawBitmap(_decoration, x, y, null);
}
}
I would say your best bet would be to use the ImageMap class to take care of most of the required functionality. You can use the supplied Area.onDraw() method to take care of the area 'highlighting'. Instead of drawing a 'decoration bitmap', use the shape information defined in the Area class or subclasses and use Canvas methods to draw a transparent highlight mask over the selected areas.
EDIT: You would need a seperate onDraw() implementation for each of the Area subclasses, as the area bounds information is stored in the subclasses.

Android Circle Menu Like Catch Notes

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!

Categories

Resources