11

I'm trying to draw an arc inside a circle to represent the temperature, but I'm having a difficult time achieving that. During my search I found those solutions

This one I couldn't understand what is the scale method for, and is drawing more than one arch, which confused me a bit

This post gave it a fixed size where I need the size to be controlled by the custom view in my XML layout

From here I understood the concept the degrees but I didn't understand how to determine the oval size

What i reached so far

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int startTop = 0;
    int startLeft = 0;
    int endBottom = getHeight() / 2;
    int endRight = endBottom;// This makes an equal square.

    int centerX = getWidth() / 2;
    int centerY = getHeight() / 2;

    int upperEdgeX = (int) (centerX + getWidth() / 2 * Math.cos(270 * Math.PI / 180));
    int upperEdgeY = (int) (centerY + getWidth() / 2 * Math.sin(270 * Math.PI / 180));

    int bottomEdgeX = (int) (centerX + getWidth() / 2 * Math.cos(90 * Math.PI / 180));
    int bottomEdgeY = (int) (centerY + getWidth() / 2 * Math.sin(90 * Math.PI / 180));

    int leftEdgeX = (int) (centerX + getWidth() / 2 * Math.cos(180 * Math.PI / 180));
    int leftEdgeY = (int) (centerY + getWidth() / 2 * Math.sin(180 * Math.PI / 180));

    int rightEdgeX = (int) (centerX + getWidth() / 2 * Math.cos(0 * Math.PI / 180));
    int rightEdgeY = (int) (centerY + getWidth() / 2 * Math.sin(0 * Math.PI / 180));

    RectF rect = new RectF(startTop, startLeft, endRight, endBottom);
    canvas.drawCircle(centerX, centerY, getWidth() / 2, mBasePaint);


    canvas.drawCircle(centerX, centerY, getWidth() / 3, mCenterPaint); // White circle
}

UPDATE: I need my view to be like a Donut Pie chart where the middle will hold the degree

UPDATE 2:

I'm trying to have something like this

enter image description here

Community
  • 1
  • 1
Motassem Jalal
  • 1,254
  • 1
  • 22
  • 46
  • This SO post might help you. [Check out](http://stackoverflow.com/questions/3874424/android-looking-for-a-drawarc-method-with-inner-outer-radius) – Imran Ali Aug 29 '16 at 16:05

1 Answers1

20

The following custom View draws two arcs connecting to form a circle as well as an inner circle.

enter image description here

Moreover, I let it fill the rectangle used for drawing the arc and use a yellow background for the View, the activity background is dark. (These additional features are meant for getting a better impression of how drawing a circle / an arc works, they can help with debugging.)

The custom View:

public class MySimpleView extends View{

private static final int STROKE_WIDTH = 20;
private Paint mBasePaint, mDegreesPaint, mCenterPaint, mRectPaint;
private RectF mRect;
private int centerX, centerY, radius;

public MySimpleView(Context context) {
    super(context);
    init();
}

public MySimpleView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public MySimpleView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init()
{
    mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mRectPaint.setColor(ContextCompat.getColor(getContext(), R.color.magenta));
    mRectPaint.setStyle(Paint.Style.FILL);

    mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mCenterPaint.setColor(ContextCompat.getColor(getContext(), R.color.white));
    mCenterPaint.setStyle(Paint.Style.FILL);

    mBasePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mBasePaint.setStyle(Paint.Style.STROKE);
    mBasePaint.setStrokeWidth(STROKE_WIDTH);
    mBasePaint.setColor(ContextCompat.getColor(getContext(), R.color.blue));

    mDegreesPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mDegreesPaint.setStyle(Paint.Style.STROKE);
    mDegreesPaint.setStrokeWidth(STROKE_WIDTH);
    mDegreesPaint.setColor(ContextCompat.getColor(getContext(), R.color.green));

}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // getHeight() is not reliable, use getMeasuredHeight() on first run:
    // Note: mRect will also be null after a configuration change,
    // so in this case the new measured height and width values will be used:
    if (mRect == null)
    {
        // take the minimum of width and height here to be on he safe side:
        centerX = getMeasuredWidth()/ 2;
        centerY = getMeasuredHeight()/ 2;
        radius = Math.min(centerX,centerY);

        // mRect will define the drawing space for drawArc()
        // We have to take into account the STROKE_WIDTH with drawArc() as well as drawCircle():
        // circles as well as arcs are drawn 50% outside of the bounds defined by the radius (radius for arcs is calculated from the rectangle mRect).
        // So if mRect is too large, the lines will not fit into the View
        int startTop = STROKE_WIDTH / 2;
        int startLeft = startTop;

        int endBottom = 2 * radius - startTop;
        int endRight = endBottom;

        mRect = new RectF(startTop, startLeft, endRight, endBottom);
    }

    // just to show the rectangle bounds:
    canvas.drawRect(mRect, mRectPaint);

    // subtract half the stroke width from radius so the blue circle fits inside the View
    canvas.drawCircle(centerX, centerY, radius - STROKE_WIDTH / 2, mBasePaint);
    // Or draw arc from degree 192 to degree 90 like this ( 258 = (360 - 192) + 90:
    // canvas.drawArc(mRect, 192, 258, false, mBasePaint);

    // draw an arc from 90 degrees to 192 degrees (102 = 192 - 90)
    // Note that these degrees are not like mathematical degrees:
    // they are mirrored along the y-axis and so incremented clockwise (zero degrees is always on the right hand side of the x-axis)
    canvas.drawArc(mRect, 90, 102, false, mDegreesPaint);

    // subtract stroke width from radius so the white circle does not cover the blue circle/ arc
    canvas.drawCircle(centerX, centerY, radius - STROKE_WIDTH, mCenterPaint);
}
}
Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
  • Thank you for your well detailed answer, but one more question, Should I specify a static height and width for my custom view in the xml layouts? Since the arc is being drawn at the top of the layout when I specify wrap_content as width – Motassem Jalal Aug 30 '16 at 07:14
  • 1
    @MotassemJa - Right now, the View will draw in the upper left corner (assuming a LTR locale). For the image above I used 120dp for width and height. For "wrap_content" to work, I suppose there would have to be some content first. Does your custom View have some kind of child View? – Bö macht Blau Aug 30 '16 at 07:33
  • No my custom view is empty. Would it work if I put a single View element inside it? – Motassem Jalal Aug 30 '16 at 07:42
  • Because the arc is not stable in different screen sizes – Motassem Jalal Aug 30 '16 at 07:45
  • 1
    @MotassemJa - I think that's what dp values for View width and height are for. It's a good idea to use not hardcoded dp values in the xml files but refer instead some dimen value. then you can provide different dimen values depending on screen size. – Bö macht Blau Aug 30 '16 at 08:21
  • Is it possible to add text on the circumference of the circle? And also a text in the bottom of the Rect without changing the circle size? – Motassem Jalal Aug 30 '16 at 14:53
  • @MotassemJa - well, to achieve this you can either draw text on the canvas or consider using a RelativeLayout with your custom View as lowest element (add it first to the RelativeLayout) and some TextViews overlapping the custom View to which you can then assign the desired size and textStyle and position by means of RelativeLayout parameters in the layout xml file. It may be worth asking an extra question for this - I'm sure there are lots of layout gurus out there with much more experience than I have :) – Bö macht Blau Aug 30 '16 at 15:36
  • I want to go with the first option, just to avoid adding 6 TextViews inside each RelativeLayout. I tried adding a text using canvas.drawText("Temp 1", canvas.getWidth() / 2, canvas.getHeight() + STROKE_WIDTH, mTextPaint); but it didn't show up on the view – Motassem Jalal Aug 31 '16 at 06:53
  • @MotassemJa - I think this happens because the value for the y-coordinate is way too high - you are drawing below the View. The value has to be smaller than getHeight(). See also the reference on [Paint.Align](https://developer.android.com/reference/android/graphics/Paint.Align.html) – Bö macht Blau Aug 31 '16 at 07:40