How to create a 'transparent circle inside rectangle' shape in XML in Android?
Asked Answered
B

4

20

I'm trying to create the following design in my app.

Design Mockup
(https://photos.google.com/share/AF1QipPhCGTgf9zi7MetWJYm0NQ14c3wqzjqnEwxsajCFHEjqzt5R29qYvIjZ2C71q7EnQ?key=WUZKSXA1WVVwSlI2LVdTQy1IRjdUdzVuQlpCY0Rn)

Its an overlay on top of the main UI. Trying to create this using a layout on top of the main UI with its background as a translucent shape created in XML. However, even after reading multiple posts, I'm not able to figure it out.

I tried the following approach, but it didn't work. Created a ring shape with 200dp stroke and set it as source for a imageview and then set the scaletype to centerCrop but the shape does not scale as a bitmap does.

Shape XML :

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadius="0dp"
    android:shape="ring"
    android:thicknessRatio="2"
    android:useLevel="false" >

    <solid android:color="@android:color/transparent" />

    <stroke
        android:width="200dp"
        android:color="#80000000" />
</shape>

Overlay layout :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/onboarding_background"
        android:scaleType="centerCrop"/>

</RelativeLayout>

Any pointers on how to do this or code would be really helpful.

Brandenburg answered 21/4, 2016 at 8:15 Comment(7)
In other words, you want a circular hole cutout at the center of a screen-wide semitransparent rectangleVenita
You could easily use a vector for this (it's another solution, in case xml is not mandatory)Straightjacket
You want to do something like Radius Around Point Map right? developers.google.com/android/reference/com/google/android/gms/… hope it be helpful! It could be duplicate of #13991801Connacht
https://mcmap.net/q/357709/-android-maps-api-v2-draw-circle refer the answerVouch
Hey @BobMalooga, Thanks for that edit, I couldnt directly add the picture cuz apparently, I need min 10 points for that. Guess I should answer some quuestions.Brandenburg
@MathieudeBrito, its just me working as dev (i.e. no designer) and i dont even know basic photosop, thats why I was trying to get by using xml. And this also shaves off some bit of apk size.Brandenburg
@M.Mariscal, that solution wont work, as the overlay is not only for that map. Thankx for the suggestion though.Brandenburg
T
31

I've been playing recently with something similar, and adapted it for you. All the magic is happening in the onDraw :

public class FocusView extends View {
  private Paint mTransparentPaint;
  private Paint mSemiBlackPaint;
  private Path mPath = new Path();

  public FocusView(Context context) {
    super(context);
    initPaints();
  }

  public FocusView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initPaints();
  }

  public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaints();
  }

  private void initPaints() {
    mTransparentPaint = new Paint();
    mTransparentPaint.setColor(Color.TRANSPARENT);
    mTransparentPaint.setStrokeWidth(10);

    mSemiBlackPaint = new Paint();
    mSemiBlackPaint.setColor(Color.TRANSPARENT);
    mSemiBlackPaint.setStrokeWidth(10);
  }

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

    mPath.reset();

    mPath.addCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, 550, Path.Direction.CW);
    mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);

    canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, 550, mTransparentPaint);

    canvas.drawPath(mPath, mSemiBlackPaint);
    canvas.clipPath(mPath);
    canvas.drawColor(Color.parseColor("#A6000000"));
  }
 }

The trick here is to create a Path (the transparent circle) so that we can set the drawing method of the path to be "outside of the path" instead of "inside of the path". Finally we can simply clip the canvas to that path, and fill in the black color.

For you, you'll just need to change Color.BLACK to your color, as well as change the desired radius.

EDIT : Oh and simply add it programmatically : FocusView view = new FocusView(context) your_layout.addView(view)

Or by XML :

<package_path_to_.FocusView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

EDIT2 : I just saw you wanted this for the onboarding of your app. You might consider having a look at https://github.com/iammert/MaterialIntroView then

Tore answered 21/4, 2016 at 8:47 Comment(3)
Hi how do i make an oval at the center just like this..?Isopiestic
Can someone tweak this to draw a square instead of circle in center?Situate
Fix: instead of 550 use canvas.getWidth() / 2Isotonic
M
4

I ran into such a problem that code does not work on api lvl 16 from NSimon. I fixed the code and now it supports api 16+.

public class FocusView extends View {
    private Paint mPaint;
    private Paint mStrokePaint;
    private Path mPath = new Path();

    public FocusView(Context context) {
        super(context);
        initPaints();
    }

    public FocusView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaints();
    }

    public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaints();
    }

    private void initPaints() {
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#A6000000"));

        mStrokePaint = new Paint();
        mStrokePaint.setColor(Color.YELLOW);
        mStrokePaint.setStrokeWidth(2);
        mStrokePaint.setStyle(Paint.Style.STROKE);

    }

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

        float radius = 0;
        float strokeWidth = 0;
        if (canvas.getWidth() < canvas.getHeight()) {
            radius = canvas.getWidth() / 2 - 10;
            strokeWidth = (canvas.getHeight() - canvas.getWidth())/2;
        } else {
            radius = canvas.getHeight() / 2 - 10;
            strokeWidth = (canvas.getWidth() - canvas.getHeight())/2;
        }

        mPaint.setStrokeWidth(strokeWidth);

        mPath.addCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, radius, Path.Direction.CW);
        mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);

        canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, radius, mStrokePaint);

        canvas.drawPath(mPath, mPaint);
    }
}
Micropaleontology answered 12/3, 2018 at 13:35 Comment(1)
how can i make an oval at the center instead of the circle?Isopiestic
D
3

You can use PorterDuffXferMode and custom view for that.

Good example of different modes provided at this picture (see A Out B): AlphaCompositing

The idea is to create custom view, with opaque black rectangle and circle over it. When you apply PorterDuffXferMode.SRC_OUT, it will "erase" the circle from rectangle, so you wil have result what you want.

In your customview you should override dispatchDraw(Canvas canvas) method, and draw resulting bitmap on your frame.

Then you can put MapView and your custom view in FrameLayout and enjoy result.

Diann answered 21/4, 2016 at 8:55 Comment(0)
O
0

Just use a vector like this:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <vector
            android:width="200dp"
            android:height="200dp"
            android:viewportWidth="284"
            android:viewportHeight="284">
            <path
                android:fillColor="@android:color/white"
                android:pathData="M0 142L0 0l142 0 142 0 0 142 0 142 -142 0 -142 0zm165 137.34231c26.06742 -4.1212 52.67405 -17.543 72.66855 -36.65787 11.82805 -11.30768 20.55487 -22.85153 27.7633 -36.72531C290.23789 158.21592 285.62874 101.14121 253.48951 58.078079 217.58149 9.9651706 154.68849 -10.125717 98.348685 8.5190299 48.695824 24.95084 12.527764 67.047123 3.437787 118.98655 1.4806194 130.16966 1.511302 152.96723 3.4990422 164.5 12.168375 214.79902 47.646316 256.70775 96 273.76783c21.72002 7.66322 44.26673 9.48476 69 5.57448z" />
        </vector>
    </item>
    <item>
        <shape
            android:innerRadius="0dp"
            android:shape="oval"
            android:useLevel="false">
            <solid android:color="@android:color/transparent" />
            <stroke
                android:width="15dp"
                android:color="@color/your_color" />
        </shape>
    </item>
</layer-list>

Output:enter image description here

And then you can have layouts like this, with transparent content.

enter image description here

Obscene answered 19/7, 2023 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.