I have a custom SwipeRefreshLayout that has a custom GridView inside it. I want to change the column number of that GridView based on pinch/zoom gesture. I have successfully implemented it. The problem is, the scale is too sensitive.
For example, i have column number 3-5. It is easy to scale to 3 or 5, but to make it 4 is hard, since the scale itself is too sensitive.
Here is my custom SwipeRefreshLayout class
/**
* This class contains fix from http://stackoverflow.com/questions/23989910/horizontalscrollview-inside-swiperefreshlayout
*/
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
private int mTouchSlop;
private float mPrevX;
private ScaleGestureDetector mScaleGestureDetector;
private ScaleListener mScaleListener;
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public void setScaleListener(Context context, ScaleListener scaleListener) {
this.mScaleListener = scaleListener;
mScaleGestureDetector = new ScaleGestureDetector(context, new MyOnScaleGestureListener());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mScaleGestureDetector != null) {
mScaleGestureDetector.onTouchEvent(event);
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
setEnabled(false);
if (mScaleListener != null) {
mScaleListener.onTwoFingerStart();
}
break;
case MotionEvent.ACTION_POINTER_UP:
setEnabled(true);
mPrevX = MotionEvent.obtain(event).getX();
if (mScaleListener != null) {
mScaleListener.onTwoFingerEnd();
}
break;
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
class MyOnScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private static final String TAG = "MyOnScaleGestureListene";
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (mScaleListener != null) {
// Too sensitive, must change to other approach
float scaleFactor = detector.getScaleFactor();
Log.d(TAG, "onScale: " + scaleFactor);
if (scaleFactor > 1F) {
mScaleListener.onScaleUp(scaleFactor);
} else if (scaleFactor < 1F) {
mScaleListener.onScaleDown(scaleFactor);
} else {
// no scale
}
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
public interface ScaleListener {
void onTwoFingersStart();
void onTwoFingersEnd();
void onScaleUp(float scaleFactor);
void onScaleDown(float scaleFactor);
}
}
Is there any way to reduce the sensitivity of ScaleGestureDetector.SimpleOnScaleGestureListener? If there is no way, is there any alternatives to solve it?
Here is a short video that showed the problem https://www.youtube.com/watch?v=0MItDNZ_o4c
I am able to solve the problem to meet my requirement. @azizbekian's answer was not really meet my requirement because spanDelta
always resets if my fingers too slow and if my fingers too fast, it can scale, but still hard to get 4 columns. But from his code example, i am able to create a workaround.
I just need to save initial scale distance(span) using detector.getCurrentSpan()
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mInitialDistance = detector.getCurrentSpan();
return true;
}
And then compare it directly with detector.getCurrentSpan()
every time onScale()
called. Then reset initial distance with mInitialDistance = detector.getCurrentSpan();
if scale occurred.
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (gestureTolerance(detector)) {
if (mScaleListener != null) {
float scaleFactor = detector.getScaleFactor();
if (scaleFactor > 1F) {
mScaleListener.onScaleUp(scaleFactor);
mInitialDistance = detector.getCurrentSpan();
} else if (scaleFactor < 1F) {
mScaleListener.onScaleDown(scaleFactor);
mInitialDistance = detector.getCurrentSpan();
}
}
}
return true;
}
private boolean gestureTolerance(@NonNull ScaleGestureDetector detector) {
final float currentDistance = detector.getCurrentSpan();
final float distanceDelta = Math.abs(mInitialDistance - currentDistance);
return distanceDelta > mScaleTriggerDistance;
}
Here is the complete code of my custom SwipeRefreshLayout
public class MyCustomSwipeRefreshLayout extends SwipeRefreshLayout {
private static final String TAG = "OneTouchRefreshFreeSwip";
private static final float DEFAULT_SCALE_TRIGGER_DISTANCE = 48;// in dp
private int mTouchSlop;
private float mPrevX;
private ScaleGestureDetector mScaleGestureDetector;
private ScaleListener mScaleListener;
private float mScaleTriggerDistance;
private float mInitialDistance;
public MyCustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public void setScaleListener(Context context, ScaleListener scaleListener) {
this.mScaleListener = scaleListener;
mScaleGestureDetector = new ScaleGestureDetector(context, new MyOnScaleGestureListener());
mScaleTriggerDistance = Util.dp2px(DEFAULT_SCALE_TRIGGER_DISTANCE, context);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mScaleGestureDetector != null) {
mScaleGestureDetector.onTouchEvent(event);
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
setEnabled(false);
if (mScaleListener != null) {
mScaleListener.onTwoFingersStart();
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (mScaleListener != null) {
mScaleListener.onTwoFingersEnd();
}
mPrevX = MotionEvent.obtain(event).getX();
setEnabled(true);
return true;
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
private boolean gestureTolerance(@NonNull ScaleGestureDetector detector) {
final float currentDistance = detector.getCurrentSpan();
final float distanceDelta = Math.abs(mInitialDistance - currentDistance);
return distanceDelta > mScaleTriggerDistance;
}
class MyOnScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (gestureTolerance(detector)) {
if (mScaleListener != null) {
float scaleFactor = detector.getScaleFactor();
if (scaleFactor > 1F) {
mScaleListener.onScaleUp(scaleFactor);
mInitialDistance = detector.getCurrentSpan();
} else if (scaleFactor < 1F) {
mScaleListener.onScaleDown(scaleFactor);
mInitialDistance = detector.getCurrentSpan();
}
}
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mInitialDistance = detector.getCurrentSpan();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
public interface ScaleListener {
void onTwoFingersStart();
void onTwoFingersEnd();
void onScaleUp(float scaleFactor);
void onScaleDown(float scaleFactor);
}
}