Android SurfaceView Tutorial With Example

Hello Readers! In this post, we are going to learn about how to use android surfaceView widget in any android application. We will also go through different attributes of surfaceView widget that can be used to customise it.

It would be helpful for you if you already know about TextView Widget.

In this tutorial, we will show camera using android surfaceView and take picture. Then, we will save taken picture into external storage.

Output

Tutorialwing Android SurfaceView Tutorial Output of Android SurfaceView tutorial with example

Tutorialwing Android SurfaceView Tutorial Output

Sourcecode(Java)

You can download source code of this tutorial on android SurfaceView by entering the details –
[emaillocker id=”4963″]
SurfaceView Java Sourcecode
[/emaillocker]

Getting Started

Android SurfaceView widget can be defined as below –

SurfaceView is a subclass of View class that provides a drawing surface embedded inside of a view hierarchy. You can also control the surface of the view or it’s size or the place where surfaceView will be placed in the window.

You can access the underlying surface using SurfaceHolder interface by calling getHolder().

Attributes of Android surfaceView Widget

Some of the popular attributes of android surfaceView inherited from View class are –

Sr. XML Attributes Description
1 android:background Sets background of the view.
2 android:theme Sets a theme of the view
3 android:visibility Sets visibility (VISIBLE, INVISIBLE or GONE) of the view
4 android:elevation Sets z-depth of the view
5 android:id Sets id of the view
6 android:padding Sets padding of the view



Example of Android SurfaceView Widget

At first, we will create android application. Then, we will use surfaceView widget in this application.

1. Creating New Project

Follow steps below to create new project. Please ignore the steps if you have already created a new application.

Step Description
1. Open Android Studio.
2. Go to File => New => New Project. Write application name as SurfaceView. Then, click next button.
3. Select minimum SDK you need. However, we have selected 21 as minimum SDK. Then, click next button
4. Then, select Empty Activity => click next => click finish.
5. If you have followed above process correctly, you will get a newly created project successfully. However, you can also visit post to create a new project to know steps in detail.

Now, we will modify xml and java file to use android surfaceView widget in the application.

2. Modify Values folder

Open res/values/strings.xml file. Then, add below code into it.

<resources>
	<string name="app_name">SurfaceView</string>
	<string name="take_photo">TAKE PHOTO</string>
    <string name="permission_required">Permission Required</string>
	<string name="permission_message">You must grant permission to access camera and external storage to run this application.</string>
	<string name="permission_warning">All permissions are required.</string>
</resources>

Then, open res/values/dimens.xml file and below code into it.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="show_permission_padding">20dp</dimen>
</resources>

3. Modify Layout in xml file

Open src/main/res/layout/activity_main.xml file and add below code into it.

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

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <Button
        android:id="@+id/startBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:text="@string/take_photo"
        android:visibility="gone" />

    <TextView
        android:id="@+id/showPermissionMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="@dimen/show_permission_padding"
        android:text="@string/permission_message"
        android:textAlignment="center"
        android:textStyle="bold|italic"
        android:visibility="gone" />

</FrameLayout>

Here, we have modified the code in xml file to update the ui according the permission granted by the user.

– If all the required permission are not granted by the user, then, we will show a message to the user accordingly.




4. Setup For Required Permission at runtime.

In this application, we need two permissions – CAMERA and WRITE_EXTERNAL_STORAGE permissions. As, we know already that we need to provide runtime permissions to the user on or after android version 2.6 . So, prior to android version 2.6, we write the required permissions name in AndroidManifest.xml file.

Now, we will modify the code for required permissions.

Permission prior to Android 6.0

Open main/AndroidManifest.xml file. Then, add below code into it.

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Permission after Android 6.0

We will write code in java file to get permissions at runtime. So, open main/java/com.tutorialwing.surfaceview/MainActivity.java file. Then, write below code into it.

package com.tutorialwing.surfaceview;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;

import java.util.ArrayList;

import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PictureCallback {

    public static final int REQUEST_CODE = 100;

    private String[] neededPermissions = new String[]{CAMERA, WRITE_EXTERNAL_STORAGE};

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

        boolean result = checkPermission();
        if (result) {
           setupSurfaceHolder();
        }
    }

    private boolean checkPermission() {
        int currentAPIVersion = Build.VERSION.SDK_INT;
        if (currentAPIVersion >= android.os.Build.VERSION_CODES.M) {
            ArrayList<String> permissionsNotGranted = new ArrayList<>();
            for (String permission : neededPermissions) {
                if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                    permissionsNotGranted.add(permission);
                }
            }
            if (permissionsNotGranted.size() > 0) {
                boolean shouldShowAlert = false;
                for (String permission : permissionsNotGranted) {
                    shouldShowAlert = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                }
                if (shouldShowAlert) {
                    showPermissionAlert(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]));
                } else {
                    requestPermissions(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]));
                }
                return false;
            }
        }
        return true;
    }

    private void showPermissionAlert(final String[] permissions) {
        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
        alertBuilder.setCancelable(true);
        alertBuilder.setTitle(R.string.permission_required);
        alertBuilder.setMessage(R.string.permission_message);
        alertBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                requestPermissions(permissions);
            }
        });
        AlertDialog alert = alertBuilder.create();
        alert.show();
    }

    private void requestPermissions(String[] permissions) {
        ActivityCompat.requestPermissions(MainActivity.this, permissions, REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE:
                for (int result : grantResults) {
                    if (result == PackageManager.PERMISSION_DENIED) {
                        // Not all permissions granted. Show message to the user.
                        return;
                    }
                }

                // All permissions are granted. So, do the appropriate work now.
                break;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

Here, we have written the code to get CAMERA and WRITE_EXTERNAL_STORAGE permissions from the user. If all the permissions are not granted by the user, we will show a message to the user. If all permissions are granted, we will go ahead with the setup of surfaceView, camera etc.

5. Setup SurfaceView and Show Camera in java file

Till now, we have done all the setup (view modification, required permissions setup etc.). Now, we will write the code to setup android surfaceView and show camera. So, open src/main/java/com.tutorialwing.surfaceview/MainActivity.java file. Then, add below code into it.

package com.tutorialwing.surfaceview;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PictureCallback {

    private SurfaceHolder surfaceHolder;
    private Camera camera;

    public static final int REQUEST_CODE = 100;

    private SurfaceView surfaceView;

    private String[] neededPermissions = new String[]{CAMERA, WRITE_EXTERNAL_STORAGE};

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

        surfaceView = findViewById(R.id.surfaceView);
        if (surfaceView != null) {
            boolean result = checkPermission();
            if (result) {
                setupSurfaceHolder();
            }
        }
    }

    //...Other code related to permissions
    //...Other code related to view setup
    //...etc.

    private void setupSurfaceHolder() {
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        setBtnClick();
    }

    private void setBtnClick() {
        Button startBtn = findViewById(R.id.startBtn);
        if (startBtn != null) {
            startBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    captureImage();
                }
            });
        }
    }

    public void captureImage() {
        if (camera != null) {
            camera.takePicture(null, null, this);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        startCamera();
    }

    private void startCamera() {
        camera = Camera.open();
        camera.setDisplayOrientation(90);
        try {
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        resetCamera();
    }

    public void resetCamera() {
        if (surfaceHolder.getSurface() == null) {
            // Return if preview surface does not exist
            return;
        }

        if (camera != null) {
            // Stop if preview surface is already running.
            camera.stopPreview();
            try {
                // Set preview display
                camera.setPreviewDisplay(surfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // Start the camera preview...
            camera.startPreview();
        }
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        releaseCamera();
    }

    private void releaseCamera() {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    @Override
    public void onPictureTaken(byte[] bytes, Camera camera) {
        // Picture had been taken by camera. So, do appropriate action. For example, save it in file.
    }
}

Here, we have implemented SurfaceHolder.Callback interface to receive information about any changes
to the surface. This interface contains three methods –

(a) surfaceChanged() method – This is called immediately after any structural changes have been made to the surface. This method is called at least once after surfaceCreated() method is called. So, reset the camera here so that it is adjusted according to the changes in surface. So, do the stuffs like start camera preview etc. here.

(b) surfaceCreated() method – This is called immediately after surface is first created.

(c) surfaceDestroyed() method – This is called immediately before a surface is being destroyed. So, do the stuffs like releasing camera resources, stopping the camera preview etc. here.

This callback is set using SurfaceHolder.addCallback method. For example, we have set by calling surfaceHolder.addCallback(this); in setupSurfaceHolder() method.

There is another interface – Camera.PictureCallback. This is called when picture is taken by the camera. onPictureTaken() method is called when picture is taken. So, do the stuffs like saving photo in external storage, showing message to the user etc. here.




Final MainActivity.java code

Final code in MainActivity.java file would be like below –

package com.tutorialwing.surfaceview;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PictureCallback {

    private SurfaceHolder surfaceHolder;
    private Camera camera;

    public static final int REQUEST_CODE = 100;

    private SurfaceView surfaceView;

    private String[] neededPermissions = new String[]{CAMERA, WRITE_EXTERNAL_STORAGE};

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

        surfaceView = findViewById(R.id.surfaceView);
        if (surfaceView != null) {
            boolean result = checkPermission();
            if (result) {
                setupSurfaceHolder();
            }
        }
    }

    private boolean checkPermission() {
        int currentAPIVersion = Build.VERSION.SDK_INT;
        if (currentAPIVersion >= android.os.Build.VERSION_CODES.M) {
            ArrayList<String> permissionsNotGranted = new ArrayList<>();
            for (String permission : neededPermissions) {
                if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                    permissionsNotGranted.add(permission);
                }
            }
            if (permissionsNotGranted.size() > 0) {
                boolean shouldShowAlert = false;
                for (String permission : permissionsNotGranted) {
                    shouldShowAlert = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                }
                if (shouldShowAlert) {
                    showPermissionAlert(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]));
                } else {
                    requestPermissions(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]));
                }
                return false;
            }
        }
        return true;
    }

    private void showPermissionAlert(final String[] permissions) {
        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
        alertBuilder.setCancelable(true);
        alertBuilder.setTitle(R.string.permission_required);
        alertBuilder.setMessage(R.string.permission_message);
        alertBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                requestPermissions(permissions);
            }
        });
        AlertDialog alert = alertBuilder.create();
        alert.show();
    }

    private void requestPermissions(String[] permissions) {
        ActivityCompat.requestPermissions(MainActivity.this, permissions, REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE:
                for (int result : grantResults) {
                    if (result == PackageManager.PERMISSION_DENIED) {
                        Toast.makeText(MainActivity.this, R.string.permission_warning, Toast.LENGTH_LONG).show();
                        setViewVisibility(R.id.showPermissionMsg, View.VISIBLE);
                        return;
                    }
                }

                setupSurfaceHolder();
                break;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    private void setViewVisibility(int id, int visibility) {
        View view = findViewById(id);
        if (view != null) {
            view.setVisibility(visibility);
        }
    }

    private void setupSurfaceHolder() {
        setViewVisibility(R.id.startBtn, View.VISIBLE);
        setViewVisibility(R.id.surfaceView, View.VISIBLE);

        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        setBtnClick();
    }

    private void setBtnClick() {
        Button startBtn = findViewById(R.id.startBtn);
        if (startBtn != null) {
            startBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    captureImage();
                }
            });
        }
    }

    public void captureImage() {
        if (camera != null) {
            camera.takePicture(null, null, this);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        startCamera();
    }

    private void startCamera() {
        camera = Camera.open();
        camera.setDisplayOrientation(90);
        try {
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        resetCamera();
    }

    public void resetCamera() {
        if (surfaceHolder.getSurface() == null) {
            // Return if preview surface does not exist
            return;
        }

        if (camera != null) {
            // Stop if preview surface is already running.
            camera.stopPreview();
            try {
                // Set preview display
                camera.setPreviewDisplay(surfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // Start the camera preview...
            camera.startPreview();
        }
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        releaseCamera();
    }

    private void releaseCamera() {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    @Override
    public void onPictureTaken(byte[] bytes, Camera camera) {
        saveImage(bytes);
        resetCamera();
    }

    private void saveImage(byte[] bytes) {
        FileOutputStream outStream;
        try {
            String fileName = "TUTORIALWING_" + System.currentTimeMillis() + ".jpg";
            File file = new File(Environment.getExternalStorageDirectory(), fileName);
            outStream = new FileOutputStream(file);
            outStream.write(bytes);
            outStream.close();
            Toast.makeText(MainActivity.this, "Picture Saved: " + fileName, Toast.LENGTH_LONG).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This is final code in MainActivity.java file that includes camera permissions at runtime, surfaceView setup, camera setup, save taken picture into external folder etc.

Since AndroidManifest.xml file is very important in any android application, we are also going to see the content inside this file.

AndroidManifest.xml

Code inside src/main/AndroidManifest.xml file is as below –

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tutorialwing.surfaceview"
		  xmlns:android="http://schemas.android.com/apk/res/android">

	<uses-permission android:name="android.permission.CAMERA"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

	<application
		android:allowBackup="true"
		android:icon="@mipmap/ic_launcher"
		android:label="@string/app_name"
		android:roundIcon="@mipmap/ic_launcher_round"
		android:supportsRtl="true"
		android:theme="@style/AppTheme">
		<activity android:name=".MainActivity">
			<intent-filter>
				<action android:name="android.intent.action.MAIN"/>

				<category android:name="android.intent.category.LAUNCHER"/>
			</intent-filter>
		</activity>
	</application>

</manifest>

When we run the program, we will get output as shown above.

That’s end of tutorial on Android SurfaceView Widget.

Leave a Reply