Skip to content

Commit

Permalink
Implement adjustViewBounds
Browse files Browse the repository at this point in the history
Change-Id: I0917adaa3deac4346039c3a4642e4923a6160990
yaraki committed Apr 1, 2016
1 parent f97ea8b commit a4f9936
Showing 12 changed files with 375 additions and 52 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
classpath 'com.android.tools.build:gradle:2.1.0-alpha5'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Original file line number Diff line number Diff line change
@@ -18,9 +18,11 @@

import com.google.android.cameraview.CameraView;

import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {

@@ -32,6 +34,10 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 16) {
// Hide the status bar
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
}
mCameraView = (CameraView) findViewById(R.id.camera);
if (mCameraView != null) {
mCameraView.addCallback(mCallback);
27 changes: 27 additions & 0 deletions demo/src/main/res/layout-land/activity_main.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<com.google.android.cameraview.CameraView
android:id="@+id/camera"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"/>

</FrameLayout>
7 changes: 2 additions & 5 deletions demo/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -16,15 +16,12 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

<com.google.android.cameraview.CameraView
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="wrap_content"
android:adjustViewBounds="true"/>

</FrameLayout>
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;

import java.io.Closeable;
import java.io.IOException;
@@ -49,7 +50,11 @@
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static junit.framework.Assert.assertFalse;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;

@RunWith(AndroidJUnit4.class)
@@ -122,6 +127,64 @@ public void check(View view, NoMatchingViewException noViewFoundException) {
});
}

@Test
public void testAspectRatio() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
AspectRatio ratio = cameraView.getAspectRatio();
assertThat(ratio, is(notNullValue()));
SizeMap map = cameraView.getSupportedPreviewSizes();
assertThat(map.ratios(), hasItem(ratio));
AspectRatio otherRatio = null;
for (AspectRatio r : map.ratios()) {
if (!r.equals(ratio)) {
otherRatio = r;
break;
}
}
if (otherRatio != null) {
cameraView.setAspectRatio(otherRatio);
assertThat(cameraView.getAspectRatio(), is(equalTo(otherRatio)));
}
}
});
}

@Test
public void testAdjustViewBounds() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
assertThat(cameraView.getAdjustViewBounds(), is(false));
cameraView.setAdjustViewBounds(true);
assertThat(cameraView.getAdjustViewBounds(), is(true));
}
})
.perform(new AnythingAction("layout") {
@Override
public void perform(UiController uiController, View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
view.setLayoutParams(params);
}
})
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
AspectRatio cameraRatio = cameraView.getAspectRatio();
AspectRatio viewRatio = new AspectRatio(view.getWidth(), view.getHeight());
assertThat(cameraRatio, is(either(equalTo(viewRatio))
.or(equalTo(viewRatio.inverse()))));
}
});
}

/**
* Wait for a camera to open.
*/
@@ -180,29 +243,39 @@ public void registerIdleTransitionCallback(ResourceCallback callback) {

}

private static class WaitAction implements ViewAction {
private static class WaitAction extends AnythingAction {

private final long mMs;

public WaitAction(long ms) {
super("wait");
mMs = ms;
}

@Override
public Matcher<View> getConstraints() {
return new IsAnything<>();
public void perform(UiController uiController, View view) {
SystemClock.sleep(mMs);
}

@Override
public String getDescription() {
return "wait";
}

private static abstract class AnythingAction implements ViewAction {

private final String mDescription;

public AnythingAction(String description) {
mDescription = description;
}

@Override
public void perform(UiController uiController, View view) {
SystemClock.sleep(mMs);
public Matcher<View> getConstraints() {
return new IsAnything<>();
}

@Override
public String getDescription() {
return mDescription;
}
}

}
Original file line number Diff line number Diff line change
@@ -94,6 +94,14 @@ public int compareTo(@NonNull AspectRatio another) {
return -1;
}

/**
* @return The inverse of this {@link AspectRatio}.
*/
public AspectRatio inverse() {
//noinspection SuspiciousNameCombination
return new AspectRatio(mY, mX);
}

private static int gcd(int a, int b) {
while (b != 0) {
int c = b;
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@

abstract class CameraViewImpl {

protected final InternalCameraViewCallback mCallback;
protected final Callback mCallback;

public CameraViewImpl(InternalCameraViewCallback callback) {
public CameraViewImpl(Callback callback) {
mCallback = callback;
}

@@ -39,4 +39,16 @@ public CameraViewImpl(InternalCameraViewCallback callback) {
abstract SizeMap getSupportedPreviewSizes();

abstract boolean isCameraOpened();

abstract void setAspectRatio(AspectRatio ratio);

abstract AspectRatio getAspectRatio();

interface Callback {

void onCameraOpened();

void onCameraClosed();

}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -24,24 +24,31 @@
import android.view.WindowManager;

import java.io.IOException;
import java.util.List;

@SuppressWarnings("deprecation")
class Camera1 extends CameraViewImpl {

private static final int INVALID_CAMERA_ID = -1;

private static final AspectRatio DEFAULT_ASPECT_RATIO = new AspectRatio(4, 3);

private final Context mContext;

private int mCameraId;

private Camera mCamera;

private Camera.Parameters mCameraParameters;

private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();

private final PreviewInfo mPreviewInfo = new PreviewInfo();

private final SizeMap mPreviewSizes = new SizeMap();

private AspectRatio mAspectRatio;

private static class PreviewInfo {
SurfaceTexture surface;
int width;
@@ -60,13 +67,17 @@ void configure(SurfaceTexture s, int w, int h) {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mPreviewInfo.configure(surface, width, height);
setUpPreview();
if (mCamera != null) {
setUpPreview();
}
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mPreviewInfo.configure(surface, width, height);
setUpPreview();
if (mCamera != null) {
setUpPreview();
}
}

@Override
@@ -80,7 +91,7 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
};

public Camera1(Context context, InternalCameraViewCallback callback) {
public Camera1(Context context, Callback callback) {
super(callback);
mContext = context;
}
@@ -131,6 +142,29 @@ boolean isCameraOpened() {
return mCamera != null;
}

@Override
void setAspectRatio(AspectRatio ratio) {
if (mAspectRatio == null || !isCameraOpened()) {
// Handle this later when camera is opened
mAspectRatio = ratio;
} else if (!mAspectRatio.equals(ratio)) {
final List<Size> sizes = mPreviewSizes.sizes(ratio);
if (sizes == null) {
throw new UnsupportedOperationException(ratio + " is not supported");
} else {
mAspectRatio = ratio;
Size size = chooseOptimalSize(sizes);
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
mCamera.setParameters(mCameraParameters);
}
}
}

@Override
AspectRatio getAspectRatio() {
return mAspectRatio;
}

/**
* This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
*/
@@ -151,14 +185,43 @@ private void openCamera() {
releaseCamera();
}
mCamera = Camera.open(mCameraId);
Camera.Parameters parameters = mCamera.getParameters();
mCameraParameters = mCamera.getParameters();
// Supported preview sizes
mPreviewSizes.clear();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
// AspectRatio
if (mAspectRatio == null) {
mAspectRatio = chooseAspectRatio();
} else {
final List<Size> sizes = mPreviewSizes.sizes(mAspectRatio);
if (sizes == null) { // Not supported
mAspectRatio = chooseAspectRatio();
} else { // The specified AspectRatio is supported
Size size = chooseOptimalSize(sizes);
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());
mCamera.setParameters(mCameraParameters);
}
}
mCallback.onCameraOpened();
}

private AspectRatio chooseAspectRatio() {
AspectRatio r = null;
for (AspectRatio ratio : mPreviewSizes.ratios()) {
r = ratio;
if (ratio.equals(DEFAULT_ASPECT_RATIO)) {
return ratio;
}
}
return r;
}

private Size chooseOptimalSize(List<Size> sizes) {
return sizes.get(0); // TODO: Pick optimally
}

private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
133 changes: 130 additions & 3 deletions library/src/main/java/com/google/android/cameraview/CameraView.java
Original file line number Diff line number Diff line change
@@ -18,8 +18,10 @@

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.TextureView;
import android.widget.FrameLayout;
@@ -28,9 +30,13 @@

public class CameraView extends FrameLayout {

private static final String TAG = "CameraView";

private final CameraViewImpl mImpl;

private final InternalCallbacks mCallbacks;
private final CallbackBridge mCallbacks;

private boolean mAdjustViewBounds;

public CameraView(Context context) {
this(context, null);
@@ -42,15 +48,58 @@ public CameraView(Context context, AttributeSet attrs) {

public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCallbacks = new InternalCallbacks();
// Internal setup
mCallbacks = new CallbackBridge();
if (Build.VERSION.SDK_INT < 21) {
mImpl = new Camera1(context, mCallbacks);
} else {
mImpl = new Camera1(context, mCallbacks); // TODO: Implement Camera2 and replace this
}
// View content
inflate(context, R.layout.camera_view, this);
TextureView textureView = (TextureView) findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(mImpl.getSurfaceTextureListener());
// Attributes
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,
R.style.Widget_CameraView);
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
a.recycle();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mAdjustViewBounds) {
if (!isCameraOpened()) {
mCallbacks.reserveRequestLayoutOnOpen();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
final AspectRatio ratio = getAspectRatio();
assert ratio != null;
int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat());
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
final AspectRatio ratio = getAspectRatio();
assert ratio != null;
int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

/**
@@ -79,22 +128,79 @@ public SizeMap getSupportedPreviewSizes() {
return mImpl.getSupportedPreviewSizes();
}

/**
* @return {@code true} if the camera is opened.
*/
public boolean isCameraOpened() {
return mImpl.isCameraOpened();
}

/**
* Add a new callback.
*
* @param callback The {@link Callback} to add.
* @see #removeCallback(Callback)
*/
public void addCallback(@NonNull Callback callback) {
mCallbacks.add(callback);
}

/**
* Remove a callback.
*
* @param callback The {@link Callback} to remove.
* @see #addCallback(Callback)
*/
public void removeCallback(@NonNull Callback callback) {
mCallbacks.remove(callback);
}

private class InternalCallbacks implements InternalCameraViewCallback {
/**
* @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to
* preserve the aspect ratio of camera.
* @see #getAdjustViewBounds()
*/
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (mAdjustViewBounds != adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
requestLayout();
}
}

/**
* @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of
* camera.
* @see #setAdjustViewBounds(boolean)
*/
public boolean getAdjustViewBounds() {
return mAdjustViewBounds;
}

/**
* Sets the aspect ratio of camera.
*
* @param ratio The {@AspectRatio} to be set.
*/
public void setAspectRatio(@NonNull AspectRatio ratio) {
mImpl.setAspectRatio(ratio);
}

/**
* Gets the current aspect ratio of camera.
*
* @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet.
*/
@Nullable
public AspectRatio getAspectRatio() {
return mImpl.getAspectRatio();
}

private class CallbackBridge implements CameraViewImpl.Callback {

private final ArrayList<Callback> mCallbacks = new ArrayList<>();

private boolean mRequestLayoutOnOpen;

public void add(Callback callback) {
mCallbacks.add(callback);
}
@@ -105,6 +211,10 @@ public void remove(Callback callback) {

@Override
public void onCameraOpened() {
if (mRequestLayoutOnOpen) {
mRequestLayoutOnOpen = false;
requestLayout();
}
for (Callback callback : mCallbacks) {
callback.onCameraOpened(CameraView.this);
}
@@ -116,14 +226,31 @@ public void onCameraClosed() {
callback.onCameraClosed(CameraView.this);
}
}

public void reserveRequestLayoutOnOpen() {
mRequestLayoutOnOpen = true;
}
}

/**
* Callback for monitoring events about {@link CameraView}.
*/
@SuppressWarnings("UnusedParameters")
public abstract static class Callback {

/**
* Called when camera is opened.
*
* @param cameraView The associated {@link CameraView}.
*/
public void onCameraOpened(CameraView cameraView) {
}

/**
* Called when camera is closed.
*
* @param cameraView The associated {@link CameraView}.
*/
public void onCameraClosed(CameraView cameraView) {
}
}
18 changes: 18 additions & 0 deletions library/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<declare-styleable name="CameraView">
<attr name="android:adjustViewBounds"/>
</declare-styleable>
</resources>
20 changes: 20 additions & 0 deletions library/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>

<style name="Widget.CameraView" parent="android:Widget">
<item name="android:adjustViewBounds">false</item>
</style>

</resources>

0 comments on commit a4f9936

Please sign in to comment.