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!
Related
I am trying to draw a heart shaped Canvas using Path in Android. The code is as follows :
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Fill the canvas with background color
canvas.drawColor(Color.WHITE);
// paint.setShader(null);
// Defining of the heart path starts
path.moveTo(left + WIDTH / 2, top + HEIGHT / 4); // Starting point
// Create a cubic Bezier cubic left path
path.cubicTo(left+WIDTH/5,top,
left+WIDTH/4,top+4*HEIGHT/5,
left+WIDTH/2, top+HEIGHT);
// This is right Bezier cubic path
path.cubicTo(left + 3 * WIDTH / 4, top + 4 * HEIGHT / 5,
left + 4 * WIDTH / 5, top,
left + WIDTH / 2, top + HEIGHT / 4);
paint.setShader(new LinearGradient(0, canvas.getHeight()/4, canvas.getWidth(), canvas.getHeight()/4, new int[]{Color.RED, Color.YELLOW, Color.GREEN}, new float[]{0, 0.6f, 1}, Shader.TileMode.CLAMP));
canvas.drawPath(path, paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(4);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
}
I am able to draw heart without any issue and I am able to fill color inside the heart using the Fill option in Paint. But I should be able to fill the heart dynamically according to some data and it cannot be filled fully all the time. What I have achieved so far is as follows :
I have made an extensive search and came across a lot of things similar to this. Some of which includes :
Android fill in part of a path?
filling a circle gradually from bottom to top android
I also came across the concept of converting the canvas to bitmap and filling color inside the bitmap using Flood Fill Algorithm which lets users to fill colors inside the bitmap. However, I do not want the bitmap to fill the color while touching inside the heart but to fill while a button click action.
I thought that filling a circle gradually from bottom to top android
would give help me but it makes use of a circle and I am not well-versed in Canvas which makes me very weak in adapting the circle code to such a shape.
If anybody has some ideas or any insights on how to achieve this, it will be really helpful. Cheers. Thanks in advance.
P.S : I also tried some tricks using setShader in Paint but nothing would give me what I want.
EDIT :
I just stumbled upon a idea of drawing a rectangle over the heart with another color same as the background of the canvas so that it will look like its half filled !! I am still working on the idea and not sure how accurate this is gonna be for me. If someone has a better idea, you're most welcome.
I used clipPath function available in Canvas to achieve what I needed. I draw the heart by above method and draw a rectangle over it, and I use the clipPathfunction to clip out the region that is outside the heart.
public static double filled_amount = .90;
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(left_x1, left_y1, left_x2, left_y2, left_x3, left_y3);
path.cubicTo(right_x2, right_y2, right_x1, right_y1, left_x_moveto, left_y_moveto);
path.close();
Rect rect = new Rect((int)(canvas.getWidth()*.10),(int)(canvas.getHeight()*filled_amount),(int) canvas.getWidth(), (int) canvas.getHeight());
canvas.clipPath(path);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
canvas.drawRect(rect, rect_paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
This will give me the desired result of filling the heart dynamically. Changing the value of filled_amount dynamically and calling invalidate() will make it look like the heart is being filled dynamically.
#Henry's answer might be a better one but this did the trick for me and I dont look deeply in to the edges so a bit of zig-zags here and there is all right.
You could use Bitmap Masking to get a partially filled Heart. What you ideally do here is use one bitmap to mask the other.
In your case you could have a filled rectangle in the canvas and you have then have the heart shape in a new bitmap to act as the mask. You could then dynamically change the filling of the heart by changing the height of the background rectangle.
Refer this: https://stackoverflow.com/a/33483600/4747587. This contains the implementation of partially filling a Star. The idea is the same.
I am trying to show a gridview of the canvas images that were drawn in the previous screen. I have a set of different views which is drawn in the first screen and those views are subjected to change, those canvas will be re drawn according to the user's actions. I have 5 heart shaped canvas views and I am using different views for every heart meaning that I am not using a same class to draw the five hearts, rather I am using 5 different class for 5 different hearts. These hearts will be filled with color on user click.
In the next page, I am trying to just view the hearts that were drawn in the previous page and there is no action involved here, in the sense that this page is solely for the purpose of viewing. THIS PAGE CAN CONTAIN N NUMBER OF HEARTS BUT THE PREVIOUS PAGE WILL HAVE ONLY 5 HEARTS AT ANY INSTANT. For this purpose, I am using a single view in the display page to display all the hearts. But all I get is the last drawn heart and all other previous hearts are just getting overriden.
My idea of filling the heart is drawing a rectangle over it and clipping the path.
My onDraw is :
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(left_x1,left_y1,left_x2,left_y2,left_x3,left_y3);
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(right_x1,right_y1,right_x2,right_y2,right_x3,right_y3);
this.setDrawingCacheEnabled(true);
rect = new Rect((int)(canvas.getWidth()*.10),(int)(canvas.getHeight()*filled_amount),(int) canvas.getWidth(), (int) canvas.getHeight());
canvas.clipPath(path);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
canvas.drawRect(rect, rect_paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
The part where I add my views into an arraylist of views :
public static ArrayList<View> heart_views = new ArrayList<View>();
for(int i=0;i<dbHelper.getHeartPercentagesForSummary().size();i++)
{
SummaryHeartShape.filled_amount = .90 - (dbHelper.getHeartPercentagesForSummary().get(i)*0.008);
SummaryHeartShape summary_heart_shape = new SummaryHeartShape(getActivity());
summary_heart_shape.invalidate();
heart_views.add(summary_heart_shape);
}
I presume that onDraw() will be called when I create an instance of the SummaryHeartShape class.
So as you can see, everytime I am filling the rectangle to a different height which is given by SummaryHeartShape.filled_amount and this static variable is assigned in the onDraw of the rect as
rect = new Rect((int)(canvas.getWidth()*.10),(int)(canvas.getHeight()*filled_amount),(int) canvas.getWidth(), (int) canvas.getHeight());
So this basically means that the rect will be drawn according to the height that I give and as it differs every time, I should be receiving a canvas view with that rectangle height but every time I am getting the same rectangle with same height.
My Screen where user can fill the heart - Filling Heart
My Screen where user can view the filled heart - Summary of Hearts
As you can clearly see that the hearts in the first page are filled right but the hearts in the second page are just showing the last filled hearts.
I have checked the values that gets passed to SummaryHeartShape.filled_amount, it is different all the time but even though my hearts are broken !!!!
Can someone please help me in fixing my heart ? Please feel free to ask any doubts or if I want to make this more clear.
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).
This code was supposed to convert text to image
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
paint.setTextSize(16);
paint.setAntiAlias(true);
paint.setTypeface(Typeface.MONOSPACE);
Bitmap bm = Bitmap.createBitmap(16, 16, Bitmap.Config.ALPHA_8);
float x = bm.getWidth();
float y = bm.getHeight();
Canvas c = new Canvas(bm);
c.drawText("Test", x, y, paint);
}
Is this code ok? If yes, how can I make this new bitmap visible on screen? I tried this code which produced an error
setContentView(c); //<- ERROR!
I am confused with the element Canvas as there is not such element in XML which I can use in the code.
setContentView(View) takes a View and Canvas is not a View.
I am not sure that you want to create a Canvas on your own. There are ways to get a Canvas passed to you from the Android Framework though. One way you can do this is by creating a custom View. To do this, you will need to create a new class that extends View.
When overriding a View class, you will have the ability to override the onDraw(Canvas) method. This is probably where you want to do what you are attempting to do in your onCreate() method in the code you posted.
This link gives a good overview of what is required to create your own custom view.
First: If you draw your text at the x and y position you specified, you draw it
at the lower right corner, starting with exactly that pixel. Nothing will be drawn on your canvas. Try bm.getWidth()/2, for height the same for test drawing. You can optimize that later.
Second: Canvas is not a View (does not extend the View class). You can only set Views via set ContentView(). What I recommend here is writing a XML layout containing only a single ImageView and set that via setContentView(R.layout.mylayout).
After that, you can use findViewById() to grab that ImageView and use ImageView.setImageBitmap(bm) to show your bitmap on it.
You dont have to do anything with the canvas, once you created it with your bitmap. Everything you draw inside the canvas from that point on is found in the Bitmap immediately.
Therefore you can't specify the Canvas in XML. It's just an "Editor" to edit pictures, so to speak and not an actual UI element.
public class POCII extends Activity {
myView mv = new myView(this);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mv);
}
}
class myView extends View {
public myView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
canvas.drawRect(0,0,100,100, paint);
canvas.clipRect(0,0,50,50);
}
}
My question is, shouldn't the above code draw a rectangle and then crop the top left portion? The rectangle is not getting cropped.
Please explain what clipRect does. What is it actually clipping? Does it clip in the form of a rectangle, given the co-ordinates? If so, Why is the above code not working?
Canvas.clipRect(left, top, right, bottom) reduces the region of the screen that future draw operations can write to. It sets the clipBounds to be the spacial intersection of the current clipping rectangle and the rectangle specified. There are lot of variants of the clipRect method that accept different forms for regions and allow different operations on the clipping rectangle. If you want to explicitly set the clipping region, try:
canvas.clipRect(left, top, right, bottom, Region.Op.REPLACE);
The 5th argument means replace the clipping rectangle rather than creating the intersection with the previous version.
Try moving the clipRect statement before the drawRect statement. Or, try adding:
paint.setColor(Color.YELLOW);
drawRect(0,0,75,75);
after your existing clipRect statement. It should draw a 50x50 yellow square over what you had before.
Another note: (after long frustration with the apparently, largely undocumented View/ViewGroup/drawing code) I found that canvas.translate(x,y) also adjusts the clipRect. The interaction of clipRect and the drawing matrix is very confusing. It is helpful to point out:
canvas.getMatrix()
and
canvas.getClipBounds()
before and after modifications to the canvas and before drawing things.
To crop the top left portion, do:
canvas.clipRect(0,0,50,50, Region.Op.DIFFERENCE);
// secondly...
canvas.drawRect(0,0,100,100, paint);
ICS and above ...
XOR, Difference and ReverseDifference clip modes are
ignored by ICS if hardware acceleration is enabled.
Just disable 2D hardware acceleration in your view:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Reference Android: Howto use clipRect in API15
your drawing looks like this without using cliprect:
now if we use a cliprect we are putting a overlay of a rectange over what we already have. its sort of invisible. lets say we called the following:
override fun onDraw(canvas: Canvas) {
val paint = Paint();
paint.color = Color.RED
canvas.clipRect(0f,0f,500f,500f, Region.Op.DIFFERENCE);
// secondly...
canvas.drawRect(0f,0f,1000f,1000f, paint);
}
since we use DIFFERENCE option and we know the clipping rectangle is now OVER our canvas red rectangle we can tell me special things. above says we should KEEP the DIFFERENCE between the clipping rectangle and the original. so it will look like this (since i used half of 1000 for clipping rectangle):
and the opposite if we used intersect would look like this:
i'd love to see if someone can make it do rounded corners.