So now I’m doing Android development and on my first task was to make a camera view that have on overlay on top of it, but with a hole that acts as a camera vieport or viewfinder. I’ve seen other app have this feature so I thought there must be some tutorial or code snippet that I can learn from out there, and there is, but most of them are not obvious or doesn’t quite fit in with my requirement.

And the search goes on and on, up to the point where I promise myself that if I somehow manage to pull this off I’m going to blog about it. And here it is, the simplest way I find to create a “hole” on an android view.

Basically what I end up doing is extending a ViewGroup and override the onDraw method to draw a clearing to create a “hole”. A code snippet worth a thousand words.

MainActivity.javaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.myapp.viewport;

import android.hardware.Camera;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.FrameLayout;

public class MainActivity extends AppCompatActivity {
private FrameLayout preview;
private Camera mCamera;
private CameraView cameraView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mCamera = getCameraInstance();
preview = (FrameLayout) findViewById(R.id.preview1);
cameraView = new CameraView(this, mCamera);
preview.addView(cameraView);

mCamera.startPreview();
}

public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
}
CameraView.javaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.myapp.viewport;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ImageFormat;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.support.annotation.Nullable;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class CameraView extends SurfaceView {

private Camera mCamera;
private SurfaceHolder mHolder;

public CameraView(Context context, @Nullable Camera camera) {
super(context);
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(new MyCallback());
}

private class MyCallback implements SurfaceHolder.Callback {
public void surfaceCreated(SurfaceHolder surface) {
if (mHolder == null) {
mHolder = surface;
}
if (mCamera != null) {
try {
mCamera.setPreviewDisplay(surface);
} catch (IOException e) {
// Something bad happened
}
}
}

public void surfaceChanged(SurfaceHolder surface, int format, int width, int height) {
// Ignored, Camera does all the work for us
}

public void surfaceDestroyed(SurfaceHolder surface) {
}
}
}

And this is the meat of the viewport, this is the class draw the “hole” on the view.

Viewport.javaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.myapp.viewport;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.ViewGroup;

public class Viewport extends ViewGroup {

public Viewport(Context context) {
super(context);
}

public Viewport(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public Viewport(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}

@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

@Override
public boolean shouldDelayChildPressedState() {
return false;
}

@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
int viewportMargin = 32;
int viewportCornerRadius = 8;
Paint eraser = new Paint();
eraser.setAntiAlias(true);
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
float width = (float) getWidth() - viewportMargin;
float height = width * (float) 0.7;
RectF rect = new RectF((float)viewportMargin, (float)viewportMargin, width, height);
RectF frame = new RectF((float)viewportMargin-2, (float)viewportMargin-2, width+4, height+4);
Path path = new Path();
Paint stroke = new Paint();
stroke.setAntiAlias(true);
stroke.setStrokeWidth(4);
stroke.setColor(Color.WHITE);
stroke.setStyle(Paint.Style.STROKE);
path.addRoundRect(frame, (float) viewportCornerRadius, (float) viewportCornerRadius, Path.Direction.CW);
canvas.drawPath(path, stroke);
canvas.drawRoundRect(rect, (float) viewportCornerRadius, (float) viewportCornerRadius, eraser);
}
}
activity_main.xmlview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.myapp.viewport.MainActivity">


<FrameLayout
android:id="@+id/preview1"
android:layout_width="match_parent"
android:layout_height="match_parent">

</FrameLayout>

<!-- use the view to get the viewport -->
<com.myapp.viewport.Viewport
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight=".5"
android:background="#7f000000">

</com.myapp.viewport.Viewport>

</FrameLayout>

And this is the result running on an android emulator:

viewport.png