diff --git a/library/src/main/api14/com/google/android/cameraview/Camera1.java b/library/src/main/api14/com/google/android/cameraview/Camera1.java index 5eeacd14..245d0b16 100644 --- a/library/src/main/api14/com/google/android/cameraview/Camera1.java +++ b/library/src/main/api14/com/google/android/cameraview/Camera1.java @@ -17,13 +17,19 @@ package com.google.android.cameraview; import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.os.Build; +import android.os.Handler; import android.support.v4.util.SparseArrayCompat; +import android.view.MotionEvent; import android.view.SurfaceHolder; +import android.view.View; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.SortedSet; @@ -71,6 +77,10 @@ class Camera1 extends CameraViewImpl { private int mDisplayOrientation; + private Handler mHandler = new Handler(); + + private Camera.AutoFocusCallback mAutofocusCallback; + Camera1(Callback callback, PreviewImpl preview) { super(callback, preview); preview.setCallback(new PreviewImpl.Callback() { @@ -107,6 +117,8 @@ void stop() { mCamera.stopPreview(); } mShowingPreview = false; + if (mHandler != null) + mHandler.removeCallbacksAndMessages(null); releaseCamera(); } @@ -443,12 +455,16 @@ private boolean setAutoFocusInternal(boolean autoFocus) { if (isCameraOpened()) { final List modes = mCameraParameters.getSupportedFocusModes(); if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + attachFocusTapListener(); mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { + detachFocusTapListener(); mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { + detachFocusTapListener(); mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); } else { + detachFocusTapListener(); mCameraParameters.setFocusMode(modes.get(0)); } return true; @@ -482,4 +498,118 @@ private boolean setFlashInternal(int flash) { } } + @TargetApi(14) + private void attachFocusTapListener() { + mPreview.getView().setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCamera != null) { + Camera.Parameters parameters = mCamera.getParameters(); + String focusMode = parameters.getFocusMode(); + Rect rect = calculateFocusArea(event.getX(), event.getY()); + List meteringAreas = new ArrayList<>(); + meteringAreas.add(new Camera.Area(rect, getFocusMeteringAreaWeight())); + if (parameters.getMaxNumFocusAreas() != 0 && focusMode != null && + (focusMode.equals(Camera.Parameters.FOCUS_MODE_AUTO) || + focusMode.equals(Camera.Parameters.FOCUS_MODE_MACRO) || + focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) || + focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) + ) { + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + parameters.setFocusAreas(meteringAreas); + if (parameters.getMaxNumMeteringAreas() > 0) { + parameters.setMeteringAreas(meteringAreas); + } + if(!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) { + return false; //cannot autoFocus + } + mCamera.setParameters(parameters); + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + resetFocus(success, camera); + } + }); + } else if (parameters.getMaxNumMeteringAreas() > 0) { + if(!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) { + return false; //cannot autoFocus + } + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + parameters.setFocusAreas(meteringAreas); + parameters.setMeteringAreas(meteringAreas); + + mCamera.setParameters(parameters); + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + resetFocus(success, camera); + } + }); + } else { + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + if (mAutofocusCallback != null) { + mAutofocusCallback.onAutoFocus(success, camera); + } + } + }); + } + } + } + return true; + } + }); + } + + @TargetApi(14) + private void resetFocus(final boolean success, final Camera camera) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (camera != null) { + camera.cancelAutoFocus(); + Camera.Parameters params = camera.getParameters(); + if (params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + params.setFocusAreas(null); + params.setMeteringAreas(null); + camera.setParameters(params); + } + + if (mAutofocusCallback != null) { + mAutofocusCallback.onAutoFocus(success, camera); + } + } + } + }, DELAY_MILLIS_BEFORE_RESETTING_FOCUS); + } + + Rect calculateFocusArea(float x, float y) { + int buffer = getFocusAreaSize() / 2; + int centerX = calculateCenter(x, mPreview.getView().getWidth(), buffer); + int centerY = calculateCenter(y, mPreview.getView().getHeight(), buffer); + return new Rect( + centerX - buffer, + centerY - buffer, + centerX + buffer, + centerY + buffer + ); + } + + static int calculateCenter(float coord, int dimen, int buffer) { + int normalized = (int) ((coord / dimen) * 2000 - 1000); + if (Math.abs(normalized) + buffer > 1000) { + if (normalized > 0) { + return 1000 - buffer; + } else { + return -1000 + buffer; + } + } else { + return normalized; + } + } + } diff --git a/library/src/main/api21/com/google/android/cameraview/Camera2.java b/library/src/main/api21/com/google/android/cameraview/Camera2.java index 4835f89e..3b1990f5 100644 --- a/library/src/main/api21/com/google/android/cameraview/Camera2.java +++ b/library/src/main/api21/com/google/android/cameraview/Camera2.java @@ -19,21 +19,26 @@ import android.annotation.TargetApi; import android.content.Context; import android.graphics.ImageFormat; +import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.support.annotation.NonNull; import android.util.Log; import android.util.SparseIntArray; +import android.view.MotionEvent; import android.view.Surface; +import android.view.View; import java.nio.ByteBuffer; import java.util.Arrays; @@ -525,14 +530,17 @@ void updateAutoFocus() { // Auto focus is not supported if (modes == null || modes.length == 0 || (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) { + detachFocusTapListener(); mAutoFocus = false; mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); } else { + attachFocusTapListener(); mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); } } else { + detachFocusTapListener(); mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); } @@ -671,6 +679,63 @@ void unlockFocus() { } } + private void attachFocusTapListener() { + mPreview.getView().setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCamera != null) { + Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + if (rect == null) return true; + int areaSize = getFocusAreaSize(); + int right = rect.right; + int bottom = rect.bottom; + int viewWidth = mPreview.getView().getWidth(); + int viewHeight = mPreview.getView().getHeight(); + int ll, rr; + Rect newRect; + int centerX = (int) event.getX(); + int centerY = (int) event.getY(); + ll = ((centerX * right) - areaSize) / viewWidth; + rr = ((centerY * bottom) - areaSize) / viewHeight; + int focusLeft = clamp(ll, 0, right); + int focusBottom = clamp(rr, 0, bottom); + newRect = new Rect(focusLeft, focusBottom, focusLeft + areaSize, focusBottom + areaSize); + MeteringRectangle meteringRectangle = new MeteringRectangle(newRect, getFocusMeteringAreaWeight()); + MeteringRectangle[] meteringRectangleArr = {meteringRectangle}; + + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangleArr); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangleArr); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START); + + updatePreview(); + } + } + return true; + } + }); + } + + private int clamp(int x, int min, int max) { + if (x < min) { + return min; + } else if (x > max) { + return max; + } else { + return x; + } + } + + private void updatePreview() { + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + /** * A {@link CameraCaptureSession.CaptureCallback} for capturing a still picture. */ diff --git a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java index e6f820bb..ab8eeec9 100644 --- a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java +++ b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java @@ -22,6 +22,10 @@ abstract class CameraViewImpl { + static final int FOCUS_AREA_SIZE_DEFAULT = 300; + static final int FOCUS_METERING_AREA_WEIGHT_DEFAULT = 1000; + static final int DELAY_MILLIS_BEFORE_RESETTING_FOCUS = 3000; + protected final Callback mCallback; protected final PreviewImpl mPreview; @@ -35,6 +39,18 @@ View getView() { return mPreview.getView(); } + int getFocusAreaSize() { + return FOCUS_AREA_SIZE_DEFAULT; + } + + int getFocusMeteringAreaWeight() { + return FOCUS_METERING_AREA_WEIGHT_DEFAULT; + } + + void detachFocusTapListener() { + mPreview.getView().setOnTouchListener(null); + } + /** * @return {@code true} if the implementation was able to start the camera session. */ diff --git a/library/src/main/base/com/google/android/cameraview/FocusMarkerLayout.java b/library/src/main/base/com/google/android/cameraview/FocusMarkerLayout.java new file mode 100644 index 00000000..1b03385f --- /dev/null +++ b/library/src/main/base/com/google/android/cameraview/FocusMarkerLayout.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package com.google.android.cameraview; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.TargetApi; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.ImageView; + +@TargetApi(14) +public class FocusMarkerLayout extends FrameLayout { + + private FrameLayout mFocusMarkerContainer; + private ImageView mFill; + + public FocusMarkerLayout(@NonNull Context context) { + this(context, null); + } + + public FocusMarkerLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(getContext()).inflate(R.layout.layout_focus_marker, this); + + mFocusMarkerContainer = (FrameLayout) findViewById(R.id.focusMarkerContainer); + mFill = (ImageView) findViewById(R.id.fill); + + mFocusMarkerContainer.setAlpha(0); + } + + public void focus(float mx, float my) { + int x = (int) (mx - mFocusMarkerContainer.getWidth() / 2); + int y = (int) (my - mFocusMarkerContainer.getWidth() / 2); + + mFocusMarkerContainer.setTranslationX(x); + mFocusMarkerContainer.setTranslationY(y); + + mFocusMarkerContainer.animate().setListener(null).cancel(); + mFill.animate().setListener(null).cancel(); + + mFill.setScaleX(0); + mFill.setScaleY(0); + mFill.setAlpha(1f); + + mFocusMarkerContainer.setScaleX(1.36f); + mFocusMarkerContainer.setScaleY(1.36f); + mFocusMarkerContainer.setAlpha(1f); + + mFocusMarkerContainer.animate().scaleX(1).scaleY(1).setStartDelay(0).setDuration(330) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mFocusMarkerContainer.animate().alpha(0).setStartDelay(750).setDuration(800).setListener(null).start(); + } + }).start(); + + mFill.animate().scaleX(1).scaleY(1).setDuration(330) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mFill.animate().alpha(0).setDuration(800).setListener(null).start(); + } + }).start(); + + } + + +} diff --git a/library/src/main/java/com/google/android/cameraview/CameraView.java b/library/src/main/java/com/google/android/cameraview/CameraView.java index 22fe1930..eab48e76 100644 --- a/library/src/main/java/com/google/android/cameraview/CameraView.java +++ b/library/src/main/java/com/google/android/cameraview/CameraView.java @@ -29,6 +29,8 @@ import android.support.v4.os.ParcelableCompatCreatorCallbacks; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; import android.widget.FrameLayout; import java.lang.annotation.Retention; @@ -125,6 +127,21 @@ public void onDisplayOrientationChanged(int displayOrientation) { mImpl.setDisplayOrientation(displayOrientation); } }; + + final FocusMarkerLayout focusMarkerLayout = new FocusMarkerLayout(getContext()); + addView(focusMarkerLayout); + focusMarkerLayout.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent motionEvent) { + int action = motionEvent.getAction(); + if (action == MotionEvent.ACTION_UP) { + focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY()); + } + + preview.getView().dispatchTouchEvent(motionEvent); + return true; + } + }); } @NonNull diff --git a/library/src/main/res/drawable/focus_marker_fill.xml b/library/src/main/res/drawable/focus_marker_fill.xml new file mode 100644 index 00000000..d5fc7756 --- /dev/null +++ b/library/src/main/res/drawable/focus_marker_fill.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/focus_marker_outline.xml b/library/src/main/res/drawable/focus_marker_outline.xml new file mode 100644 index 00000000..5628256c --- /dev/null +++ b/library/src/main/res/drawable/focus_marker_outline.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout-v14/layout_focus_marker.xml b/library/src/main/res/layout-v14/layout_focus_marker.xml new file mode 100644 index 00000000..81fdac7a --- /dev/null +++ b/library/src/main/res/layout-v14/layout_focus_marker.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file