This is not the usually tutorial we go through how to create or use a feature in android. I stumbled over this android library that is still in active development and I decided to share it because I hope it will be handy when you are working with Android camera API in your android application.
Android CameraView is not from Google or one of the UI widget you found in Android. It is an android library that integrate android Camera1 and Camera2 API together depending on the version of android in your device.
I wrote tutorials on Android Camera1 API here and also on Android Camera2 API here but if you want a library project then check out CameraView. More detail about CameraView can be found in Github.
The CameraView is still in preview release so you should keep an eye on it.
This is the screenshot of the CameraView I took with my device
CameraView Requirement
Requires API Level 14. The library uses Camera 1 API on API Level 14-20 and Camera2 on 21 and above
CameraView Features
Camera preview by placing it in a layout XML (and calling the start method)
Configuration by attributes
Aspect ratio (app:aspectRatio)
Auto-focus (app:autoFocus)
Flash (app:flash)
CameraView Usage
com.google.android.cameraview.CameraView
<com.google.android.cameraview.CameraView android:id="@+id/camera" android:layout_width="match_parent" android:layout_height="wrap_content" android:keepScreenOn="true" android:adjustViewBounds="true" app:autoFocus="true" app:aspectRatio="4:3" app:facing="back" app:flash="auto"/> @Override protected void onResume() { super.onResume(); mCameraView.start(); } @Override protected void onPause() { mCameraView.stop(); super.onPause(); }
If you want to understand CameraView library more then you can dive into the demo application that is include with your download.
The MainActivity sample class is shown below.
package com.google.android.cameraview.demo; import android.Manifest; import android.app.Dialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import com.google.android.cameraview.CameraView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { private static final String TAG = "MainActivity"; private static final int REQUEST_CAMERA_PERMISSION = 1; private static final int REQUEST_STORAGE_PERMISSION = 2; private static final String FRAGMENT_DIALOG = "dialog"; private static final int[] FLASH_OPTIONS = { CameraView.FLASH_AUTO, CameraView.FLASH_OFF, CameraView.FLASH_ON, }; private static final int[] FLASH_ICONS = { R.drawable.ic_flash_auto, R.drawable.ic_flash_off, R.drawable.ic_flash_on, }; private static final int[] FLASH_TITLES = { R.string.flash_auto, R.string.flash_off, R.string.flash_on, }; private int mCurrentFlash; private CameraView mCameraView; private Handler mBackgroundHandler; private View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.take_picture: if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { if (mCameraView != null) { mCameraView.takePicture(); } } else if (ActivityCompat.shouldShowRequestPermissionRationale( MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { ConfirmationDialogFragment .newInstance(R.string.storage_permission_confirmation, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION, R.string.storage_permission_not_granted) .show(getSupportFragmentManager(), FRAGMENT_DIALOG); } else { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); } break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCameraView = (CameraView) findViewById(R.id.camera); if (mCameraView != null) { mCameraView.addCallback(mCallback); } FloatingActionButton takePicture = (FloatingActionButton) findViewById(R.id.take_picture); if (takePicture != null) { takePicture.setOnClickListener(mOnClickListener); } Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayShowTitleEnabled(false); } } @Override protected void onResume() { super.onResume(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraView.start(); } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { ConfirmationDialogFragment .newInstance(R.string.camera_permission_confirmation, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION, R.string.camera_permission_not_granted) .show(getSupportFragmentManager(), FRAGMENT_DIALOG); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } } @Override protected void onPause() { mCameraView.stop(); super.onPause(); } @Override protected void onDestroy() { super.onDestroy(); if (mBackgroundHandler != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { mBackgroundHandler.getLooper().quitSafely(); } else { mBackgroundHandler.getLooper().quit(); } mBackgroundHandler = null; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_CAMERA_PERMISSION: if (permissions.length != 1 || grantResults.length != 1) { throw new RuntimeException("Error on requesting camera permission."); } if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, R.string.camera_permission_not_granted, Toast.LENGTH_SHORT).show(); } // No need to start camera here; it is handled by onResume break; case REQUEST_STORAGE_PERMISSION: if (permissions.length != 1 || grantResults.length != 1) { throw new RuntimeException("Error on requesting storage permission."); } if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, R.string.storage_permission_not_granted, Toast.LENGTH_SHORT).show(); } break; } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.switch_flash: if (mCameraView != null) { mCurrentFlash = (mCurrentFlash + 1) % FLASH_OPTIONS.length; item.setTitle(FLASH_TITLES[mCurrentFlash]); item.setIcon(FLASH_ICONS[mCurrentFlash]); mCameraView.setFlash(FLASH_OPTIONS[mCurrentFlash]); } break; case R.id.switch_camera: if (mCameraView != null) { int facing = mCameraView.getFacing(); mCameraView.setFacing(facing == CameraView.FACING_FRONT ? CameraView.FACING_BACK : CameraView.FACING_FRONT); } break; } return false; } private Handler getBackgroundHandler() { if (mBackgroundHandler == null) { HandlerThread thread = new HandlerThread("background"); thread.start(); mBackgroundHandler = new Handler(thread.getLooper()); } return mBackgroundHandler; } private CameraView.Callback mCallback = new CameraView.Callback() { @Override public void onCameraOpened(CameraView cameraView) { Log.d(TAG, "onCameraOpened"); } @Override public void onCameraClosed(CameraView cameraView) { Log.d(TAG, "onCameraClosed"); } @Override public void onPictureTaken(CameraView cameraView, final byte[] data) { Log.d(TAG, "onPictureTaken " + data.length); Toast.makeText(cameraView.getContext(), R.string.picture_taken, Toast.LENGTH_SHORT) .show(); getBackgroundHandler().post(new Runnable() { @Override public void run() { // This demo app saves the taken picture to a constant file. // $ adb pull /sdcard/Android/data/com.google.android.cameraview.demo/files/Pictures/picture.jpg File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "picture.jpg"); OutputStream os = null; try { os = new FileOutputStream(file); os.write(data); os.close(); } catch (IOException e) { Log.w(TAG, "Cannot write to " + file, e); } finally { if (os != null) { try { os.close(); } catch (IOException e) { // Ignore } } } } }); } }; public static class ConfirmationDialogFragment extends DialogFragment { private static final String ARG_MESSAGE = "message"; private static final String ARG_PERMISSIONS = "permissions"; private static final String ARG_REQUEST_CODE = "request_code"; private static final String ARG_NOT_GRANTED_MESSAGE = "not_granted_message"; public static ConfirmationDialogFragment newInstance(@StringRes int message, String[] permissions, int requestCode, @StringRes int notGrantedMessage) { ConfirmationDialogFragment fragment = new ConfirmationDialogFragment(); Bundle args = new Bundle(); args.putInt(ARG_MESSAGE, message); args.putStringArray(ARG_PERMISSIONS, permissions); args.putInt(ARG_REQUEST_CODE, requestCode); args.putInt(ARG_NOT_GRANTED_MESSAGE, notGrantedMessage); fragment.setArguments(args); return fragment; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Bundle args = getArguments(); return new AlertDialog.Builder(getActivity()) .setMessage(args.getInt(ARG_MESSAGE)) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String[] permissions = args.getStringArray(ARG_PERMISSIONS); if (permissions == null) { throw new IllegalArgumentException(); } ActivityCompat.requestPermissions(getActivity(), permissions, args.getInt(ARG_REQUEST_CODE)); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(getActivity(), args.getInt(ARG_NOT_GRANTED_MESSAGE), Toast.LENGTH_SHORT).show(); } }) .create(); } } }
Since this library is still in Preview Release, there are still some issues that have not yet been fix.
If you find anyone kindly report it to the developer. I have added a sample of the screen-shot I took.