From 9b8cc16d96e82b405e6e532802f8758c67765332 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 1 Mar 2018 00:41:55 +0100 Subject: [PATCH] Add discrete granularity to pinch and scroll events (#170) * Add discrete granularity to pinch and scroll events * Better tests --- .../cameraview/CameraViewTest.java | 26 +++++++++++++++---- .../otaliastudios/cameraview/CameraView.java | 10 ++++--- .../cameraview/GestureLayout.java | 25 +++++++++++++++--- .../cameraview/PinchGestureLayout.java | 16 +++--------- .../cameraview/ScrollGestureLayout.java | 26 +++++-------------- 5 files changed, 59 insertions(+), 44 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index 38618c01..b3f14516 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -206,6 +206,7 @@ public class CameraViewTest extends BaseTest { @Test public void testGestureAction_zoom() { mockController.mockStarted(true); + mockController.mZoomChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); ui(new Runnable() { @Override @@ -214,10 +215,18 @@ public class CameraViewTest extends BaseTest { public boolean onTouchEvent(MotionEvent event) { return true; } }; cameraView.mPinchGestureLayout.setGestureType(Gesture.PINCH); + cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); + } }); - mockController.mZoomChanged = false; - cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); + + // If factor is 0, we return the same value. The controller should not be notified. + cameraView.mPinchGestureLayout.mFactor = 0f; + cameraView.dispatchTouchEvent(event); + assertFalse(mockController.mZoomChanged); + + // For larger factors, the value is scaled. The controller should be notified. + cameraView.mPinchGestureLayout.mFactor = 1f; cameraView.dispatchTouchEvent(event); assertTrue(mockController.mZoomChanged); } @@ -230,7 +239,7 @@ public class CameraViewTest extends BaseTest { when(o.getExposureCorrectionMaxValue()).thenReturn(10f); mockController.setMockCameraOptions(o); mockController.mockStarted(true); - + mockController.mExposureCorrectionChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); ui(new Runnable() { @Override @@ -239,10 +248,17 @@ public class CameraViewTest extends BaseTest { public boolean onTouchEvent(MotionEvent event) { return true; } }; cameraView.mScrollGestureLayout.setGestureType(Gesture.SCROLL_HORIZONTAL); + cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION); } }); - mockController.mExposureCorrectionChanged = false; - cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION); + + // If factor is 0, we return the same value. The controller should not be notified. + cameraView.mScrollGestureLayout.mFactor = 0f; + cameraView.dispatchTouchEvent(event); + assertFalse(mockController.mExposureCorrectionChanged); + + // For larger factors, the value is scaled. The controller should be notified. + cameraView.mScrollGestureLayout.mFactor = 1f; cameraView.dispatchTouchEvent(event); assertTrue(mockController.mExposureCorrectionChanged); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index aaa5a23e..46e35d49 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -505,7 +505,9 @@ public class CameraView extends FrameLayout { case ZOOM: oldValue = mCameraController.getZoomValue(); newValue = source.scaleValue(oldValue, 0, 1); - mCameraController.setZoom(newValue, points, true); + if (newValue != oldValue) { + mCameraController.setZoom(newValue, points, true); + } break; case EXPOSURE_CORRECTION: @@ -513,8 +515,10 @@ public class CameraView extends FrameLayout { float minValue = options.getExposureCorrectionMinValue(); float maxValue = options.getExposureCorrectionMaxValue(); newValue = source.scaleValue(oldValue, minValue, maxValue); - float[] bounds = new float[]{minValue, maxValue}; - mCameraController.setExposureCorrection(newValue, bounds, points, true); + if (newValue != oldValue) { + float[] bounds = new float[]{minValue, maxValue}; + mCameraController.setExposureCorrection(newValue, bounds, points, true); + } break; } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java index 6f1fcc36..5932198b 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java @@ -7,12 +7,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -/** - * Using Views to address the need to draw stuff, but think about it. - * Simple classes could be enough. - */ abstract class GestureLayout extends FrameLayout { + // The number of possible values between minValue and maxValue, for the scaleValue method. + // We could make this non-static (e.g. larger granularity for exposure correction). + private final static int GRANULARITY = 50; + protected boolean mEnabled; protected Gesture mType; protected PointF[] mPoints; @@ -48,5 +48,22 @@ abstract class GestureLayout extends FrameLayout { return mPoints; } + // Implementors should call capValue at the end. public abstract float scaleValue(float currValue, float minValue, float maxValue); + + // Checks for newValue to be between minValue and maxValue, + // and checks that it is 'far enough' from the oldValue, in order + // to reduce useless updates. + protected static float capValue(float oldValue, float newValue, float minValue, float maxValue) { + if (newValue < minValue) newValue = minValue; + if (newValue > maxValue) newValue = maxValue; + + float distance = (maxValue - minValue) / (float) GRANULARITY; + float half = distance / 2; + if (newValue >= oldValue - half && newValue <= oldValue + half) { + // Too close! Return the oldValue. + return oldValue; + } + return newValue; + } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java index 08c0cd62..1cb8acdb 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java @@ -3,12 +3,8 @@ package com.otaliastudios.cameraview; import android.content.Context; import android.graphics.PointF; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ScaleGestureDetector; -import android.view.View; class PinchGestureLayout extends GestureLayout { @@ -16,7 +12,7 @@ class PinchGestureLayout extends GestureLayout { ScaleGestureDetector mDetector; private boolean mNotify; - private float mAdditionFactor = 0; + /* tests */ float mFactor = 0; public PinchGestureLayout(Context context) { @@ -32,7 +28,7 @@ class PinchGestureLayout extends GestureLayout { @Override public boolean onScale(ScaleGestureDetector detector) { mNotify = true; - mAdditionFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY); + mFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY); return true; } }); @@ -75,7 +71,7 @@ class PinchGestureLayout extends GestureLayout { @Override public float scaleValue(float currValue, float minValue, float maxValue) { - float add = mAdditionFactor; + float add = mFactor; // ^ This works well if minValue = 0, maxValue = 1. // Account for the different range: add *= (maxValue - minValue); @@ -87,10 +83,6 @@ class PinchGestureLayout extends GestureLayout { } else if (add < 0) { add *= (currValue - minValue); } Nope, I don't like this, it slows everything down. */ - - float newValue = currValue + add; - if (newValue < minValue) newValue = minValue; - if (newValue > maxValue) newValue = maxValue; - return newValue; + return capValue(currValue, currValue + add, minValue, maxValue); } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java index 09cb84f9..f8e3b17a 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java @@ -1,16 +1,9 @@ package com.otaliastudios.cameraview; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.graphics.PointF; -import android.util.Log; import android.view.GestureDetector; -import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; class ScrollGestureLayout extends GestureLayout { @@ -19,7 +12,7 @@ class ScrollGestureLayout extends GestureLayout { private GestureDetector mDetector; private boolean mNotify; - private float mDistance; + /* tests */ float mFactor; public ScrollGestureLayout(Context context) { @@ -47,8 +40,8 @@ class ScrollGestureLayout extends GestureLayout { horizontal = mType == Gesture.SCROLL_HORIZONTAL; } mPoints[1].set(e2.getX(), e2.getY()); - mDistance = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); - mDistance = horizontal ? -mDistance : mDistance; // When vertical, up = positive + mFactor = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); + mFactor = horizontal ? -mFactor : mFactor; // When vertical, up = positive mNotify = true; return true; } @@ -80,21 +73,14 @@ class ScrollGestureLayout extends GestureLayout { @Override public float scaleValue(float currValue, float minValue, float maxValue) { - float delta = mDistance; // -1 ... 1 + float delta = mFactor; // -1 ... 1 // ^ This works well if minValue = 0, maxValue = 1. // Account for the different range: delta *= (maxValue - minValue); // -(max-min) ... (max-min) + delta *= 2; // Add some sensitivity. - // Add some sensitivity. - delta *= 2; - - // Cap - float newValue = currValue + delta; - if (newValue < minValue) newValue = minValue; - if (newValue > maxValue) newValue = maxValue; - LOG.i("curr="+currValue, "min="+minValue, "max="+maxValue, "out="+newValue); - return newValue; + return capValue(currValue, currValue + delta, minValue, maxValue); } }