Create AutoFocusMarker and DefaultAutoFocusMarker

pull/484/head
Mattia Iavarone 6 years ago
parent cd5f0a12bf
commit 9209aa6a24
  1. 12
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  2. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/IntegrationTest.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java
  4. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  5. 41
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  6. 42
      cameraview/src/main/java/com/otaliastudios/cameraview/draw/AutoFocusMarker.java
  7. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/draw/AutoFocusTrigger.java
  8. 81
      cameraview/src/main/java/com/otaliastudios/cameraview/draw/DefaultAutoFocusMarker.java
  9. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/draw/Marker.java
  10. 69
      cameraview/src/main/java/com/otaliastudios/cameraview/draw/MarkerLayout.java
  11. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java
  12. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java
  13. 71
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/TapGestureLayout.java
  14. 0
      cameraview/src/main/res/drawable/cameraview_focus_marker_fill.xml
  15. 0
      cameraview/src/main/res/drawable/cameraview_focus_marker_outline.xml
  16. 40
      cameraview/src/main/res/layout/cameraview_layout_focus_marker.xml
  17. 22
      cameraview/src/main/res/values/attrs.xml
  18. 2
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  19. 5
      demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java
  20. 2
      demo/src/main/res/layout/activity_camera.xml
  21. 4
      docs/_posts/2018-12-20-camera-events.md
  22. 6
      docs/_posts/2018-12-20-controls.md
  23. 2
      docs/_posts/2018-12-20-v1-migration-guide.md

@ -192,12 +192,12 @@ public class CameraViewCallbacksTest 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();
completeTask().when(listener).onFocusStart(point); completeTask().when(listener).onAutoFocusStart(point);
camera.mCameraCallbacks.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)).onAutoFocusStart(point);
// Can't mock package protected. verify(camera.mTapGestureLayout, times(1)).onFocusStart(point); // Can't mock package protected. verify(camera.mTapGestureLayout, times(1)).onAutoFocusStart(point);
} }
@Test @Test
@ -208,12 +208,12 @@ public class CameraViewCallbacksTest extends BaseTest {
PointF point = new PointF(); PointF point = new PointF();
boolean success = true; boolean success = true;
completeTask().when(listener).onFocusEnd(success, point); completeTask().when(listener).onAutoFocusEnd(success, point);
camera.mCameraCallbacks.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)).onAutoFocusEnd(success, point);
// Can't mock package protected. verify(camera.mTapGestureLayout, times(1)).onFocusEnd(success); // Can't mock package protected. verify(camera.mTapGestureLayout, times(1)).onAutoFocusEnd(success);
} }
@Test @Test

@ -19,8 +19,6 @@ import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.internal.utils.Task; import com.otaliastudios.cameraview.internal.utils.Task;
@ -466,7 +464,7 @@ public class IntegrationTest extends BaseTest {
CameraOptions o = waitForOpen(true); CameraOptions o = waitForOpen(true);
final Task<PointF> focus = new Task<>(true); final Task<PointF> focus = new Task<>(true);
doEndTask(focus, 0).when(listener).onFocusStart(any(PointF.class)); doEndTask(focus, 0).when(listener).onAutoFocusStart(any(PointF.class));
camera.startAutoFocus(1, 1); camera.startAutoFocus(1, 1);
PointF point = focus.await(300); PointF point = focus.await(300);

@ -88,7 +88,7 @@ public abstract class CameraListener {
* @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight() * @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight()
*/ */
@UiThread @UiThread
public void onFocusStart(@NonNull PointF point) { } public void onAutoFocusStart(@NonNull PointF point) { }
/** /**
@ -101,7 +101,7 @@ public abstract class CameraListener {
* @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight() * @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight()
*/ */
@UiThread @UiThread
public void onFocusEnd(boolean successful, @NonNull PointF point) { } public void onAutoFocusEnd(boolean successful, @NonNull PointF point) { }
/** /**

@ -150,10 +150,9 @@ public class CameraOptions {
*/ */
public boolean supports(@NonNull GestureAction action) { public boolean supports(@NonNull GestureAction action) {
switch (action) { switch (action) {
case FOCUS: case AUTOFOCUS:
case FOCUS_WITH_MARKER:
return isAutoFocusSupported(); return isAutoFocusSupported();
case CAPTURE: case TAKE_PICTURE:
case NONE: case NONE:
return true; return true;
case ZOOM: case ZOOM:

@ -35,6 +35,7 @@ import com.otaliastudios.cameraview.controls.Control;
import com.otaliastudios.cameraview.controls.ControlParser; import com.otaliastudios.cameraview.controls.ControlParser;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.draw.MarkerLayout;
import com.otaliastudios.cameraview.engine.Camera1Engine; import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.Frame;
@ -65,6 +66,8 @@ import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.size.SizeSelector; import com.otaliastudios.cameraview.size.SizeSelector;
import com.otaliastudios.cameraview.size.SizeSelectorParser; import com.otaliastudios.cameraview.size.SizeSelectorParser;
import com.otaliastudios.cameraview.size.SizeSelectors; import com.otaliastudios.cameraview.size.SizeSelectors;
import com.otaliastudios.cameraview.draw.AutoFocusMarker;
import com.otaliastudios.cameraview.draw.AutoFocusTrigger;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -103,6 +106,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
private OrientationHelper mOrientationHelper; private OrientationHelper mOrientationHelper;
private CameraEngine mCameraEngine; private CameraEngine mCameraEngine;
private MediaActionSound mSound; private MediaActionSound mSound;
private AutoFocusMarker mAutoFocusMarker;
@VisibleForTesting List<CameraListener> mListeners = new CopyOnWriteArrayList<>(); @VisibleForTesting List<CameraListener> mListeners = new CopyOnWriteArrayList<>();
@VisibleForTesting List<FrameProcessor> mFrameProcessors = new CopyOnWriteArrayList<>(); @VisibleForTesting List<FrameProcessor> mFrameProcessors = new CopyOnWriteArrayList<>();
private Lifecycle mLifecycle; private Lifecycle mLifecycle;
@ -112,6 +116,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
PinchGestureLayout mPinchGestureLayout; PinchGestureLayout mPinchGestureLayout;
TapGestureLayout mTapGestureLayout; TapGestureLayout mTapGestureLayout;
ScrollGestureLayout mScrollGestureLayout; ScrollGestureLayout mScrollGestureLayout;
private MarkerLayout mMarkerLayout;
private boolean mKeepScreenOn; private boolean mKeepScreenOn;
@SuppressWarnings({"FieldCanBeLocal", "unused"}) @SuppressWarnings({"FieldCanBeLocal", "unused"})
private boolean mExperimental; private boolean mExperimental;
@ -168,10 +173,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mPinchGestureLayout = new PinchGestureLayout(context); mPinchGestureLayout = new PinchGestureLayout(context);
mTapGestureLayout = new TapGestureLayout(context); mTapGestureLayout = new TapGestureLayout(context);
mScrollGestureLayout = new ScrollGestureLayout(context); mScrollGestureLayout = new ScrollGestureLayout(context);
mMarkerLayout = new MarkerLayout(context);
addView(mGridLinesLayout); addView(mGridLinesLayout);
addView(mPinchGestureLayout); addView(mPinchGestureLayout);
addView(mTapGestureLayout); addView(mTapGestureLayout);
addView(mScrollGestureLayout); addView(mScrollGestureLayout);
addView(mMarkerLayout);
// Apply self managed // Apply self managed
setPlaySounds(playSounds); setPlaySounds(playSounds);
@ -527,12 +534,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//noinspection ConstantConditions //noinspection ConstantConditions
switch (action) { switch (action) {
case CAPTURE: case TAKE_PICTURE:
takePicture(); takePicture();
break; break;
case FOCUS: case AUTOFOCUS:
case FOCUS_WITH_MARKER:
mCameraEngine.startAutoFocus(gesture, points[0]); mCameraEngine.startAutoFocus(gesture, points[0]);
break; break;
@ -1041,6 +1047,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
/**
* Sets an {@link AutoFocusMarker} to be notified of autofocus start, end and fail events
* so that it can draw elements on screen.
*
* @param autoFocusMarker the marker, or null
*/
public void setAutoFocusMarker(@Nullable AutoFocusMarker autoFocusMarker) {
mAutoFocusMarker = autoFocusMarker;
mMarkerLayout.onMarker(MarkerLayout.TYPE_AUTOFOCUS, autoFocusMarker);
}
/** /**
* Sets the current delay in milliseconds to reset the focus after an autofocus process. * Sets the current delay in milliseconds to reset the focus after an autofocus process.
* *
@ -1721,12 +1739,15 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (gesture != null && mGestureMap.get(gesture) == GestureAction.FOCUS_WITH_MARKER) { mMarkerLayout.onEvent(MarkerLayout.TYPE_AUTOFOCUS, new PointF[]{ point });
mTapGestureLayout.onFocusStart(point); if (mAutoFocusMarker != null) {
AutoFocusTrigger trigger = gesture != null ?
AutoFocusTrigger.GESTURE : AutoFocusTrigger.METHOD;
mAutoFocusMarker.onAutoFocusStart(trigger, point);
} }
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
listener.onFocusStart(point); listener.onAutoFocusStart(point);
} }
} }
}); });
@ -1744,12 +1765,14 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
playSound(MediaActionSound.FOCUS_COMPLETE); playSound(MediaActionSound.FOCUS_COMPLETE);
} }
if (gesture != null && mGestureMap.get(gesture) == GestureAction.FOCUS_WITH_MARKER) { if (mAutoFocusMarker != null) {
mTapGestureLayout.onFocusEnd(success); AutoFocusTrigger trigger = gesture != null ?
AutoFocusTrigger.GESTURE : AutoFocusTrigger.METHOD;
mAutoFocusMarker.onAutoFocusEnd(trigger, success, point);
} }
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
listener.onFocusEnd(success, point); listener.onAutoFocusEnd(success, point);
} }
} }
}); });

@ -0,0 +1,42 @@
package com.otaliastudios.cameraview.draw;
import android.content.Context;
import android.graphics.PointF;
import android.view.View;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
/**
* A marker for the autofocus operations. Receives callback when focus starts,
* ends successfully or failed, and can be used to draw on screen.
*
* The point coordinates are meant with respect to {@link CameraView} width and height,
* so a 0, 0 point means that focus is happening on the top-left visible corner.
*/
public interface AutoFocusMarker extends Marker {
/**
* Called when the autofocus process has started.
*
* @param trigger the autofocus trigger
* @param point coordinates
*/
void onAutoFocusStart(@NonNull AutoFocusTrigger trigger, @NonNull PointF point);
/**
* Called when the autofocus process has ended, and the camera converged
* to a new focus or failed while trying to do so.
*
* @param trigger the autofocus trigger
* @param successful whether the operation succeeded
* @param point coordinates
*/
void onAutoFocusEnd(@NonNull AutoFocusTrigger trigger, boolean successful, @NonNull PointF point);
}

@ -0,0 +1,20 @@
package com.otaliastudios.cameraview.draw;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.gesture.GestureAction;
/**
* Gives information about what triggered the autofocus operation.
*/
public enum AutoFocusTrigger {
/**
* Autofocus was triggered by {@link GestureAction#AUTOFOCUS}.
*/
GESTURE,
/**
* Autofocus was triggered by the {@link CameraView#startAutoFocus(float, float)} method.
*/
METHOD
}

@ -0,0 +1,81 @@
package com.otaliastudios.cameraview.draw;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.PointF;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.otaliastudios.cameraview.R;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A default implementation of {@link AutoFocusMarker}.
* You can call {@link com.otaliastudios.cameraview.CameraView#setAutoFocusMarker(AutoFocusMarker)}
* passing in this class to have basic marker drawing.
*/
public class DefaultAutoFocusMarker implements AutoFocusMarker {
private View mContainer;
private View mFill;
@Nullable
@Override
public View onAttach(@NonNull Context context, @NonNull ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.cameraview_layout_focus_marker, container, false);
mContainer = view.findViewById(R.id.focusMarkerContainer);
mFill = view.findViewById(R.id.focusMarkerFill);
return view;
}
@Override
public void onAutoFocusStart(@NonNull AutoFocusTrigger trigger, @NonNull PointF point) {
if (trigger == AutoFocusTrigger.METHOD) return;
mContainer.clearAnimation();
mFill.clearAnimation();
mContainer.setScaleX(1.36f);
mContainer.setScaleY(1.36f);
mContainer.setAlpha(1f);
mFill.setScaleX(0);
mFill.setScaleY(0);
mFill.setAlpha(1f);
animate(mContainer, 1, 1, 300, 0, null);
animate(mFill, 1, 1, 300, 0, null);
}
@Override
public void onAutoFocusEnd(@NonNull AutoFocusTrigger trigger, boolean successful, @NonNull PointF point) {
if (trigger == AutoFocusTrigger.METHOD) return;
if (successful) {
animate(mContainer, 1, 0, 500, 0, null);
animate(mFill, 1, 0, 500, 0, null);
} else {
animate(mFill, 0, 0, 500, 0, null);
animate(mContainer, 1.36f, 1, 500, 0, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
animate(mContainer, 1.36f, 0, 200, 1000, null);
}
});
}
}
private static void animate(@NonNull View view, float scale, float alpha, long duration,
long delay, @Nullable Animator.AnimatorListener listener) {
view.animate()
.scaleX(scale)
.scaleY(scale)
.alpha(alpha)
.setDuration(duration)
.setStartDelay(delay)
.setListener(listener)
.start();
}
}

@ -0,0 +1,32 @@
package com.otaliastudios.cameraview.draw;
import android.content.Context;
import android.graphics.PointF;
import android.view.View;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A marker is an overlay over the {@link CameraView} preview, which should be drawn
* at specific times during the camera lifecycle.
* Currently only {@link AutoFocusMarker} is available.
*/
public interface Marker {
/**
* Marker is being attached to the CameraView. If a {@link View} is returned,
* it becomes part of the hierarchy and is automatically translated (if possible)
* to match the event place on screen, for example the point where autofocus was started
* by the user finger.
*
* @param context a context
* @param container a container
* @return a view or null
*/
@Nullable
View onAttach(@NonNull Context context, @NonNull ViewGroup container);
}

@ -0,0 +1,69 @@
package com.otaliastudios.cameraview.draw;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PointF;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import java.util.HashMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Manages markers and provides an hierarchy / Canvas for them.
* It is responsible for calling {@link Marker#onAttach(Context, ViewGroup)}.
*/
public final class MarkerLayout extends FrameLayout {
public final static int TYPE_AUTOFOCUS = 1;
@SuppressLint("UseSparseArrays")
private final HashMap<Integer, View> mViews = new HashMap<>();
public MarkerLayout(@NonNull Context context) {
super(context);
}
/**
* Notifies that a new marker was added, possibly replacing another one.
* @param type the marker type
* @param marker the marker
*/
public void onMarker(int type, @Nullable Marker marker) {
// First check if we have a view for a previous marker of this type.
View oldView = mViews.get(type);
if (oldView != null) removeView(oldView);
// If new marker is null, we're done.
if (marker == null) return;
// Now see if we have a new view.
View newView = marker.onAttach(getContext(), this);
if (newView != null) {
mViews.put(type, newView);
addView(newView);
}
}
/**
* The event that should trigger the drawing is about to be dispatched to
* markers. If we have a valid View, cancel any animations on it and reposition
* it.
* @param type the event type
* @param points the position
*/
public void onEvent(int type, @NonNull PointF[] points) {
View view = mViews.get(type);
if (view == null) return;
view.clearAnimation();
if (type == TYPE_AUTOFOCUS) {
// TODO can't be sure that getWidth and getHeight are available here.
PointF point = points[0];
float x = (int) (point.x - view.getWidth() / 2);
float y = (int) (point.y - view.getHeight() / 2);
view.setTranslationX(x);
view.setTranslationY(y);
}
}
}

@ -30,9 +30,8 @@ public enum Gesture {
* Single tap gesture, typically assigned to the focus control. * Single tap gesture, typically assigned to the focus control.
* This gesture can be mapped to one shot actions: * This gesture can be mapped to one shot actions:
* *
* - {@link GestureAction#FOCUS} * - {@link GestureAction#AUTOFOCUS}
* - {@link GestureAction#FOCUS_WITH_MARKER} * - {@link GestureAction#TAKE_PICTURE}
* - {@link GestureAction#CAPTURE}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
TAP(GestureType.ONE_SHOT), TAP(GestureType.ONE_SHOT),
@ -41,9 +40,8 @@ public enum Gesture {
* Long tap gesture. * Long tap gesture.
* This gesture can be mapped to one shot actions: * This gesture can be mapped to one shot actions:
* *
* - {@link GestureAction#FOCUS} * - {@link GestureAction#AUTOFOCUS}
* - {@link GestureAction#FOCUS_WITH_MARKER} * - {@link GestureAction#TAKE_PICTURE}
* - {@link GestureAction#CAPTURE}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
LONG_TAP(GestureType.ONE_SHOT), LONG_TAP(GestureType.ONE_SHOT),

@ -2,6 +2,7 @@ package com.otaliastudios.cameraview.gesture;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.draw.AutoFocusMarker;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -27,18 +28,10 @@ public enum GestureAction {
* *
* - {@link Gesture#TAP} * - {@link Gesture#TAP}
* - {@link Gesture#LONG_TAP} * - {@link Gesture#LONG_TAP}
*/
FOCUS(1, GestureType.ONE_SHOT),
/**
* Auto focus control, typically assigned to the tap gesture.
* On top of {@link #FOCUS}, this will draw a default marker on screen.
* This action can be mapped to one shot gestures:
* *
* - {@link Gesture#TAP} * To control marker drawing, please see {@link CameraView#setAutoFocusMarker(AutoFocusMarker)}
* - {@link Gesture#LONG_TAP}
*/ */
FOCUS_WITH_MARKER(2, GestureType.ONE_SHOT), AUTOFOCUS(1, GestureType.ONE_SHOT),
/** /**
* When triggered, this action will fire a picture shoot. * When triggered, this action will fire a picture shoot.
@ -47,7 +40,7 @@ public enum GestureAction {
* - {@link Gesture#TAP} * - {@link Gesture#TAP}
* - {@link Gesture#LONG_TAP} * - {@link Gesture#LONG_TAP}
*/ */
CAPTURE(3, GestureType.ONE_SHOT), TAKE_PICTURE(2, GestureType.ONE_SHOT),
/** /**
* Zoom control, typically assigned to the pinch gesture. * Zoom control, typically assigned to the pinch gesture.
@ -57,7 +50,7 @@ public enum GestureAction {
* - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_HORIZONTAL}
* - {@link Gesture#SCROLL_VERTICAL} * - {@link Gesture#SCROLL_VERTICAL}
*/ */
ZOOM(4, GestureType.CONTINUOUS), ZOOM(3, GestureType.CONTINUOUS),
/** /**
* Exposure correction control. * Exposure correction control.
@ -67,7 +60,7 @@ public enum GestureAction {
* - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_HORIZONTAL}
* - {@link Gesture#SCROLL_VERTICAL} * - {@link Gesture#SCROLL_VERTICAL}
*/ */
EXPOSURE_CORRECTION(5, GestureType.CONTINUOUS); EXPOSURE_CORRECTION(4, GestureType.CONTINUOUS);
final static GestureAction DEFAULT_PINCH = NONE; final static GestureAction DEFAULT_PINCH = NONE;

@ -24,9 +24,6 @@ public class TapGestureLayout extends GestureLayout {
private GestureDetector mDetector; private GestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
private FrameLayout mFocusMarkerContainer;
private ImageView mFocusMarkerFill;
public TapGestureLayout(@NonNull Context context) { public TapGestureLayout(@NonNull Context context) {
super(context, 1); super(context, 1);
mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { mDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@ -54,12 +51,6 @@ public class TapGestureLayout extends GestureLayout {
}); });
mDetector.setIsLongpressEnabled(true); mDetector.setIsLongpressEnabled(true);
// Views to draw the focus marker.
LayoutInflater.from(getContext()).inflate(R.layout.cameraview_layout_focus_marker, this);
mFocusMarkerContainer = findViewById(R.id.focusMarkerContainer);
mFocusMarkerFill = findViewById(R.id.fill);
} }
@Override @Override
@ -88,66 +79,4 @@ public class TapGestureLayout extends GestureLayout {
return 0; return 0;
} }
// Draw
private final Runnable mFocusMarkerHideRunnable = new Runnable() {
@Override
public void run() {
onFocusEnd(false);
}
};
public void onFocusStart(@NonNull PointF point) {
removeCallbacks(mFocusMarkerHideRunnable);
mFocusMarkerContainer.clearAnimation(); // animate().setListener(null).cancel();
mFocusMarkerFill.clearAnimation(); // animate().setListener(null).cancel();
float x = (int) (point.x - mFocusMarkerContainer.getWidth() / 2);
float y = (int) (point.y - mFocusMarkerContainer.getWidth() / 2);
mFocusMarkerContainer.setTranslationX(x);
mFocusMarkerContainer.setTranslationY(y);
mFocusMarkerContainer.setScaleX(1.36f);
mFocusMarkerContainer.setScaleY(1.36f);
mFocusMarkerContainer.setAlpha(1f);
mFocusMarkerFill.setScaleX(0);
mFocusMarkerFill.setScaleY(0);
mFocusMarkerFill.setAlpha(1f);
// Since onFocusEnd is not guaranteed to be called, we post a hide runnable just in case.
animate(mFocusMarkerContainer, 1, 1, 300, 0, null);
animate(mFocusMarkerFill, 1, 1, 300, 0, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
postDelayed(mFocusMarkerHideRunnable, 2000);
}
});
}
public void onFocusEnd(boolean success) {
if (success) {
animate(mFocusMarkerContainer, 1, 0, 500, 0, null);
animate(mFocusMarkerFill, 1, 0, 500, 0, null);
} else {
animate(mFocusMarkerFill, 0, 0, 500, 0, null);
animate(mFocusMarkerContainer, 1.36f, 1, 500, 0, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
animate(mFocusMarkerContainer, 1.36f, 0, 200, 1000, null);
}
});
}
}
private static void animate(@NonNull View view, float scale, float alpha, long duration, long delay,
@Nullable Animator.AnimatorListener listener) {
view.animate().scaleX(scale).scaleY(scale)
.alpha(alpha)
.setDuration(duration)
.setStartDelay(delay)
.setListener(listener)
.start();
}
} }

@ -1,25 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout
android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"> android:id="@+id/focusMarkerContainer"
android:alpha="0"
<FrameLayout android:layout_width="55dp"
android:id="@+id/focusMarkerContainer" android:layout_height="55dp">
android:alpha="0" <ImageView
android:layout_width="55dp" android:id="@+id/focusMarkerFill"
android:layout_height="55dp"> android:layout_width="match_parent"
android:layout_height="match_parent"
<ImageView android:src="@drawable/cameraview_focus_marker_fill" />
android:id="@+id/fill" <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:src="@drawable/focus_marker_fill" /> android:src="@drawable/cameraview_focus_marker_outline" />
</FrameLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/focus_marker_outline" />
</FrameLayout>
</FrameLayout>

@ -30,34 +30,32 @@
<attr name="cameraGestureTap" format="enum"> <attr name="cameraGestureTap" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="focus" value="1" /> <enum name="autofocus" value="1" />
<enum name="focusWithMarker" value="2" /> <enum name="takePicture" value="2" />
<enum name="capture" value="3" />
</attr> </attr>
<attr name="cameraGestureLongTap" format="enum"> <attr name="cameraGestureLongTap" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="focus" value="1" /> <enum name="autofocus" value="1" />
<enum name="focusWithMarker" value="2" /> <enum name="takePicture" value="2" />
<enum name="capture" value="3" />
</attr> </attr>
<attr name="cameraGesturePinch" format="enum"> <attr name="cameraGesturePinch" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="4" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="5" /> <enum name="exposureCorrection" value="4" />
</attr> </attr>
<attr name="cameraGestureScrollHorizontal" format="enum"> <attr name="cameraGestureScrollHorizontal" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="4" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="5" /> <enum name="exposureCorrection" value="4" />
</attr> </attr>
<attr name="cameraGestureScrollVertical" format="enum"> <attr name="cameraGestureScrollVertical" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="4" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="5" /> <enum name="exposureCorrection" value="4" />
</attr> </attr>
<attr name="cameraFacing" format="enum"> <attr name="cameraFacing" format="enum">

@ -20,6 +20,7 @@ import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.draw.DefaultAutoFocusMarker;
import java.io.File; import java.io.File;
@ -41,6 +42,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
camera = findViewById(R.id.camera); camera = findViewById(R.id.camera);
camera.setLifecycleOwner(this); camera.setLifecycleOwner(this);
camera.addCameraListener(new Listener()); camera.addCameraListener(new Listener());
camera.setAutoFocusMarker(new DefaultAutoFocusMarker());
findViewById(R.id.edit).setOnClickListener(this); findViewById(R.id.edit).setOnClickListener(this);
findViewById(R.id.capturePicture).setOnClickListener(this); findViewById(R.id.capturePicture).setOnClickListener(this);

@ -100,9 +100,8 @@ public enum Control {
case LONG_TAP: case LONG_TAP:
ArrayList<GestureAction> list2 = new ArrayList<>(); ArrayList<GestureAction> list2 = new ArrayList<>();
addIfSupported(options, list2, GestureAction.NONE); addIfSupported(options, list2, GestureAction.NONE);
addIfSupported(options, list2, GestureAction.CAPTURE); addIfSupported(options, list2, GestureAction.TAKE_PICTURE);
addIfSupported(options, list2, GestureAction.FOCUS); addIfSupported(options, list2, GestureAction.AUTOFOCUS);
addIfSupported(options, list2, GestureAction.FOCUS_WITH_MARKER);
return list2; return list2;
case GRID_COLOR: case GRID_COLOR:
ArrayList<GridColor> list3 = new ArrayList<>(); ArrayList<GridColor> list3 = new ArrayList<>();

@ -21,7 +21,7 @@
app:cameraGrid="off" app:cameraGrid="off"
app:cameraFlash="off" app:cameraFlash="off"
app:cameraAudio="on" app:cameraAudio="on"
app:cameraGestureTap="focusWithMarker" app:cameraGestureTap="autofocus"
app:cameraGestureLongTap="none" app:cameraGestureLongTap="none"
app:cameraGesturePinch="zoom" app:cameraGesturePinch="zoom"
app:cameraGestureScrollHorizontal="exposureCorrection" app:cameraGestureScrollHorizontal="exposureCorrection"

@ -30,9 +30,9 @@ camera.addCameraListener(new CameraListener() {
public void onOrientationChanged(int orientation) {} public void onOrientationChanged(int orientation) {}
public void onFocusStart(PointF point) {} public void onAutoFocusStart(PointF point) {}
public void onFocusEnd(boolean successful, PointF point) {} public void onAutoFocusEnd(boolean successful, PointF point) {}
public void onZoomChanged(float newValue, float[] bounds, PointF[] fingers) {} public void onZoomChanged(float newValue, float[] bounds, PointF[] fingers) {}

@ -155,17 +155,17 @@ The last two actions will trigger the focus callbacks:
cameraView.addCameraListener(new CameraListener() { cameraView.addCameraListener(new CameraListener() {
@Override @Override
public void onFocusStart(@NonNull PointF point) { public void onAutoFocusStart(@NonNull PointF point) {
// Auto focus was started by a gesture or by startAutoFocus(float, float). // Auto focus was started by a gesture or by startAutoFocus(float, float).
// The camera is currently trying to focus around that area. // The camera is currently trying to focus around that area.
// This can be used to draw things on screen. // This can be used to draw things on screen.
} }
@Override @Override
public void onFocusEnd(boolean successful, @NonNull PointF point) { public void onAutoFocusEnd(boolean successful, @NonNull PointF point) {
// Auto focus operation just ended. If successful, the camera will have converged // Auto focus operation just ended. If successful, the camera will have converged
// to a new focus point, and possibly changed exposure and white balance as well. // to a new focus point, and possibly changed exposure and white balance as well.
// The point is the same that was passed to onFocusStart. // The point is the same that was passed to onAutoFocusStart.
} }
}); });
``` ```

@ -198,5 +198,3 @@ way for the future. These changes are listed below:
- Default `Facing` value is not `BACK` anymore but rather a value that guarantees that you have cameras (if possible). - Default `Facing` value is not `BACK` anymore but rather a value that guarantees that you have cameras (if possible).
If device has no `BACK` cameras, defaults to `FRONT`. If device has no `BACK` cameras, defaults to `FRONT`.
- Removed `ExtraProperties` as it was useless. - Removed `ExtraProperties` as it was useless.
TODO: improve the focus marker drawing, move out of XML (accept a drawable?)

Loading…
Cancel
Save