Android - Fill Path with color partially

2019-04-08 01:41发布

问题:

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 :

  1. Android fill in part of a path?
  2. 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.

回答1:

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.



回答2:

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.