Andrioid - Unable to re-create Canvas View dynamically - android

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.

Related

Android drawing position on view

As I am a beginner of Android developement, I need someone to give me an answer or any comments on my question.
When I draw a simple rectangle on a view with the following code, the rectangle doesn't show in the way in which I would expect.
canvas.drawLine(0, 0, getWidth(), getHeight(), framePaint);
wrong
But when I changed it like this, it shows properly.
canvas.drawRect(1, 0, getWidth(), getHeight()-1, framePaint);
Correct
That looks like the left and the bottom lines are clipped out of the view.
I was expecting that the position of view starts zero-indexed such as 0 ~ (size of view)-1. Do I understand wrong or did i do something wrong?
The framePaint is configured like this;
framePaint = new Paint();
framePaint.setColor(Color.rgb(255,0,0));
framePaint.setStrokeWidth(1);
framePaint.setStyle(Paint.Style.STROKE);
Here's what happens:
When you subtract one from the total pixels, it draws the line(s) in the image, not outside of it.
Also the "1" at the start also helps the code stay in the canvas.
You may want to try replacing the 1's in the code with 2's, as the canvas uses an extra pixel to automatically remove "aliasing".

Android - Fill Path with color partially

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.

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

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!

Dynamically create / draw images to put in android view

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.

Categories

Resources