Merge pull request #1 from natario1/master

pull from fork source
pull/55/head
Johnson145 8 years ago committed by GitHub
commit aeb833a711
  1. 14
      README.md
  2. 0
      art/old_screen1.png
  3. 0
      art/old_screen2.png
  4. BIN
      art/screen1.jpg
  5. BIN
      art/screen2.jpg
  6. BIN
      art/screen3.jpg
  7. 83
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java
  8. 12
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  9. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  10. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  11. 14
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraPreview.java
  12. 10
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
  13. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java
  14. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
  15. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  16. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  17. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  18. 62
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  19. 13
      cameraview/src/main/res/layout/surface_view.xml
  20. 5
      cameraview/src/main/res/layout/texture_view.xml
  21. 30
      cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
  22. 107
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java
  23. 60
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceViewPreview.java
  24. 4
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureCameraPreview.java
  25. 3
      demo/build.gradle
  26. 3
      demo/src/main/AndroidManifest.xml
  27. 39
      demo/src/main/java/com/otaliastudios/cameraview/demo/AutoUnfocusEditText.java
  28. 223
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  29. 176
      demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java
  30. 107
      demo/src/main/java/com/otaliastudios/cameraview/demo/ControlView.java
  31. 375
      demo/src/main/java/com/otaliastudios/cameraview/demo/MainActivity.java
  32. 50
      demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java
  33. 38
      demo/src/main/java/com/otaliastudios/cameraview/demo/PicturePreviewActivity.java
  34. 24
      demo/src/main/java/com/otaliastudios/cameraview/demo/VideoPreviewActivity.java
  35. 8
      demo/src/main/res/drawable/background.xml
  36. 2
      demo/src/main/res/drawable/ic_edit.xml
  37. 100
      demo/src/main/res/layout/activity_camera.xml
  38. 555
      demo/src/main/res/layout/activity_main.xml
  39. 161
      demo/src/main/res/layout/activity_picture_preview.xml
  40. 45
      demo/src/main/res/layout/activity_video_preview.xml
  41. 31
      demo/src/main/res/layout/control_view.xml
  42. 8
      demo/src/main/res/layout/spinner_text.xml
  43. 2
      demo/src/main/res/values/colors.xml
  44. 8
      demo/src/main/res/values/styles.xml

@ -14,8 +14,9 @@ compile 'com.otaliastudios:cameraview:1.2.1'
```
<p>
<img src="art/screen1.png" width="250" vspace="20" hspace="5">
<img src="art/screen2.png" width="250" vspace="20" hspace="5">
<img src="art/screen1.jpg" width="250" vspace="20" hspace="5">
<img src="art/screen2.jpg" width="250" vspace="20" hspace="5">
<img src="art/screen3.jpg" width="250" vspace="20" hspace="5">
</p>
*This was a fork of [CameraKit-Android library](https://github.com/gogopop/CameraKit-Android), originally a fork of [Google's CameraView library](https://github.com/google/cameraview), but has been [completely rewritten](https://github.com/natario1/CameraView/graphs/contributors?type=d). See [below](#roadmap) for a list of what was done. Feel free to contribute - this is under active development.*
@ -56,6 +57,7 @@ compile 'com.otaliastudios:cameraview:1.2.1'
- [Permissions Behavior](#permissions-behavior)
- [Manifest file](#manifest-file)
- [Roadmap](#roadmap)
- [Device-specific issues](#device-specific-issues)
## Usage
@ -525,3 +527,11 @@ These are still things that need to be done, off the top of my head:
- [ ] add onRequestPermissionResults for easy permission callback
- [ ] better error handling, maybe with a onError(e) method in the public listener, or have each public method return a boolean
- [ ] decent code coverage
## Device-specific issues
There are a couple of known issues if you are working with certain devices. The emulator is one of
the most tricky in this sense.
- Devices, or activities, with hardware acceleration turned off: this can be the case with emulators. In this case we will use SurfaceView as our surface provider. That is intrinsically flawed and can't deal with all we want to do here (runtime layout changes, scaling, etc.). So, nothing to do in this case.
- Devices with no support for MediaRecorder: the emulator does not support it, officially. This means that video/audio recording is flawed. Again, not our fault.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@ -4,10 +4,7 @@ package com.otaliastudios.cameraview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup;
@ -18,9 +15,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import org.mockito.stubbing.Stubber;
import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
@ -33,19 +28,18 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class CameraCallbacksTest extends BaseTest {
private CameraView camera;
private CameraView.CameraCallbacks callbacks;
private CameraListener listener;
private MockCameraController mockController;
private MockPreview mockPreview;
private MockCameraPreview mockPreview;
private Task<Boolean> task;
@ -58,14 +52,14 @@ public class CameraCallbacksTest extends BaseTest {
listener = mock(CameraListener.class);
camera = new CameraView(context) {
@Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
mockController = new MockCameraController(callbacks, preview);
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
mockController = new MockCameraController(callbacks);
return mockController;
}
@Override
protected Preview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockPreview(context, container);
protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockCameraPreview(context, container);
return mockPreview;
}
@ -74,8 +68,8 @@ public class CameraCallbacksTest extends BaseTest {
return true;
}
};
camera.instantiatePreview();
camera.addCameraListener(listener);
callbacks = camera.mCameraCallbacks;
task = new Task<>();
task.listen();
}
@ -87,26 +81,25 @@ public class CameraCallbacksTest extends BaseTest {
camera = null;
mockController = null;
mockPreview = null;
callbacks = null;
listener = null;
}
// Completes our task.
private Answer completeTask() {
return new Answer() {
private Stubber completeTask() {
return doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
task.end(true);
return null;
}
};
});
}
@Test
public void testDontDispatchIfRemoved() {
camera.removeCameraListener(listener);
doAnswer(completeTask()).when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null);
completeTask().when(listener).onCameraOpened(null);
camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null);
@ -115,8 +108,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDontDispatchIfCleared() {
camera.clearCameraListeners();
doAnswer(completeTask()).when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null);
completeTask().when(listener).onCameraOpened(null);
camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null);
@ -124,8 +117,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDispatchOnCameraOpened() {
doAnswer(completeTask()).when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null);
completeTask().when(listener).onCameraOpened(null);
camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNotNull(task.await(200));
verify(listener, times(1)).onCameraOpened(null);
@ -133,8 +126,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDispatchOnCameraClosed() {
doAnswer(completeTask()).when(listener).onCameraClosed();
callbacks.dispatchOnCameraClosed();
completeTask().when(listener).onCameraClosed();
camera.mCameraCallbacks.dispatchOnCameraClosed();
assertNotNull(task.await(200));
verify(listener, times(1)).onCameraClosed();
@ -142,8 +135,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDispatchOnVideoTaken() {
doAnswer(completeTask()).when(listener).onVideoTaken(null);
callbacks.dispatchOnVideoTaken(null);
completeTask().when(listener).onVideoTaken(null);
camera.mCameraCallbacks.dispatchOnVideoTaken(null);
assertNotNull(task.await(200));
verify(listener, times(1)).onVideoTaken(null);
@ -151,8 +144,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDispatchOnZoomChanged() {
doAnswer(completeTask()).when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
callbacks.dispatchOnZoomChanged(0f, null);
completeTask().when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
camera.mCameraCallbacks.dispatchOnZoomChanged(0f, null);
assertNotNull(task.await(200));
verify(listener, times(1)).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
@ -160,8 +153,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testDispatchOnExposureCorrectionChanged() {
doAnswer(completeTask()).when(listener).onExposureCorrectionChanged(0f, null, null);
callbacks.dispatchOnExposureCorrectionChanged(0f, null, null);
completeTask().when(listener).onExposureCorrectionChanged(0f, null, null);
camera.mCameraCallbacks.dispatchOnExposureCorrectionChanged(0f, null, null);
assertNotNull(task.await(200));
verify(listener, times(1)).onExposureCorrectionChanged(0f, null, null);
@ -174,8 +167,8 @@ public class CameraCallbacksTest extends BaseTest {
camera.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER);
PointF point = new PointF();
doAnswer(completeTask()).when(listener).onFocusStart(point);
callbacks.dispatchOnFocusStart(Gesture.TAP, point);
completeTask().when(listener).onFocusStart(point);
camera.mCameraCallbacks.dispatchOnFocusStart(Gesture.TAP, point);
assertNotNull(task.await(200));
verify(listener, times(1)).onFocusStart(point);
@ -190,8 +183,8 @@ public class CameraCallbacksTest extends BaseTest {
PointF point = new PointF();
boolean success = true;
doAnswer(completeTask()).when(listener).onFocusEnd(success, point);
callbacks.dispatchOnFocusEnd(Gesture.TAP, success, point);
completeTask().when(listener).onFocusEnd(success, point);
camera.mCameraCallbacks.dispatchOnFocusEnd(Gesture.TAP, success, point);
assertNotNull(task.await(200));
verify(listener, times(1)).onFocusEnd(success, point);
@ -200,31 +193,31 @@ public class CameraCallbacksTest extends BaseTest {
@Test
public void testOrientationCallbacks_deviceOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt());
completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called.
callbacks.onDeviceOrientationChanged(0);
camera.mCameraCallbacks.onDeviceOrientationChanged(0);
assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt());
}
@Test
public void testOrientationCallbacks_displayOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt());
completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called.
callbacks.onDisplayOffsetChanged(0);
camera.mCameraCallbacks.onDisplayOffsetChanged(0);
assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt());
}
@Test
public void testOrientationCallbacks_both() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt());
completeTask().when(listener).onOrientationChanged(anyInt());
// Assert called.
callbacks.onDisplayOffsetChanged(0);
callbacks.onDeviceOrientationChanged(90);
camera.mCameraCallbacks.onDisplayOffsetChanged(0);
camera.mCameraCallbacks.onDeviceOrientationChanged(90);
assertNotNull(task.await(200));
verify(listener, times(1)).onOrientationChanged(anyInt());
}
@ -290,9 +283,9 @@ public class CameraCallbacksTest extends BaseTest {
// Create fake JPEG array and trigger the process.
if (jpeg) {
callbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false);
camera.mCameraCallbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false);
} else {
callbacks.processSnapshot(mockYuv(imageDim[0], imageDim[1]), true, false);
camera.mCameraCallbacks.processSnapshot(mockYuv(imageDim[0], imageDim[1]), true, false);
}
// Wait for result and get out dimensions.

@ -26,7 +26,7 @@ public class CameraViewTest extends BaseTest {
private CameraView cameraView;
private MockCameraController mockController;
private Preview mockPreview;
private CameraPreview mockPreview;
private boolean hasPermissions;
@Before
@ -37,14 +37,14 @@ public class CameraViewTest extends BaseTest {
Context context = context();
cameraView = new CameraView(context) {
@Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
mockController = new MockCameraController(callbacks, preview);
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
mockController = new MockCameraController(callbacks);
return mockController;
}
@Override
protected Preview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockPreview(context, container);
protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockCameraPreview(context, container);
return mockPreview;
}
@ -53,6 +53,8 @@ public class CameraViewTest extends BaseTest {
return hasPermissions;
}
};
// Instantiate preview now.
cameraView.instantiatePreview();
}
});
}

@ -56,8 +56,8 @@ public class IntegrationTest extends BaseTest {
public void run() {
camera = new CameraView(rule.getActivity()) {
@Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
controller = new Camera1(callbacks, preview);
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
controller = new Camera1(callbacks);
return controller;
}
};

@ -15,8 +15,8 @@ public class MockCameraController extends CameraController {
boolean mZoomChanged;
boolean mExposureCorrectionChanged;
MockCameraController(CameraView.CameraCallbacks callback, Preview preview) {
super(callback, preview);
MockCameraController(CameraView.CameraCallbacks callback) {
super(callback);
}
void setMockCameraOptions(CameraOptions options) {

@ -7,15 +7,21 @@ import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
public class MockPreview extends Preview<View, Void> {
public class MockCameraPreview extends CameraPreview<View, Void> {
MockPreview(Context context, ViewGroup parent) {
MockCameraPreview(Context context, ViewGroup parent) {
super(context, parent, null);
}
private boolean mCropping = false;
public void setIsCropping(boolean crop) {
getView().setScaleX(crop ? 2 : 1);
getView().setScaleY(crop ? 2 : 1);
mCropping = crop;
}
@Override
boolean isCropping() {
return mCropping;
}
@NonNull

@ -19,14 +19,14 @@ import static org.mockito.Mockito.*;
public abstract class PreviewTest extends BaseTest {
protected abstract Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback);
protected abstract CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback);
@Rule
public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
protected Preview preview;
protected CameraPreview preview;
protected Size surfaceSize;
private Preview.SurfaceCallback callback;
private CameraPreview.SurfaceCallback callback;
private Task<Boolean> availability;
@Before
@ -40,7 +40,7 @@ public abstract class PreviewTest extends BaseTest {
TestActivity a = rule.getActivity();
surfaceSize = a.getContentSize();
callback = mock(Preview.SurfaceCallback.class);
callback = mock(CameraPreview.SurfaceCallback.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
@ -121,8 +121,10 @@ public abstract class PreviewTest extends BaseTest {
// If we apply a different aspect ratio, there should be cropping.
float desired = view * 1.2f;
if (preview.supportsCropping()) {
setDesiredAspectRatio(desired);
assertTrue(preview.isCropping());
}
// Since desired is 'desired', let's fake a new view size that is consistent with it.
// Ensure crop is not happening anymore.

@ -10,10 +10,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SurfaceViewPreviewTest extends PreviewTest {
public class SurfaceCameraPreviewTest extends PreviewTest {
@Override
protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) {
return new SurfaceViewPreview(context, parent, callback);
protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
return new SurfaceCameraPreview(context, parent, callback);
}
}

@ -10,11 +10,11 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TextureViewPreviewTest extends PreviewTest {
public class TextureCameraPreviewTest extends PreviewTest {
@Override
protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) {
return new TextureViewPreview(context, parent, callback);
protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
return new TextureCameraPreview(context, parent, callback);
}
@Override

@ -15,7 +15,6 @@ import android.support.annotation.WorkerThread;
import android.view.SurfaceHolder;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -57,8 +56,8 @@ class Camera1 extends CameraController {
private final Object mLock = new Object();
Camera1(CameraView.CameraCallbacks callback, final Preview preview) {
super(callback, preview);
Camera1(CameraView.CameraCallbacks callback) {
super(callback);
}
/**
@ -118,7 +117,7 @@ class Camera1 extends CameraController {
}
private boolean shouldSetup() {
return isCameraAvailable() && mPreview.isReady() && !mIsSetup;
return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsSetup;
}
// The act of binding an "open" camera to a "ready" preview.
@ -557,7 +556,7 @@ class Camera1 extends CameraController {
* This is called either on cameraView.start(), or when the underlying surface changes.
* It is possible that in the first call the preview surface has not already computed its
* dimensions.
* But when it does, the {@link Preview.SurfaceCallback} should be called,
* But when it does, the {@link CameraPreview.SurfaceCallback} should be called,
* and this should be refreshed.
*/
private Size computeCaptureSize() {

@ -22,8 +22,8 @@ import java.util.List;
@TargetApi(21)
class Camera2 extends CameraController {
public Camera2(CameraView.CameraCallbacks callback, Preview preview) {
super(callback, preview);
public Camera2(CameraView.CameraCallbacks callback) {
super(callback);
}
@Override

@ -2,14 +2,13 @@ package com.otaliastudios.cameraview;
import android.graphics.PointF;
import android.location.Location;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import java.io.File;
abstract class CameraController implements Preview.SurfaceCallback {
abstract class CameraController implements CameraPreview.SurfaceCallback {
private static final String TAG = CameraController.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -20,7 +19,7 @@ abstract class CameraController implements Preview.SurfaceCallback {
static final int STATE_STARTED = 2; // Camera is available and we can set parameters.
protected final CameraView.CameraCallbacks mCameraCallbacks;
protected final Preview mPreview;
protected CameraPreview mPreview;
protected Facing mFacing;
protected Flash mFlash;
@ -43,10 +42,8 @@ abstract class CameraController implements Preview.SurfaceCallback {
protected WorkerHandler mHandler;
CameraController(CameraView.CameraCallbacks callback, Preview preview) {
CameraController(CameraView.CameraCallbacks callback) {
mCameraCallbacks = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);
mHandler = WorkerHandler.get("CameraViewController");
mHandler.getThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
@ -67,6 +64,11 @@ abstract class CameraController implements Preview.SurfaceCallback {
});
}
void setPreview(CameraPreview cameraPreview) {
mPreview = cameraPreview;
mPreview.setSurfaceCallback(this);
}
//region Start&Stop
private String ss() {

@ -58,9 +58,9 @@ public class CameraView extends FrameLayout {
// Components
/* for tests */ CameraCallbacks mCameraCallbacks;
private CameraPreview mCameraPreview;
private OrientationHelper mOrientationHelper;
private CameraController mCameraController;
private Preview mPreviewImpl;
private ArrayList<CameraListener> mListeners = new ArrayList<>(2);
private MediaActionSound mSound;
@ -89,6 +89,7 @@ public class CameraView extends FrameLayout {
@SuppressWarnings("WrongConstant")
private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setWillNotDraw(false);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0);
// Self managed
int jpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, DEFAULT_JPEG_QUALITY);
@ -115,8 +116,7 @@ public class CameraView extends FrameLayout {
// Components
mCameraCallbacks = new Callbacks();
mPreviewImpl = instantiatePreview(context, this);
mCameraController = instantiateCameraController(mCameraCallbacks, mPreviewImpl);
mCameraController = instantiateCameraController(mCameraCallbacks);
mUiHandler = new Handler(Looper.getMainLooper());
mWorkerHandler = WorkerHandler.get("CameraViewWorker");
@ -158,23 +158,31 @@ public class CameraView extends FrameLayout {
}
}
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
return new Camera1(callbacks);
}
protected Preview instantiatePreview(Context context, ViewGroup container) {
protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
// TextureView is not supported without hardware acceleration.
LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated());
return isHardwareAccelerated() ?
new TextureViewPreview(context, container, null) :
new SurfaceViewPreview(context, container, null);
new TextureCameraPreview(context, container, null) :
new SurfaceCameraPreview(context, container, null);
}
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) {
return new Camera1(callbacks, preview);
/* for tests */ void instantiatePreview() {
mCameraPreview = instantiatePreview(getContext(), this);
mCameraController.setPreview(mCameraPreview);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mCameraPreview == null) {
// isHardwareAccelerated will return the real value only after we are
// attached. That's why we instantiate the preview here.
instantiatePreview();
}
if (!isInEditMode()) {
mOrientationHelper.enable(getContext());
}
@ -211,7 +219,7 @@ public class CameraView extends FrameLayout {
* aspect ratio.
*
* When both dimensions are MATCH_PARENT, CameraView will fill its
* parent no matter the preview. Thanks to what happens in {@link Preview}, this acts like
* parent no matter the preview. Thanks to what happens in {@link CameraPreview}, this acts like
* a CENTER CROP scale type.
*
* When both dimensions are WRAP_CONTENT, CameraView will take the biggest dimensions that
@ -235,18 +243,24 @@ public class CameraView extends FrameLayout {
final float previewWidth = flip ? previewSize.getHeight() : previewSize.getWidth();
final float previewHeight = flip ? previewSize.getWidth() : previewSize.getHeight();
// Pre-process specs
final ViewGroup.LayoutParams lp = getLayoutParams();
if (!mCameraPreview.supportsCropping()) {
// We can't allow EXACTLY constraints in this case.
if (widthMode == EXACTLY) widthMode = AT_MOST;
if (heightMode == EXACTLY) heightMode = AT_MOST;
} else {
// If MATCH_PARENT is interpreted as AT_MOST, transform to EXACTLY
// to be consistent with our semantics (and our docs).
final ViewGroup.LayoutParams lp = getLayoutParams();
if (widthMode == AT_MOST && lp.width == MATCH_PARENT) widthMode = EXACTLY;
if (heightMode == AT_MOST && lp.height == MATCH_PARENT) heightMode = EXACTLY;
LOG.i("onMeasure:", "requested dimensions are",
"(" + widthValue + "[" + ms(widthMode) + "]x" +
}
LOG.i("onMeasure:", "requested dimensions are", "(" + widthValue + "[" + ms(widthMode) + "]x" +
heightValue + "[" + ms(heightMode) + "])");
LOG.i("onMeasure:", "previewSize is", "(" + previewWidth + "x" + previewHeight + ")");
// If we have fixed dimensions (either 300dp or MATCH_PARENT), there's nothing we should do,
// (1) If we have fixed dimensions (either 300dp or MATCH_PARENT), there's nothing we should do,
// other than respect it. The preview will eventually be cropped at the sides (by PreviewImpl scaling)
// except the case in which these fixed dimensions manage to fit exactly the preview aspect ratio.
if (widthMode == EXACTLY && heightMode == EXACTLY) {
@ -256,8 +270,8 @@ public class CameraView extends FrameLayout {
return;
}
// If both dimensions are free, with no limits, then our size will be exactly the
// preview size. This can happen rarely, for example in scrollable containers.
// (2) If both dimensions are free, with no limits, then our size will be exactly the
// preview size. This can happen rarely, for example in 2d scrollable containers.
if (widthMode == UNSPECIFIED && heightMode == UNSPECIFIED) {
LOG.i("onMeasure:", "both are completely free.",
"We respect that and extend to the whole preview size.",
@ -271,7 +285,7 @@ public class CameraView extends FrameLayout {
// It's sure now that at least one dimension can be determined (either because EXACTLY or AT_MOST).
// This starts to seem a pleasant situation.
// If one of the dimension is completely free (e.g. in a scrollable container),
// (3) If one of the dimension is completely free (e.g. in a scrollable container),
// take the other and fit the ratio.
// One of the two might be AT_MOST, but we use the value anyway.
float ratio = previewHeight / previewWidth;
@ -292,7 +306,7 @@ public class CameraView extends FrameLayout {
return;
}
// At this point both dimensions are either AT_MOST-AT_MOST, EXACTLY-AT_MOST or AT_MOST-EXACTLY.
// (4) At this point both dimensions are either AT_MOST-AT_MOST, EXACTLY-AT_MOST or AT_MOST-EXACTLY.
// Let's manage this sanely. If only one is EXACTLY, we can TRY to fit the aspect ratio,
// but it is not guaranteed to succeed. It depends on the AT_MOST value of the other dimensions.
if (widthMode == EXACTLY || heightMode == EXACTLY) {
@ -313,8 +327,8 @@ public class CameraView extends FrameLayout {
return;
}
// Last case, AT_MOST and AT_MOST. Here we can SURELY fit the aspect ratio by filling one
// dimension and adapting the other.
// (5) Last case, AT_MOST and AT_MOST. Here we can SURELY fit the aspect ratio by
// filling one dimension and adapting the other.
int height, width;
float atMostRatio = (float) heightValue / (float) widthValue;
if (atMostRatio >= ratio) {
@ -1373,7 +1387,7 @@ public class CameraView extends FrameLayout {
@Override
public void run() {
byte[] jpeg2 = jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) {
if (mCropOutput && mCameraPreview.isCropping()) {
// If consistent, dimensions of the jpeg Bitmap and dimensions of getWidth(), getHeight()
// Live in the same reference system.
int w = consistentWithView ? getWidth() : getHeight();
@ -1395,7 +1409,7 @@ public class CameraView extends FrameLayout {
@Override
public void run() {
byte[] jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) {
if (mCropOutput && mCameraPreview.isCropping()) {
int w = consistentWithView ? getWidth() : getHeight();
int h = consistentWithView ? getHeight() : getWidth();
AspectRatio targetRatio = AspectRatio.of(w, h);

@ -1,10 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This FL could be removed, I added to test if we could implement
proper cropping with SurfaceView. I was not easily able to. -->
<FrameLayout
android:id="@+id/surface_view_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" />
android:layout_height="match_parent" />
</FrameLayout>
</merge>

@ -3,8 +3,9 @@
<TextureView
android:id="@+id/texture_view"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center" />
</merge>

@ -6,9 +6,9 @@ import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
abstract class Preview<T extends View, Output> {
abstract class CameraPreview<T extends View, Output> {
protected final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName());
private final static CameraLogger LOG = CameraLogger.create(CameraPreview.class.getSimpleName());
// Used for testing.
Task<Void> mCropTask = new Task<>();
@ -22,6 +22,7 @@ abstract class Preview<T extends View, Output> {
private SurfaceCallback mSurfaceCallback;
private T mView;
private boolean mCropping;
// As far as I can see, these are the view/surface dimensions.
// This live in the 'View' orientation.
@ -32,7 +33,7 @@ abstract class Preview<T extends View, Output> {
private int mDesiredWidth;
private int mDesiredHeight;
Preview(Context context, ViewGroup parent, SurfaceCallback callback) {
CameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
mView = onCreateView(context, parent);
mSurfaceCallback = callback;
}
@ -118,6 +119,12 @@ abstract class Preview<T extends View, Output> {
*/
private final void crop() {
mCropTask.start();
if (!supportsCropping()) {
mCropTask.end(null);
return;
};
getView().post(new Runnable() {
@Override
public void run() {
@ -137,8 +144,8 @@ abstract class Preview<T extends View, Output> {
// We must increase width.
scaleX = target.toFloat() / current.toFloat();
}
getView().setScaleX(scaleX);
getView().setScaleY(scaleY);
applyCrop(scaleX, scaleY);
mCropping = scaleX > 1.02f || scaleY > 1.02f;
LOG.i("crop:", "applied scaleX=", scaleX);
LOG.i("crop:", "applied scaleY=", scaleY);
mCropTask.end(null);
@ -146,14 +153,21 @@ abstract class Preview<T extends View, Output> {
});
}
protected void applyCrop(float scaleX, float scaleY) {
getView().setScaleX(scaleX);
getView().setScaleY(scaleY);
}
boolean supportsCropping() {
return true;
}
/**
* Whether we are cropping the output.
* If false, this means that the output image will match the visible bounds.
* @return true if cropping
*/
final boolean isCropping() {
// Account for some error
return getView().getScaleX() > 1.02f || getView().getScaleY() > 1.02f;
/* not final for tests */ boolean isCropping() {
return mCropping;
}
}

@ -0,0 +1,107 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
// Fallback preview when hardware acceleration is off.
// Currently this does NOT support cropping (e. g. the crop inside behavior),
// so we return false in supportsCropping() in order to have proper measuring.
// This means that CameraView is forced to be wrap_content.
class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
private final static CameraLogger LOG = CameraLogger.create(SurfaceCameraPreview.class.getSimpleName());
SurfaceCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback);
}
private SurfaceView mSurfaceView;
@NonNull
@Override
protected View onCreateView(Context context, ViewGroup parent) {
final View root = View.inflate(context, R.layout.surface_view, parent); // MATCH_PARENT
mSurfaceView = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = mSurfaceView.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
private boolean mFirstTime = true;
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Looks like this is too early to call anything.
// surfaceChanged is guaranteed to be called after, with exact dimensions.
LOG.i("callback:", "surfaceCreated");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime);
if (mFirstTime) {
onSurfaceAvailable(width, height);
mFirstTime = false;
} else {
onSurfaceSizeChanged(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed");
onSurfaceDestroyed();
mFirstTime = true;
}
});
return root.findViewById(R.id.surface_view_root);
}
@Override
Surface getSurface() {
return getOutput().getSurface();
}
@Override
SurfaceHolder getOutput() {
return mSurfaceView.getHolder();
}
@Override
Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class;
}
@Override
boolean supportsCropping() {
return false;
}
@Override
protected void applyCrop(float scaleX, float scaleY) {
/* float currWidth = getView().getWidth();
float currHeight = getView().getHeight();
float cropX = currWidth * (scaleX - 1) / 2f;
float cropY = currHeight * (scaleX - 1) / 2f;
LOG.e("applyCrop:", "currWidth:", currWidth, "currHeight:", currHeight);
LOG.e("applyCrop:", "scaleX:", scaleX, "scaleY:", scaleY);
mSurfaceView.setTop((int) -cropY);
mSurfaceView.setLeft((int) -cropX);
mSurfaceView.setBottom((int) (currHeight + cropY));
mSurfaceView.setRight((int) (currWidth + cropX));
LOG.e("applyCrop:", "top:", -cropY, "left:", -cropX, "bottom:", currHeight+cropY, "right:", currWidth+cropX);
mSurfaceView.requestLayout(); */
// mSurfaceView.getLayoutParams().width = (int) (currWidth * scaleX);
// mSurfaceView.getLayoutParams().height = (int) (currHeight * scaleY);
// mSurfaceView.setTranslationY(-cropY / 2f);
// mSurfaceView.setTranslationX(-cropX / 2f);
// getView().setScrollX((int) (cropX / 2f));
// getView().setScrollY((int) (cropY / 2f));
// mSurfaceView.requestLayout();
// super.applyCrop(scaleX, scaleY);
}
}

@ -1,60 +0,0 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
// Fallback preview when hardware acceleration is off.
class SurfaceViewPreview extends Preview<SurfaceView, SurfaceHolder> {
SurfaceViewPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback);
}
@NonNull
@Override
protected SurfaceView onCreateView(Context context, ViewGroup parent) {
final View root = View.inflate(context, R.layout.surface_view, parent); // MATCH_PARENT
SurfaceView surface = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = surface.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
onSurfaceAvailable(getView().getWidth(), getView().getHeight());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
onSurfaceSizeChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
onSurfaceDestroyed();
}
});
return surface;
}
@Override
Surface getSurface() {
return getOutput().getSurface();
}
@Override
SurfaceHolder getOutput() {
return getView().getHolder();
}
@Override
Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class;
}
}

@ -9,11 +9,11 @@ import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
class TextureViewPreview extends Preview<TextureView, SurfaceTexture> {
class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
private Surface mSurface;
TextureViewPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
TextureCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback);
}

@ -23,6 +23,5 @@ android {
dependencies {
compile project(':cameraview')
compile "com.android.support:appcompat-v7:$supportLibVersion"
compile 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
compile "com.android.support:design:$supportLibVersion"
}

@ -13,8 +13,9 @@
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:name=".CameraActivity"
android:theme="@style/Theme.MainActivity"
android:hardwareAccelerated="true"
android:configChanges="orientation|screenLayout|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

@ -1,39 +0,0 @@
package com.otaliastudios.cameraview.demo;
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
public class AutoUnfocusEditText extends AppCompatEditText {
public AutoUnfocusEditText(Context context) {
super(context);
}
public AutoUnfocusEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoUnfocusEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
closeKeyboard();
clearFocus();
return true;
}
return super.dispatchKeyEvent(event);
}
private void closeKeyboard() {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
}

@ -0,0 +1,223 @@
package com.otaliastudios.cameraview.demo;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetBehavior;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.Toast;
import com.otaliastudios.cameraview.Audio;
import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.Flash;
import com.otaliastudios.cameraview.Grid;
import com.otaliastudios.cameraview.SessionType;
import com.otaliastudios.cameraview.Size;
import com.otaliastudios.cameraview.VideoQuality;
import com.otaliastudios.cameraview.WhiteBalance;
import java.io.File;
public class CameraActivity extends AppCompatActivity implements View.OnClickListener, ControlView.Callback {
private CameraView camera;
private ViewGroup controlPanel;
private boolean mCapturingPicture;
private boolean mCapturingVideo;
// To show stuff in the callback
private Size mCaptureNativeSize;
private long mCaptureTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
setContentView(R.layout.activity_camera);
CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE);
camera = findViewById(R.id.camera);
camera.addCameraListener(new CameraListener() {
public void onCameraOpened(CameraOptions options) { onOpened(); }
public void onPictureTaken(byte[] jpeg) { onPicture(jpeg); }
public void onVideoTaken(File video) { onVideo(video); }
});
findViewById(R.id.edit).setOnClickListener(this);
findViewById(R.id.capturePhoto).setOnClickListener(this);
findViewById(R.id.captureVideo).setOnClickListener(this);
findViewById(R.id.toggleCamera).setOnClickListener(this);
controlPanel = findViewById(R.id.controls);
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
Control[] controls = Control.values();
for (Control control : controls) {
ControlView view = new ControlView(this, control, this);
group.addView(view, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
controlPanel.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_HIDDEN);
}
});
}
private void message(String content, boolean important) {
int length = important ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
Toast.makeText(this, content, length).show();
}
private void onOpened() {
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
for (int i = 0; i < group.getChildCount(); i++) {
ControlView view = (ControlView) group.getChildAt(i);
view.onCameraOpened(camera);
}
}
private void onPicture(byte[] jpeg) {
mCapturingPicture = false;
long callbackTime = System.currentTimeMillis();
if (mCapturingVideo) {
message("Captured while taking video. Size="+mCaptureNativeSize, false);
return;
}
// This can happen if picture was taken with a gesture.
if (mCaptureTime == 0) mCaptureTime = callbackTime - 300;
if (mCaptureNativeSize == null) mCaptureNativeSize = camera.getCaptureSize();
PicturePreviewActivity.setImage(jpeg);
Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class);
intent.putExtra("delay", callbackTime - mCaptureTime);
intent.putExtra("nativeWidth", mCaptureNativeSize.getWidth());
intent.putExtra("nativeHeight", mCaptureNativeSize.getHeight());
startActivity(intent);
mCaptureTime = 0;
mCaptureNativeSize = null;
}
private void onVideo(File video) {
mCapturingVideo = false;
Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class);
intent.putExtra("video", Uri.fromFile(video));
startActivity(intent);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.edit: edit(); break;
case R.id.capturePhoto: capturePhoto(); break;
case R.id.captureVideo: captureVideo(); break;
case R.id.toggleCamera: toggleCamera(); break;
}
}
private void edit() {
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
private void capturePhoto() {
if (mCapturingPicture) return;
mCapturingPicture = true;
mCaptureTime = System.currentTimeMillis();
mCaptureNativeSize = camera.getCaptureSize();
message("Capturing picture...", false);
camera.capturePicture();
}
private void captureVideo() {
if (camera.getSessionType() != SessionType.VIDEO) {
message("Can't record video while session type is 'picture'.", false);
return;
}
if (mCapturingPicture || mCapturingVideo) return;
mCapturingVideo = true;
message("Recording for 8 seconds...", true);
camera.startCapturingVideo(null, 8000);
}
private void toggleCamera() {
if (mCapturingPicture) return;
switch (camera.toggleFacing()) {
case BACK:
message("Switched to back camera!", false);
break;
case FRONT:
message("Switched to front camera!", false);
break;
}
}
@Override
public boolean onValueChanged(Control control, Object value, String name) {
if (!camera.isHardwareAccelerated() && (control == Control.WIDTH || control == Control.HEIGHT)) {
if ((Integer) value > 0) {
message("This device does not support hardware acceleration. " +
"In this case you can not change width or height. " +
"The view will act as WRAP_CONTENT by default.", true);
return false;
}
}
control.applyValue(camera, value);
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_HIDDEN);
message("Changed " + control.getName() + " to " + name, false);
return true;
}
//region Boilerplate
@Override
protected void onResume() {
super.onResume();
camera.start();
}
@Override
protected void onPause() {
super.onPause();
camera.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
camera.destroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean valid = true;
for (int grantResult : grantResults) {
valid = valid && grantResult == PackageManager.PERMISSION_GRANTED;
}
if (valid && !camera.isStarted()) {
camera.start();
}
}
//endregion
}

@ -0,0 +1,176 @@
package com.otaliastudios.cameraview.demo;
import android.view.View;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.Audio;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.Flash;
import com.otaliastudios.cameraview.Gesture;
import com.otaliastudios.cameraview.GestureAction;
import com.otaliastudios.cameraview.Grid;
import com.otaliastudios.cameraview.SessionType;
import com.otaliastudios.cameraview.VideoQuality;
import com.otaliastudios.cameraview.WhiteBalance;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Controls that we want to display in a ControlView.
*/
public enum Control {
WIDTH("Width", false),
HEIGHT("Height", true),
SESSION("Session type", false),
CROP_OUTPUT("Crop output", true),
FLASH("Flash", false),
WHITE_BALANCE("White balance", false),
GRID("Grid", true),
VIDEO_QUALITY("Video quality", false),
AUDIO("Audio", true),
PINCH("Pinch gesture", false),
HSCROLL("Horizontal scroll gesture", false),
VSCROLL("Vertical scroll gesture", false),
TAP("Single tap gesture", false),
LONG_TAP("Long tap gesture", true);
private String name;
private boolean last;
Control(String n, boolean l) {
name = n;
last = l;
}
public String getName() {
return name;
}
public boolean isSectionLast() {
return last;
}
public Collection<?> getValues(CameraView view) {
CameraOptions options = view.getCameraOptions();
switch (this) {
case WIDTH:
case HEIGHT:
View root = (View) view.getParent();
ArrayList<Integer> list = new ArrayList<>();
int boundary = this == WIDTH ? root.getWidth() : root.getHeight();
if (boundary == 0) boundary = 1000;
int step = boundary / 10;
list.add(ViewGroup.LayoutParams.WRAP_CONTENT);
list.add(ViewGroup.LayoutParams.MATCH_PARENT);
for (int i = step; i < boundary; i += step) {
list.add(i);
}
return list;
case SESSION: return Arrays.asList(SessionType.values());
case CROP_OUTPUT: return Arrays.asList(true, false);
case FLASH: return options.getSupportedFlash();
case WHITE_BALANCE: return options.getSupportedWhiteBalance();
case GRID: return Arrays.asList(Grid.values());
case VIDEO_QUALITY: return Arrays.asList(VideoQuality.values());
case AUDIO: return Arrays.asList(Audio.values());
case PINCH:
case HSCROLL:
case VSCROLL:
ArrayList<GestureAction> list1 = new ArrayList<>();
addIfSupported(options, list1, GestureAction.NONE);
addIfSupported(options, list1, GestureAction.ZOOM);
addIfSupported(options, list1, GestureAction.EXPOSURE_CORRECTION);
return list1;
case TAP:
case LONG_TAP:
ArrayList<GestureAction> list2 = new ArrayList<>();
addIfSupported(options, list2, GestureAction.NONE);
addIfSupported(options, list2, GestureAction.CAPTURE);
addIfSupported(options, list2, GestureAction.FOCUS);
addIfSupported(options, list2, GestureAction.FOCUS_WITH_MARKER);
return list2;
}
return null;
}
private void addIfSupported(CameraOptions options, List<GestureAction> list, GestureAction value) {
if (options.supports(value)) list.add(value);
}
public Object getCurrentValue(CameraView view) {
switch (this) {
case WIDTH: return view.getLayoutParams().width;
case HEIGHT: return view.getLayoutParams().height;
case SESSION: return view.getSessionType();
case CROP_OUTPUT: return view.getCropOutput();
case FLASH: return view.getFlash();
case WHITE_BALANCE: return view.getWhiteBalance();
case GRID: return view.getGrid();
case VIDEO_QUALITY: return view.getVideoQuality();
case AUDIO: return view.getAudio();
case PINCH: return view.getGestureAction(Gesture.PINCH);
case HSCROLL: return view.getGestureAction(Gesture.SCROLL_HORIZONTAL);
case VSCROLL: return view.getGestureAction(Gesture.SCROLL_VERTICAL);
case TAP: return view.getGestureAction(Gesture.TAP);
case LONG_TAP: return view.getGestureAction(Gesture.LONG_TAP);
}
return null;
}
public void applyValue(CameraView camera, Object value) {
switch (this) {
case WIDTH:
camera.getLayoutParams().width = (int) value;
camera.setLayoutParams(camera.getLayoutParams());
break;
case HEIGHT:
camera.getLayoutParams().height = (int) value;
camera.setLayoutParams(camera.getLayoutParams());
break;
case SESSION:
camera.setSessionType((SessionType) value);
break;
case CROP_OUTPUT:
camera.setCropOutput((boolean) value);
break;
case FLASH:
camera.setFlash((Flash) value);
break;
case WHITE_BALANCE:
camera.setWhiteBalance((WhiteBalance) value);
break;
case GRID:
camera.setGrid((Grid) value);
break;
case VIDEO_QUALITY:
camera.setVideoQuality((VideoQuality) value);
break;
case AUDIO:
camera.setAudio((Audio) value);
break;
case PINCH:
camera.mapGesture(Gesture.PINCH, (GestureAction) value);
break;
case HSCROLL:
camera.mapGesture(Gesture.SCROLL_HORIZONTAL, (GestureAction) value);
break;
case VSCROLL:
camera.mapGesture(Gesture.SCROLL_VERTICAL, (GestureAction) value);
break;
case TAP:
camera.mapGesture(Gesture.TAP, (GestureAction) value);
break;
case LONG_TAP:
camera.mapGesture(Gesture.LONG_TAP, (GestureAction) value);
break;
}
}
}

@ -0,0 +1,107 @@
package com.otaliastudios.cameraview.demo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import java.util.ArrayList;
import java.util.Collection;
public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener {
interface Callback {
boolean onValueChanged(Control control, Object value, String name);
}
private Value value;
private ArrayList<Value> values;
private ArrayList<String> valuesStrings;
private Control control;
private Callback callback;
private Spinner spinner;
public ControlView(Context context, Control control, Callback callback) {
super(context);
this.control = control;
this.callback = callback;
setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this);
TextView title = findViewById(R.id.title);
title.setText(control.getName());
View divider = findViewById(R.id.divider);
divider.setVisibility(control.isSectionLast() ? View.VISIBLE : View.GONE);
ViewGroup content = findViewById(R.id.content);
spinner = new Spinner(context, Spinner.MODE_DROPDOWN);
content.addView(spinner);
}
public Value getValue() {
return value;
}
@SuppressWarnings("all")
public void onCameraOpened(CameraView view) {
values = new ArrayList(control.getValues(view));
value = (Value) control.getCurrentValue(view);
valuesStrings = new ArrayList<>();
for (Value value : values) {
valuesStrings.add(stringify(value));
}
if (values.isEmpty()) {
spinner.setOnItemSelectedListener(null);
spinner.setEnabled(false);
spinner.setAlpha(0.8f);
spinner.setAdapter(new ArrayAdapter(getContext(),
R.layout.spinner_text, new String[]{ "Not supported." }));
spinner.setSelection(0, false);
} else {
spinner.setEnabled(true);
spinner.setAlpha(1f);
spinner.setAdapter(new ArrayAdapter(getContext(),
R.layout.spinner_text, valuesStrings));
spinner.setSelection(values.indexOf(value), false);
spinner.setOnItemSelectedListener(this);
}
}
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if (values.get(i) != value) {
Log.e("ControlView", "curr: " + value + " new: " + values.get(i));
if (!callback.onValueChanged(control, values.get(i), valuesStrings.get(i))) {
spinner.setSelection(values.indexOf(value)); // Go back.
} else {
value = values.get(i);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {}
private String stringify(Value value) {
if (value instanceof Integer) {
if ((Integer) value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent";
if ((Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content";
}
return String.valueOf(value).replace("_", " ").toLowerCase();
}
}

@ -1,375 +0,0 @@
package com.otaliastudios.cameraview.demo;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.Grid;
import com.otaliastudios.cameraview.SessionType;
import com.otaliastudios.cameraview.Size;
import com.otaliastudios.cameraview.VideoQuality;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity implements View.OnLayoutChangeListener {
@BindView(R.id.activity_main)
ViewGroup parent;
@BindView(R.id.camera)
CameraView camera;
// Capture Mode:
@BindView(R.id.sessionTypeRadioGroup)
RadioGroup sessionTypeRadioGroup;
// Crop Mode:
@BindView(R.id.cropModeRadioGroup)
RadioGroup cropModeRadioGroup;
// Video Quality:
@BindView(R.id.videoQualityRadioGroup)
RadioGroup videoQualityRadioGroup;
// Grid mode:
@BindView(R.id.gridModeRadioGroup)
RadioGroup gridModeRadioGroup;
// Width:
@BindView(R.id.screenWidth)
TextView screenWidth;
@BindView(R.id.width)
EditText width;
@BindView(R.id.widthUpdate)
Button widthUpdate;
@BindView(R.id.widthModeRadioGroup)
RadioGroup widthModeRadioGroup;
// Height:
@BindView(R.id.screenHeight)
TextView screenHeight;
@BindView(R.id.height)
EditText height;
@BindView(R.id.heightUpdate)
Button heightUpdate;
@BindView(R.id.heightModeRadioGroup)
RadioGroup heightModeRadioGroup;
private boolean mCapturingPicture;
private boolean mCapturingVideo;
// To show stuff in the callback
private Size mCaptureNativeSize;
private long mCaptureTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE);
parent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
screenWidth.setText("screen: " + parent.getWidth() + "px");
screenHeight.setText("screen: " + parent.getHeight() + "px");
}
});
sessionTypeRadioGroup.setOnCheckedChangeListener(sessionTypeChangedListener);
cropModeRadioGroup.setOnCheckedChangeListener(cropModeChangedListener);
widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener);
heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener);
videoQualityRadioGroup.setOnCheckedChangeListener(videoQualityChangedListener);
gridModeRadioGroup.setOnCheckedChangeListener(gridModeChangedListener);
camera.addOnLayoutChangeListener(this);
camera.addCameraListener(new CameraListener() {
@Override
public void onPictureTaken(byte[] jpeg) {
super.onPictureTaken(jpeg);
mCapturingPicture = false;
long callbackTime = System.currentTimeMillis();
if (mCapturingVideo) {
message("Captured while taking video. Size="+mCaptureNativeSize, false);
return;
}
PicturePreviewActivity.setImage(jpeg);
Intent intent = new Intent(MainActivity.this, PicturePreviewActivity.class);
intent.putExtra("delay", callbackTime - mCaptureTime);
intent.putExtra("nativeWidth", mCaptureNativeSize.getWidth());
intent.putExtra("nativeHeight", mCaptureNativeSize.getHeight());
startActivity(intent);
}
@Override
public void onVideoTaken(File video) {
super.onVideoTaken(video);
mCapturingVideo = false;
Intent intent = new Intent(MainActivity.this, VideoPreviewActivity.class);
intent.putExtra("video", Uri.fromFile(video));
startActivity(intent);
}
});
// Debug location.
/* camera.postDelayed(new Runnable() {
@Override
public void run() {
message("set Location", false);
camera.setLocation(-20, 40.12345);
}
}, 4500); */
}
private void message(String content, boolean important) {
int length = important ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
Toast.makeText(this, content, length).show();
}
@Override
protected void onResume() {
super.onResume();
camera.start();
}
@Override
protected void onPause() {
super.onPause();
camera.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
camera.destroy();
}
@OnClick(R.id.capturePhoto)
void capturePhoto() {
if (mCapturingPicture) return;
mCapturingPicture = true;
mCaptureTime = System.currentTimeMillis();
mCaptureNativeSize = camera.getCaptureSize();
message("Capturing picture...", false);
camera.capturePicture();
}
@OnClick(R.id.captureVideo)
void captureVideo() {
if (camera.getSessionType() != SessionType.VIDEO) {
message("Can't record video while session type is 'picture'.", false);
return;
}
if (mCapturingPicture || mCapturingVideo) return;
mCapturingVideo = true;
message("Recording for 8 seconds...", true);
camera.startCapturingVideo(null, 8000);
}
@OnClick(R.id.toggleCamera)
void toggleCamera() {
if (mCapturingPicture) return;
switch (camera.toggleFacing()) {
case BACK:
message("Switched to back camera!", false);
break;
case FRONT:
message("Switched to front camera!", false);
break;
}
}
@OnClick(R.id.toggleFlash)
void toggleFlash() {
if (mCapturingPicture) return;
switch (camera.toggleFlash()) {
case ON:
message("Flash on!", false);
break;
case OFF:
message("Flash off!", false);
break;
case AUTO:
message("Flash auto!", false);
break;
}
}
RadioGroup.OnCheckedChangeListener sessionTypeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingPicture) return;
boolean pic = checkedId == R.id.sessionTypePicture;
camera.setSessionType(pic ? SessionType.PICTURE : SessionType.VIDEO);
message("Session type set to" + (pic ? " picture!" : " video!"), true);
}
};
RadioGroup.OnCheckedChangeListener cropModeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingPicture) return;
camera.setCropOutput(checkedId == R.id.modeCropVisible);
message("Picture cropping is" + (checkedId == R.id.modeCropVisible ? " on!" : " off!"), false);
}
};
RadioGroup.OnCheckedChangeListener videoQualityChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingVideo) return;
VideoQuality videoQuality = VideoQuality.HIGHEST;
switch (checkedId) {
case R.id.videoQualityLowest: videoQuality = VideoQuality.LOWEST; break;
case R.id.videoQualityQvga: videoQuality = VideoQuality.MAX_QVGA; break;
case R.id.videoQuality480p: videoQuality = VideoQuality.MAX_480P; break;
case R.id.videoQuality720p: videoQuality = VideoQuality.MAX_720P; break;
case R.id.videoQuality1080p: videoQuality = VideoQuality.MAX_1080P; break;
case R.id.videoQuality2160p: videoQuality = VideoQuality.MAX_2160P; break;
case R.id.videoQualityHighest: videoQuality = VideoQuality.HIGHEST; break;
}
camera.setVideoQuality(videoQuality);
message("Video quality changed!", false);
}
};
RadioGroup.OnCheckedChangeListener gridModeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Grid grid = Grid.OFF;
switch (checkedId) {
case R.id.gridModeOff: grid = Grid.OFF; break;
case R.id.gridMode3x3: grid = Grid.DRAW_3X3; break;
case R.id.gridMode4x4: grid = Grid.DRAW_4X4; break;
case R.id.gridModeGolden: grid = Grid.DRAW_PHI; break;
}
camera.setGrid(grid);
message("Grid mode changed!", false);
}
};
@OnClick(R.id.widthUpdate)
void widthUpdateClicked() {
if (mCapturingPicture) return;
if (widthUpdate.getAlpha() >= 1) {
updateCamera(true, false);
}
}
RadioGroup.OnCheckedChangeListener widthModeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingPicture) return;
widthUpdate.setEnabled(checkedId == R.id.widthCustom);
widthUpdate.setAlpha(checkedId == R.id.widthCustom ? 1f : 0.3f);
width.clearFocus();
width.setEnabled(checkedId == R.id.widthCustom);
width.setAlpha(checkedId == R.id.widthCustom ? 1f : 0.5f);
updateCamera(true, false);
}
};
@OnClick(R.id.heightUpdate)
void heightUpdateClicked() {
if (mCapturingPicture) return;
if (heightUpdate.getAlpha() >= 1) {
updateCamera(false, true);
}
}
RadioGroup.OnCheckedChangeListener heightModeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingPicture) return;
heightUpdate.setEnabled(checkedId == R.id.heightCustom);
heightUpdate.setAlpha(checkedId == R.id.heightCustom ? 1f : 0.3f);
height.clearFocus();
height.setEnabled(checkedId == R.id.heightCustom);
height.setAlpha(checkedId == R.id.heightCustom ? 1f : 0.5f);
updateCamera(false, true);
}
};
private void updateCamera(boolean updateWidth, boolean updateHeight) {
if (mCapturingPicture) return;
ViewGroup.LayoutParams cameraLayoutParams = camera.getLayoutParams();
int width = cameraLayoutParams.width;
int height = cameraLayoutParams.height;
if (updateWidth) {
switch (widthModeRadioGroup.getCheckedRadioButtonId()) {
case R.id.widthCustom:
String widthInput = this.width.getText().toString();
try { width = Integer.valueOf(widthInput); } catch (Exception e) {}
break;
case R.id.widthWrapContent:
width = ViewGroup.LayoutParams.WRAP_CONTENT;
break;
case R.id.widthMatchParent:
width = ViewGroup.LayoutParams.MATCH_PARENT;
break;
}
}
if (updateHeight) {
switch (heightModeRadioGroup.getCheckedRadioButtonId()) {
case R.id.heightCustom:
String heightInput = this.height.getText().toString();
try { height = Integer.valueOf(heightInput); } catch (Exception e) {}
break;
case R.id.heightWrapContent:
height = ViewGroup.LayoutParams.WRAP_CONTENT;
break;
case R.id.heightMatchParent:
// We are in a vertically scrolling container, match parent would not work at all.
height = parent.getHeight();
break;
}
}
cameraLayoutParams.width = width;
cameraLayoutParams.height = height;
camera.addOnLayoutChangeListener(this);
camera.setLayoutParams(cameraLayoutParams);
String what = (updateWidth && updateHeight ? "Width and height" : updateWidth ? "Width" : "Height");
message(what + " updated! Internal preview size: " + camera.getPreviewSize(), false);
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int mCameraWidth = right - left;
int mCameraHeight = bottom - top;
width.setText(String.valueOf(mCameraWidth));
height.setText(String.valueOf(mCameraHeight));
camera.removeOnLayoutChangeListener(this);
}
}

@ -0,0 +1,50 @@
package com.otaliastudios.cameraview.demo;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.otaliastudios.cameraview.CameraView;
import java.util.ArrayList;
public class MessageView extends LinearLayout {
private TextView message;
private TextView title;
public MessageView(Context context) {
this(context, null);
}
public MessageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this);
ViewGroup content = findViewById(R.id.content);
inflate(context, R.layout.spinner_text, content);
title = findViewById(R.id.title);
message = (TextView) content.getChildAt(0);
}
public void setTitle(String title) {
this.title.setText(title);
}
public void setMessage(String message) {
this.message.setText(message);
}
}

@ -12,26 +12,9 @@ import com.otaliastudios.cameraview.CameraUtils;
import java.lang.ref.WeakReference;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PicturePreviewActivity extends Activity {
@BindView(R.id.image)
ImageView imageView;
@BindView(R.id.nativeCaptureResolution)
TextView nativeCaptureResolution;
@BindView(R.id.actualResolution)
TextView actualResolution;
@BindView(R.id.approxUncompressedSize)
TextView approxUncompressedSize;
@BindView(R.id.captureLatency)
TextView captureLatency;
private static WeakReference<byte[]> image;
public static void setImage(@Nullable byte[] im) {
@ -42,7 +25,11 @@ public class PicturePreviewActivity extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_picture_preview);
ButterKnife.bind(this);
final ImageView imageView = findViewById(R.id.image);
final MessageView nativeCaptureResolution = findViewById(R.id.nativeCaptureResolution);
final MessageView actualResolution = findViewById(R.id.actualResolution);
final MessageView approxUncompressedSize = findViewById(R.id.approxUncompressedSize);
final MessageView captureLatency = findViewById(R.id.captureLatency);
final long delay = getIntent().getLongExtra("delay", 0);
final int nativeWidth = getIntent().getIntExtra("nativeWidth", 0);
@ -57,14 +44,21 @@ public class PicturePreviewActivity extends Activity {
@Override
public void onBitmapReady(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
approxUncompressedSize.setText(getApproximateFileMegabytes(bitmap) + "MB");
captureLatency.setText(delay + " milliseconds");
approxUncompressedSize.setTitle("Approx. uncompressed size");
approxUncompressedSize.setMessage(getApproximateFileMegabytes(bitmap) + "MB");
captureLatency.setTitle("Capture latency");
captureLatency.setMessage(delay + " milliseconds");
// ncr and ar might be different when cropOutput is true.
AspectRatio nativeRatio = AspectRatio.of(nativeWidth, nativeHeight);
AspectRatio finalRatio = AspectRatio.of(bitmap.getWidth(), bitmap.getHeight());
nativeCaptureResolution.setText(nativeWidth + "x" + nativeHeight + " (" + nativeRatio + ")");
actualResolution.setText(bitmap.getWidth() + "x" + bitmap.getHeight() + " (" + finalRatio + ")");
nativeCaptureResolution.setTitle("Native capture resolution");
nativeCaptureResolution.setMessage(nativeWidth + "x" + nativeHeight + " (" + nativeRatio + ")");
actualResolution.setTitle("Actual resolution");
actualResolution.setMessage(bitmap.getWidth() + "x" + bitmap.getHeight() + " (" + finalRatio + ")");
}
});

@ -5,28 +5,29 @@ import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class VideoPreviewActivity extends Activity {
@BindView(R.id.video)
VideoView videoView;
@BindView(R.id.actualResolution)
TextView actualResolution;
private VideoView videoView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_preview);
ButterKnife.bind(this);
videoView = findViewById(R.id.video);
videoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
playVideo();
}
});
final MessageView actualResolution = findViewById(R.id.actualResolution);
Uri videoUri = getIntent().getParcelableExtra("video");
MediaController controller = new MediaController(this);
@ -38,7 +39,8 @@ public class VideoPreviewActivity extends Activity {
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
actualResolution.setText(mp.getVideoWidth() + " x " + mp.getVideoHeight());
actualResolution.setTitle("Actual resolution");
actualResolution.setMessage(mp.getVideoWidth() + " x " + mp.getVideoHeight());
ViewGroup.LayoutParams lp = videoView.getLayoutParams();
float videoWidth = mp.getVideoWidth();
float videoHeight = mp.getVideoHeight();
@ -50,8 +52,6 @@ public class VideoPreviewActivity extends Activity {
});
}
@OnClick(R.id.video)
void playVideo() {
if (videoView.isPlaying()) return;
videoView.start();

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary"/>
<corners android:radius="4dp"/>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccent"/>
</shape>

@ -5,5 +5,5 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:background="#FF444444"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Camera -->
<com.otaliastudios.cameraview.CameraView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:keepScreenOn="true"
app:cameraGrid="off"
app:cameraCropOutput="false"
app:cameraFacing="back"
app:cameraFlash="off"
app:cameraAudio="on"
app:cameraGestureTap="focusWithMarker"
app:cameraGestureLongTap="none"
app:cameraGesturePinch="zoom"
app:cameraGestureScrollHorizontal="exposureCorrection"
app:cameraGestureScrollVertical="none"
app:cameraJpegQuality="100"
app:cameraSessionType="picture" />
<!-- Controls -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:padding="16dp"
android:orientation="horizontal"
android:weightSum="3">
<ImageButton
android:id="@+id/edit"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_edit" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageButton
android:id="@+id/capturePhoto"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_photo" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageButton
android:id="@+id/captureVideo"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_video" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageButton
android:id="@+id/toggleCamera"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_switch" />
</LinearLayout>
<!-- Edit -->
<android.support.v4.widget.NestedScrollView
android:id="@+id/controls"
android:background="@android:color/white"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_hideable="true"
app:behavior_peekHeight="300dp"
app:behavior_skipCollapsed="false"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

@ -1,555 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Camera -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.otaliastudios.cameraview.CameraView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_gravity="center_horizontal"
android:keepScreenOn="true"
app:cameraGrid="off"
app:cameraCropOutput="false"
app:cameraFacing="back"
app:cameraFlash="off"
app:cameraAudio="on"
app:cameraGestureTap="focusWithMarker"
app:cameraGestureLongTap="none"
app:cameraGesturePinch="zoom"
app:cameraGestureScrollHorizontal="exposureCorrection"
app:cameraGestureScrollVertical="none"
app:cameraJpegQuality="100"
app:cameraSessionType="picture" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Controls -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="4">
<ImageButton
android:id="@+id/capturePhoto"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:padding="8dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_photo" />
<ImageButton
android:id="@+id/captureVideo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:padding="8dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_video" />
<ImageButton
android:id="@+id/toggleFlash"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:padding="8dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_flash" />
<ImageButton
android:id="@+id/toggleCamera"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:padding="8dp"
android:background="@drawable/background"
app:srcCompat="@drawable/ic_switch" />
</LinearLayout>
<!-- Session type -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="SESSION TYPE"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/sessionTypeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/sessionTypePicture"
android:orientation="horizontal">
<RadioButton
android:id="@+id/sessionTypePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Picture" />
<RadioButton
android:id="@+id/sessionTypeVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Video" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Crop output -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="CROP OUTPUT"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/cropModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/modeCropFullSize"
android:orientation="horizontal">
<RadioButton
android:id="@+id/modeCropFullSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Off" />
<RadioButton
android:id="@+id/modeCropVisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="On" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Width -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="WIDTH"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/screenWidth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="screen: 1080px"
android:textColor="@android:color/black"
android:textSize="11sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.otaliastudios.cameraview.demo.AutoUnfocusEditText
android:id="@+id/width"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="120dp"
android:hint="pixels"
android:inputType="number" />
<Button
android:id="@+id/widthUpdate"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="15dp"
android:background="@drawable/background"
android:gravity="center"
android:text="UPDATE"
android:stateListAnimator="@null"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
</FrameLayout>
<RadioGroup
android:id="@+id/widthModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:checkedButton="@+id/widthCustom"
android:orientation="horizontal">
<RadioButton
android:id="@+id/widthCustom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Custom" />
<RadioButton
android:id="@+id/widthWrapContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="wrap_content" />
<RadioButton
android:id="@+id/widthMatchParent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="match_parent" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Height -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="HEIGHT"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/screenHeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="screen: 1920px"
android:textColor="@android:color/black"
android:textSize="11sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.otaliastudios.cameraview.demo.AutoUnfocusEditText
android:id="@+id/height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="120dp"
android:hint="pixels"
android:inputType="number" />
<Button
android:id="@+id/heightUpdate"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="15dp"
android:background="@drawable/background"
android:gravity="center"
android:text="UPDATE"
android:stateListAnimator="@null"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
</FrameLayout>
<RadioGroup
android:id="@+id/heightModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:checkedButton="@+id/heightCustom"
android:orientation="horizontal">
<RadioButton
android:id="@+id/heightCustom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Custom" />
<RadioButton
android:id="@+id/heightWrapContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="wrap_content" />
<RadioButton
android:id="@+id/heightMatchParent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="match_parent" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Video Quality -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="VIDEO QUALITY"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<HorizontalScrollView
android:layout_marginTop="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioGroup
android:id="@+id/videoQualityRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkedButton="@+id/videoQuality480p"
android:orientation="horizontal">
<RadioButton
android:id="@+id/videoQualityLowest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="low" />
<RadioButton
android:id="@+id/videoQualityQvga"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QVGA" />
<RadioButton
android:id="@+id/videoQuality480p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="480p" />
<RadioButton
android:id="@+id/videoQuality720p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="720p" />
<RadioButton
android:id="@+id/videoQuality1080p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1080p" />
<RadioButton
android:id="@+id/videoQuality2160p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2160p" />
<RadioButton
android:id="@+id/videoQualityHighest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="high" />
</RadioGroup>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
<!-- Grid -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="GRID MODE"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/gridModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/gridModeOff"
android:orientation="horizontal">
<RadioButton
android:id="@+id/gridModeOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Off" />
<RadioButton
android:id="@+id/gridMode3x3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="3X3" />
<RadioButton
android:id="@+id/gridMode4x4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="4x4" />
<RadioButton
android:id="@+id/gridModeGolden"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Golden ratio" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -3,16 +3,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
@ -20,163 +15,25 @@
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="NATIVE CAPTURE RESOLUTION"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
<com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/nativeCaptureResolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textColor="@android:color/black"
android:textSize="14.5sp" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="ACTUAL RESOLUTION"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
android:layout_height="wrap_content"/>
<TextView
<com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/actualResolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textColor="@android:color/black"
android:textSize="14.5sp" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="APPROX. UNCOMPRESSED SIZE"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
android:layout_height="wrap_content"/>
<TextView
<com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/approxUncompressedSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textColor="@android:color/black"
android:textSize="14.5dp" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="CAPTURE LATENCY"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
<com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/captureLatency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textColor="@android:color/black"
android:textSize="14.5sp" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -14,45 +14,10 @@
android:layout_width="match_parent"
android:layout_height="200dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Resolution -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="ACTUAL RESOLUTION"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
<com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/actualResolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textColor="@android:color/black"
android:textSize="14.5sp" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
style="@style/TextAppearance.AppCompat.Subhead"
android:id="@+id/title"
android:layout_marginTop="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/content"
android:paddingTop="4dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
android:id="@+id/divider"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#44AAAAAA" />
</merge>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TextAppearance.AppCompat.Medium"
android:padding="12dp"
android:textColor="@android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

@ -2,5 +2,5 @@
<resources>
<color name="colorPrimary">#009966</color>
<color name="colorPrimaryDark">#00734d</color>
<color name="colorAccent">#65FF2D</color>
<color name="colorAccent">#57db27</color>
</resources>

@ -8,11 +8,7 @@
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Theme.MainActivity" parent="AppTheme">
</style>
<style name="Theme.PreviewActivity" parent="Theme.AppCompat.NoActionBar">
</style>
<style name="Theme.MainActivity" parent="AppTheme"/>
<style name="Theme.PreviewActivity" parent="Theme.AppCompat.NoActionBar"/>
</resources>

Loading…
Cancel
Save