Draw a semicircle in the background of a View

I am trying to create a TextView whose background is a half circle. I create a oval using a ShapeDrawable. I tried to create a semicircle by using ScaleDrawable to double the size vertical size of the oval and clip it. However, the ScaleDrawable has no effect. Why not? What is the best way to draw a semicircle in the background of a View?

res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView
        android:id="@+id/main_view"
        android:background="@drawable/semicircle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:gravity="center_horizontal"
    />
    </RelativeLayout>

res/drawable/semicircle.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/circle"
    android:scaleGravity="top|clip_vertical"
    android:scaleHeight="200%"
    android:scaleWidth="100%" >
</scale>

res/drawable/circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval"
    <solid
        android:color="#444" />
</shape>

src/.../MainActivity.java

//...
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        findViewById(R.id.main_view).getBackground().setLevel(10000);
    }
//...

Solution 1:

To clip the oval shape, just embed it in a ClipDrawable like this:

res/drawable/semicircle.xml

<?xml version="1.0" encoding="utf-8"?>
<clip
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="vertical"
    android:gravity="bottom">
    <shape android:shape="oval">
        <solid android:color="#444"/>
    </shape>
</clip>

ClipDdrawable common purpose is to create custom progress bars. It clips a part of its content and progressively displays it when its "level" property increases in a range of [0; 10000] (0=hidden, 10000=fully displayed).

  • clipOrientation is the orientation of the clipping progress.
  • gravity is the clipping progress start edge/side.

To get a half circle, set this ClipDrawable as your view background, and programmatically tweak its "level":

//...
findViewById(R.id.my_view).getBackground().setLevel(5000)
//...

Works on all Android versions ("Added in API level 1") and requires no custom view.

;-)

Solution 2:

You can implement you own Drawable. But that cannot be inflated from XML. You need to set the drawable from code using View.setBackgroundDrawable();

See my sample implementation to draw a semi circle using drawable.

public class SemiCircleDrawable extends Drawable {

    private Paint paint;
    private RectF rectF;
    private int color;
    private Direction angle;

    public enum Direction
    {
        LEFT,
        RIGHT,
        TOP,
        BOTTOM
    }

    public SemiCircleDrawable() {
        this(Color.BLUE, Direction.LEFT);
    }

    public SemiCircleDrawable(int color, Direction angle) {
        this.color = color;
        this.angle = angle;

        paint = new Paint();
        paint.setColor(color);
        paint.setStyle(Style.FILL);
        paint.setAntiAlias(true);

        rectF = new RectF();
    }

    public int getColor() {
        return color;
    }

    /**
     * A 32bit color not a color resources.
     * @param color
     */
    public void setColor(int color) {
        this.color = color;
        paint.setColor(color);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.save();

        Rect bounds = getBounds();

        if(angle == Direction.LEFT || angle == Direction.RIGHT)
        {
            canvas.scale(2, 1);
            if(angle == Direction.RIGHT)
            {
                canvas.translate(-(bounds.right / 2), 0);
            }
        }
        else
        {
            canvas.scale(1, 2);
            if(angle == Direction.BOTTOM)
            {
                canvas.translate(0, -(bounds.bottom / 2));
            }
        }


        rectF.set(bounds);

        if(angle == Direction.LEFT)
            canvas.drawArc(rectF, 90, 180, true, paint);
        else if(angle == Direction.TOP)
            canvas.drawArc(rectF, -180, 180, true, paint);
        else if(angle == Direction.RIGHT)
            canvas.drawArc(rectF, 270, 180, true, paint);
        else if(angle == Direction.BOTTOM)
            canvas.drawArc(rectF, 0, 180, true, paint);

        canvas.restore()
    }

    @Override
    public void setAlpha(int alpha) {
        // Has no effect
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // Has no effect
    }

    @Override
    public int getOpacity() {
        // Not Implemented
        return PixelFormat.UNKNOWN;
    }

}

Solution 3:

I made this class hope it help you, all variable are in spanish but its quite simple,

the constructor SemiCirculo use as parameters the rgb for the semicircle and the resolution (number of triangles for your semicircle)

the CalcularPuntosPorcentaje method use as parameters the center of the circle, the starting angle, the widht of the angle, and the radio.

the method ImprimeCirculo use the canvas as parameter, it is used to draw the semicircle once it has allready been calcultaed the points of the semicircle with the previus mentioned method.

the CalcularPuntosPorcentaje method is similar to CalcularPuntosPorcentaje, but insted of the starting and the widht angle parameters it use a % from 0 to 100

finaly the SetOffset and SetColor are used to change the default starting poing o reference for the angle and the color of the semicircle

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;


public class SemiCirculo {

    private Path circulo;
    private Paint color;
    private float px, py, radio, anguloI, anchoa,offset;
    private int r, g, b;
    private int resolucion;
    private float puntox[],puntoy[];


    public SemiCirculo(int r1, int g1, int b1, int resolucion1) {

        this.offset = 0;
        this.color = new Paint();
        this.r = r1;
        this.g = g1;
        this.b = b1;
        this.color.setColor(Color.rgb(r, g, b));
        color.setAntiAlias(true);
        circulo = new Path();
        this.resolucion = resolucion1;
        this.puntox = new float[this.resolucion];
        this.puntoy = new float[this.resolucion];
        this.anguloI = 0;
        this.anchoa = 1;

    }

    public void SetOffset(float off) {
        this.offset = off;
    }

    public void SetColor(int r1,int g1, int b1){        
        this.r=r1;
        this.g=g1;
        this.b=b1;
        this.color.setColor(Color.rgb(r, g, b));
    }

    public void CalcularPuntosPorcentaje(float px1, float py1,
            float porcentaje, float radio1) {
        this.anguloI = 0 + this.offset;
        this.px = px1;
        this.py = py1;
        this.radio = radio1;
        this.anguloI = 0;
        this.anchoa = porcentaje / 100 * 360;

        this.CalcularPuntos(px, py, anguloI, anchoa, radio);
    }

    public void CalcularPuntos(float px1, float py1, float anguloI1,
            float anchoangulo, float radio1) {
        this.anguloI = anguloI1 + this.offset;

        this.anchoa = anchoangulo;
        this.px = px1;
        this.py = py1;
        this.radio = radio1 ;

        float angulo = 360 - this.anguloI - this.anchoa;

        for (int i = 0; i < resolucion; i++) {
            this.puntox[i] = this.px - (float) Math.sin(Math.toRadians(angulo))
                    * this.radio;
            this.puntoy[i] = this.py - (float) Math.cos(Math.toRadians(angulo))
                    * this.radio;
            angulo = (360 - this.anguloI - this.anchoa)
                    + ((this.anchoa / (float) (this.resolucion)) * (i + 2));
        }

        this.circulo.reset();

        this.circulo.moveTo(this.px, this.py);
        for (int i = 0; i < resolucion; i++) {
            this.circulo.lineTo(this.puntox[i], this.puntoy[i]);
        }

    }

    public void ImprimeCirculo(Canvas canvas) {

        canvas.drawPath(this.circulo, this.color);

    }

}

Solution 4:

instead you can use the image to set as a background...

1>design any image using paint and save it to any supported format such as .jpg or .png i.e this will be your image with semicircle which you want.

2>save the image in the res/drawable folder

3>set your textview background to that image using android:background="@drawable/yourimage.jpg"

hope this helps...