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. 14
      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. 68
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  19. 15
      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. 183
      demo/src/main/res/layout/activity_picture_preview.xml
  40. 51
      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> <p>
<img src="art/screen1.png" width="250" vspace="20" hspace="5"> <img src="art/screen1.jpg" width="250" vspace="20" hspace="5">
<img src="art/screen2.png" 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> </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.* *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) - [Permissions Behavior](#permissions-behavior)
- [Manifest file](#manifest-file) - [Manifest file](#manifest-file)
- [Roadmap](#roadmap) - [Roadmap](#roadmap)
- [Device-specific issues](#device-specific-issues)
## Usage ## 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 - [ ] 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 - [ ] better error handling, maybe with a onError(e) method in the public listener, or have each public method return a boolean
- [ ] decent code coverage - [ ] 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.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -18,9 +15,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubber;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertEquals; 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.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@MediumTest @MediumTest
public class CameraCallbacksTest extends BaseTest { public class CameraCallbacksTest extends BaseTest {
private CameraView camera; private CameraView camera;
private CameraView.CameraCallbacks callbacks;
private CameraListener listener; private CameraListener listener;
private MockCameraController mockController; private MockCameraController mockController;
private MockPreview mockPreview; private MockCameraPreview mockPreview;
private Task<Boolean> task; private Task<Boolean> task;
@ -58,14 +52,14 @@ public class CameraCallbacksTest extends BaseTest {
listener = mock(CameraListener.class); listener = mock(CameraListener.class);
camera = new CameraView(context) { camera = new CameraView(context) {
@Override @Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) { protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
mockController = new MockCameraController(callbacks, preview); mockController = new MockCameraController(callbacks);
return mockController; return mockController;
} }
@Override @Override
protected Preview instantiatePreview(Context context, ViewGroup container) { protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockPreview(context, container); mockPreview = new MockCameraPreview(context, container);
return mockPreview; return mockPreview;
} }
@ -74,8 +68,8 @@ public class CameraCallbacksTest extends BaseTest {
return true; return true;
} }
}; };
camera.instantiatePreview();
camera.addCameraListener(listener); camera.addCameraListener(listener);
callbacks = camera.mCameraCallbacks;
task = new Task<>(); task = new Task<>();
task.listen(); task.listen();
} }
@ -87,26 +81,25 @@ public class CameraCallbacksTest extends BaseTest {
camera = null; camera = null;
mockController = null; mockController = null;
mockPreview = null; mockPreview = null;
callbacks = null;
listener = null; listener = null;
} }
// Completes our task. // Completes our task.
private Answer completeTask() { private Stubber completeTask() {
return new Answer() { return doAnswer(new Answer() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
task.end(true); task.end(true);
return null; return null;
} }
}; });
} }
@Test @Test
public void testDontDispatchIfRemoved() { public void testDontDispatchIfRemoved() {
camera.removeCameraListener(listener); camera.removeCameraListener(listener);
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null); verify(listener, never()).onCameraOpened(null);
@ -115,8 +108,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDontDispatchIfCleared() { public void testDontDispatchIfCleared() {
camera.clearCameraListeners(); camera.clearCameraListeners();
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null); verify(listener, never()).onCameraOpened(null);
@ -124,8 +117,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnCameraOpened() { public void testDispatchOnCameraOpened() {
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onCameraOpened(null); verify(listener, times(1)).onCameraOpened(null);
@ -133,8 +126,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnCameraClosed() { public void testDispatchOnCameraClosed() {
doAnswer(completeTask()).when(listener).onCameraClosed(); completeTask().when(listener).onCameraClosed();
callbacks.dispatchOnCameraClosed(); camera.mCameraCallbacks.dispatchOnCameraClosed();
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onCameraClosed(); verify(listener, times(1)).onCameraClosed();
@ -142,8 +135,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnVideoTaken() { public void testDispatchOnVideoTaken() {
doAnswer(completeTask()).when(listener).onVideoTaken(null); completeTask().when(listener).onVideoTaken(null);
callbacks.dispatchOnVideoTaken(null); camera.mCameraCallbacks.dispatchOnVideoTaken(null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onVideoTaken(null); verify(listener, times(1)).onVideoTaken(null);
@ -151,8 +144,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnZoomChanged() { public void testDispatchOnZoomChanged() {
doAnswer(completeTask()).when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class)); completeTask().when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
callbacks.dispatchOnZoomChanged(0f, null); camera.mCameraCallbacks.dispatchOnZoomChanged(0f, null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class)); verify(listener, times(1)).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
@ -160,8 +153,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnExposureCorrectionChanged() { public void testDispatchOnExposureCorrectionChanged() {
doAnswer(completeTask()).when(listener).onExposureCorrectionChanged(0f, null, null); completeTask().when(listener).onExposureCorrectionChanged(0f, null, null);
callbacks.dispatchOnExposureCorrectionChanged(0f, null, null); camera.mCameraCallbacks.dispatchOnExposureCorrectionChanged(0f, null, null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onExposureCorrectionChanged(0f, null, null); 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); camera.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER);
PointF point = new PointF(); PointF point = new PointF();
doAnswer(completeTask()).when(listener).onFocusStart(point); completeTask().when(listener).onFocusStart(point);
callbacks.dispatchOnFocusStart(Gesture.TAP, point); camera.mCameraCallbacks.dispatchOnFocusStart(Gesture.TAP, point);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onFocusStart(point); verify(listener, times(1)).onFocusStart(point);
@ -190,8 +183,8 @@ public class CameraCallbacksTest extends BaseTest {
PointF point = new PointF(); PointF point = new PointF();
boolean success = true; boolean success = true;
doAnswer(completeTask()).when(listener).onFocusEnd(success, point); completeTask().when(listener).onFocusEnd(success, point);
callbacks.dispatchOnFocusEnd(Gesture.TAP, success, point); camera.mCameraCallbacks.dispatchOnFocusEnd(Gesture.TAP, success, point);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onFocusEnd(success, point); verify(listener, times(1)).onFocusEnd(success, point);
@ -200,31 +193,31 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testOrientationCallbacks_deviceOnly() { public void testOrientationCallbacks_deviceOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called. // Assert not called. Both methods must be called.
callbacks.onDeviceOrientationChanged(0); camera.mCameraCallbacks.onDeviceOrientationChanged(0);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt()); verify(listener, never()).onOrientationChanged(anyInt());
} }
@Test @Test
public void testOrientationCallbacks_displayOnly() { public void testOrientationCallbacks_displayOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called. // Assert not called. Both methods must be called.
callbacks.onDisplayOffsetChanged(0); camera.mCameraCallbacks.onDisplayOffsetChanged(0);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt()); verify(listener, never()).onOrientationChanged(anyInt());
} }
@Test @Test
public void testOrientationCallbacks_both() { public void testOrientationCallbacks_both() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert called. // Assert called.
callbacks.onDisplayOffsetChanged(0); camera.mCameraCallbacks.onDisplayOffsetChanged(0);
callbacks.onDeviceOrientationChanged(90); camera.mCameraCallbacks.onDeviceOrientationChanged(90);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onOrientationChanged(anyInt()); verify(listener, times(1)).onOrientationChanged(anyInt());
} }
@ -290,9 +283,9 @@ public class CameraCallbacksTest extends BaseTest {
// Create fake JPEG array and trigger the process. // Create fake JPEG array and trigger the process.
if (jpeg) { if (jpeg) {
callbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false); camera.mCameraCallbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false);
} else { } 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. // Wait for result and get out dimensions.

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

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

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

@ -7,15 +7,21 @@ import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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); super(context, parent, null);
} }
private boolean mCropping = false;
public void setIsCropping(boolean crop) { public void setIsCropping(boolean crop) {
getView().setScaleX(crop ? 2 : 1); mCropping = crop;
getView().setScaleY(crop ? 2 : 1); }
@Override
boolean isCropping() {
return mCropping;
} }
@NonNull @NonNull

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

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

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

@ -15,7 +15,6 @@ import android.support.annotation.WorkerThread;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -57,8 +56,8 @@ class Camera1 extends CameraController {
private final Object mLock = new Object(); private final Object mLock = new Object();
Camera1(CameraView.CameraCallbacks callback, final Preview preview) { Camera1(CameraView.CameraCallbacks callback) {
super(callback, preview); super(callback);
} }
/** /**
@ -118,7 +117,7 @@ class Camera1 extends CameraController {
} }
private boolean shouldSetup() { 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. // 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. * 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 * It is possible that in the first call the preview surface has not already computed its
* dimensions. * 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. * and this should be refreshed.
*/ */
private Size computeCaptureSize() { private Size computeCaptureSize() {

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

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

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

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

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

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

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

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

@ -13,8 +13,9 @@
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MainActivity" android:name=".CameraActivity"
android:theme="@style/Theme.MainActivity" android:theme="@style/Theme.MainActivity"
android:hardwareAccelerated="true"
android:configChanges="orientation|screenLayout|keyboardHidden"> android:configChanges="orientation|screenLayout|keyboardHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <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 java.lang.ref.WeakReference;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PicturePreviewActivity extends Activity { 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; private static WeakReference<byte[]> image;
public static void setImage(@Nullable byte[] im) { public static void setImage(@Nullable byte[] im) {
@ -42,7 +25,11 @@ public class PicturePreviewActivity extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_picture_preview); 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 long delay = getIntent().getLongExtra("delay", 0);
final int nativeWidth = getIntent().getIntExtra("nativeWidth", 0); final int nativeWidth = getIntent().getIntExtra("nativeWidth", 0);
@ -57,14 +44,21 @@ public class PicturePreviewActivity extends Activity {
@Override @Override
public void onBitmapReady(Bitmap bitmap) { public void onBitmapReady(Bitmap bitmap) {
imageView.setImageBitmap(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. // ncr and ar might be different when cropOutput is true.
AspectRatio nativeRatio = AspectRatio.of(nativeWidth, nativeHeight); AspectRatio nativeRatio = AspectRatio.of(nativeWidth, nativeHeight);
AspectRatio finalRatio = AspectRatio.of(bitmap.getWidth(), bitmap.getHeight()); AspectRatio finalRatio = AspectRatio.of(bitmap.getWidth(), bitmap.getHeight());
nativeCaptureResolution.setText(nativeWidth + "x" + nativeHeight + " (" + nativeRatio + ")"); nativeCaptureResolution.setTitle("Native capture resolution");
actualResolution.setText(bitmap.getWidth() + "x" + bitmap.getHeight() + " (" + finalRatio + ")"); 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.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.MediaController; import android.widget.MediaController;
import android.widget.TextView; import android.widget.TextView;
import android.widget.VideoView; import android.widget.VideoView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class VideoPreviewActivity extends Activity { public class VideoPreviewActivity extends Activity {
@BindView(R.id.video) private VideoView videoView;
VideoView videoView;
@BindView(R.id.actualResolution)
TextView actualResolution;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_preview); 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"); Uri videoUri = getIntent().getParcelableExtra("video");
MediaController controller = new MediaController(this); MediaController controller = new MediaController(this);
@ -38,7 +39,8 @@ public class VideoPreviewActivity extends Activity {
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override @Override
public void onPrepared(MediaPlayer mp) { 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(); ViewGroup.LayoutParams lp = videoView.getLayoutParams();
float videoWidth = mp.getVideoWidth(); float videoWidth = mp.getVideoWidth();
float videoHeight = mp.getVideoHeight(); float videoHeight = mp.getVideoHeight();
@ -50,8 +52,6 @@ public class VideoPreviewActivity extends Activity {
}); });
} }
@OnClick(R.id.video)
void playVideo() { void playVideo() {
if (videoView.isPlaying()) return; if (videoView.isPlaying()) return;
videoView.start(); videoView.start();

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

@ -5,5 +5,5 @@
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFFFF" 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> </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,180 +3,37 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white"> android:background="@android:color/white">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<FrameLayout <ImageView
android:layout_width="match_parent" android:id="@+id/image"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_gravity="center_horizontal"
android:padding="16dp"> android:adjustViewBounds="true" />
<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
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" />
<TextView
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> <com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/nativeCaptureResolution"
</FrameLayout> android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<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" />
<TextView
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">
<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
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> <com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/actualResolution"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout> <com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/approxUncompressedSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout> <com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/captureLatency"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout> </LinearLayout>

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView
android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" android:layout_width="match_parent"
android:background="@android:color/white"> android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -14,45 +14,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp"/> android:layout_height="200dp"/>
<LinearLayout <com.otaliastudios.cameraview.demo.MessageView
android:id="@+id/actualResolution"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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
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>
</LinearLayout> </LinearLayout>
</ScrollView> </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> <resources>
<color name="colorPrimary">#009966</color> <color name="colorPrimary">#009966</color>
<color name="colorPrimaryDark">#00734d</color> <color name="colorPrimaryDark">#00734d</color>
<color name="colorAccent">#65FF2D</color> <color name="colorAccent">#57db27</color>
</resources> </resources>

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

Loading…
Cancel
Save