androidporter-duff

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


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.


Solution

  • 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