Add discrete granularity to pinch and scroll events (#170)

* Add discrete granularity to pinch and scroll events

* Better tests
pull/172/head
Mattia Iavarone 7 years ago committed by GitHub
parent 07f6078385
commit 9b8cc16d96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  2. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 25
      cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
  4. 16
      cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java
  5. 26
      cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java

@ -206,6 +206,7 @@ public class CameraViewTest extends BaseTest {
@Test @Test
public void testGestureAction_zoom() { public void testGestureAction_zoom() {
mockController.mockStarted(true); mockController.mockStarted(true);
mockController.mZoomChanged = false;
MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
ui(new Runnable() { ui(new Runnable() {
@Override @Override
@ -214,10 +215,18 @@ public class CameraViewTest extends BaseTest {
public boolean onTouchEvent(MotionEvent event) { return true; } public boolean onTouchEvent(MotionEvent event) { return true; }
}; };
cameraView.mPinchGestureLayout.setGestureType(Gesture.PINCH); 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); cameraView.dispatchTouchEvent(event);
assertTrue(mockController.mZoomChanged); assertTrue(mockController.mZoomChanged);
} }
@ -230,7 +239,7 @@ public class CameraViewTest extends BaseTest {
when(o.getExposureCorrectionMaxValue()).thenReturn(10f); when(o.getExposureCorrectionMaxValue()).thenReturn(10f);
mockController.setMockCameraOptions(o); mockController.setMockCameraOptions(o);
mockController.mockStarted(true); mockController.mockStarted(true);
mockController.mExposureCorrectionChanged = false;
MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0);
ui(new Runnable() { ui(new Runnable() {
@Override @Override
@ -239,10 +248,17 @@ public class CameraViewTest extends BaseTest {
public boolean onTouchEvent(MotionEvent event) { return true; } public boolean onTouchEvent(MotionEvent event) { return true; }
}; };
cameraView.mScrollGestureLayout.setGestureType(Gesture.SCROLL_HORIZONTAL); 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); cameraView.dispatchTouchEvent(event);
assertTrue(mockController.mExposureCorrectionChanged); assertTrue(mockController.mExposureCorrectionChanged);
} }

@ -505,7 +505,9 @@ public class CameraView extends FrameLayout {
case ZOOM: case ZOOM:
oldValue = mCameraController.getZoomValue(); oldValue = mCameraController.getZoomValue();
newValue = source.scaleValue(oldValue, 0, 1); newValue = source.scaleValue(oldValue, 0, 1);
mCameraController.setZoom(newValue, points, true); if (newValue != oldValue) {
mCameraController.setZoom(newValue, points, true);
}
break; break;
case EXPOSURE_CORRECTION: case EXPOSURE_CORRECTION:
@ -513,8 +515,10 @@ public class CameraView extends FrameLayout {
float minValue = options.getExposureCorrectionMinValue(); float minValue = options.getExposureCorrectionMinValue();
float maxValue = options.getExposureCorrectionMaxValue(); float maxValue = options.getExposureCorrectionMaxValue();
newValue = source.scaleValue(oldValue, minValue, maxValue); newValue = source.scaleValue(oldValue, minValue, maxValue);
float[] bounds = new float[]{minValue, maxValue}; if (newValue != oldValue) {
mCameraController.setExposureCorrection(newValue, bounds, points, true); float[] bounds = new float[]{minValue, maxValue};
mCameraController.setExposureCorrection(newValue, bounds, points, true);
}
break; break;
} }
} }

@ -7,12 +7,12 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; 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 { 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 boolean mEnabled;
protected Gesture mType; protected Gesture mType;
protected PointF[] mPoints; protected PointF[] mPoints;
@ -48,5 +48,22 @@ abstract class GestureLayout extends FrameLayout {
return mPoints; return mPoints;
} }
// Implementors should call capValue at the end.
public abstract float scaleValue(float currValue, float minValue, float maxValue); 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;
}
} }

@ -3,12 +3,8 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.graphics.PointF; import android.graphics.PointF;
import android.os.Build; 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.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
import android.view.View;
class PinchGestureLayout extends GestureLayout { class PinchGestureLayout extends GestureLayout {
@ -16,7 +12,7 @@ class PinchGestureLayout extends GestureLayout {
ScaleGestureDetector mDetector; ScaleGestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
private float mAdditionFactor = 0; /* tests */ float mFactor = 0;
public PinchGestureLayout(Context context) { public PinchGestureLayout(Context context) {
@ -32,7 +28,7 @@ class PinchGestureLayout extends GestureLayout {
@Override @Override
public boolean onScale(ScaleGestureDetector detector) { public boolean onScale(ScaleGestureDetector detector) {
mNotify = true; mNotify = true;
mAdditionFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY); mFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY);
return true; return true;
} }
}); });
@ -75,7 +71,7 @@ class PinchGestureLayout extends GestureLayout {
@Override @Override
public float scaleValue(float currValue, float minValue, float maxValue) { public float scaleValue(float currValue, float minValue, float maxValue) {
float add = mAdditionFactor; float add = mFactor;
// ^ This works well if minValue = 0, maxValue = 1. // ^ This works well if minValue = 0, maxValue = 1.
// Account for the different range: // Account for the different range:
add *= (maxValue - minValue); add *= (maxValue - minValue);
@ -87,10 +83,6 @@ class PinchGestureLayout extends GestureLayout {
} else if (add < 0) { } else if (add < 0) {
add *= (currValue - minValue); add *= (currValue - minValue);
} Nope, I don't like this, it slows everything down. */ } Nope, I don't like this, it slows everything down. */
return capValue(currValue, currValue + add, minValue, maxValue);
float newValue = currValue + add;
if (newValue < minValue) newValue = minValue;
if (newValue > maxValue) newValue = maxValue;
return newValue;
} }
} }

@ -1,16 +1,9 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context; import android.content.Context;
import android.graphics.PointF; import android.graphics.PointF;
import android.util.Log;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
class ScrollGestureLayout extends GestureLayout { class ScrollGestureLayout extends GestureLayout {
@ -19,7 +12,7 @@ class ScrollGestureLayout extends GestureLayout {
private GestureDetector mDetector; private GestureDetector mDetector;
private boolean mNotify; private boolean mNotify;
private float mDistance; /* tests */ float mFactor;
public ScrollGestureLayout(Context context) { public ScrollGestureLayout(Context context) {
@ -47,8 +40,8 @@ class ScrollGestureLayout extends GestureLayout {
horizontal = mType == Gesture.SCROLL_HORIZONTAL; horizontal = mType == Gesture.SCROLL_HORIZONTAL;
} }
mPoints[1].set(e2.getX(), e2.getY()); mPoints[1].set(e2.getX(), e2.getY());
mDistance = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); mFactor = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight());
mDistance = horizontal ? -mDistance : mDistance; // When vertical, up = positive mFactor = horizontal ? -mFactor : mFactor; // When vertical, up = positive
mNotify = true; mNotify = true;
return true; return true;
} }
@ -80,21 +73,14 @@ class ScrollGestureLayout extends GestureLayout {
@Override @Override
public float scaleValue(float currValue, float minValue, float maxValue) { 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. // ^ This works well if minValue = 0, maxValue = 1.
// Account for the different range: // Account for the different range:
delta *= (maxValue - minValue); // -(max-min) ... (max-min) delta *= (maxValue - minValue); // -(max-min) ... (max-min)
delta *= 2; // Add some sensitivity.
// Add some sensitivity. return capValue(currValue, currValue + delta, minValue, maxValue);
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;
} }
} }

Loading…
Cancel
Save