Search code examples
androidtextviewtouchscrollviewswipe

Trouble with swiping and normal scrolling


My activity contains a TextView set in a ScrollView covering the entire screen. The content in the TextView is typically multiple pages in length.

I am trying to enable touch controls comprising of single tap, double tap, swipe left, swipe right and pinching and these are working fine. The problem however is the difficulty for the user to swipe left or right with accidentally scrolling the ScrollView up or down (and hence cancelling the swipe detection)

Here is my custom OnTouchListener that is used on my TextView

public class OnSwipeTouchListener implements OnTouchListener {

private final GestureDetector gestureDetector;
private boolean mTapConsumed = false;
private int mTapsDetected = 0;
private boolean mEnableTap = true;

public OnSwipeTouchListener (Context ctx){
    gestureDetector = new GestureDetector(ctx, new GestureListener());
}

private double mPinchOriginalDistance = 0;
private float mPinchLastScale = 1.0f;               //scale change for current pinch action
private ScrollView mScrollView;

@Override
public boolean onTouch(View v, MotionEvent event) {

    mScrollView = (ScrollView) v.getParent();
    if(event.getPointerCount() == 2) {
        mScrollView.requestDisallowInterceptTouchEvent(true);
        mTapConsumed = true;
        mTapsDetected = 0;
        switch(event.getAction() & MotionEvent.ACTION_MASK){

            case MotionEvent.ACTION_POINTER_DOWN:
                //pinch mode
                float distx, disty;
                //Get the current distance
                distx = event.getX(0) - event.getX(1);
                disty = event.getY(0) - event.getY(1);
                mPinchOriginalDistance = Math.sqrt(distx * distx + disty * disty);
                break;

            case MotionEvent.ACTION_MOVE:
                //Get the current distance
                distx = event.getX(0) - event.getX(1);
                disty = event.getY(0) - event.getY(1);
                double distance = Math.sqrt(distx * distx + disty * disty);
                if(mPinchOriginalDistance == 0)
                    break;
                float scale = (float)(distance / mPinchOriginalDistance);
                mPinchLastScale = scale;
                onPinchScaleChange(scale);
                break;

            case MotionEvent.ACTION_POINTER_UP:
                onPinchFinal(mPinchLastScale);
                mPinchOriginalDistance = 0;
                mPinchLastScale = 1.0f;
                break;

            default:
                break;
        }
        return true;
    }
    else
        return gestureDetector.onTouchEvent(event);

}

private final class GestureListener extends SimpleOnGestureListener {

    private static final int SWIPE_THRESHOLD = 80;
    private static final int SWIPE_VELOCITY_THRESHOLD = 60;
    private final static int GESTURE_TAP_DELAY = 300;
    private final static int GESTURE_FLING_DELAY = 1000;


    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        if(!mEnableTap)
            return true;
        mTapConsumed = false;
        mTapsDetected++;
        if(mTapsDetected == 1)
            handler.postDelayed(checkTap, GESTURE_TAP_DELAY);
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        if(!mEnableTap)
            return true;
        mTapConsumed = false;
        mTapsDetected++;
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        mEnableTap = false;
        mTapConsumed = true;
        mTapsDetected = 0;
        handler.removeCallbacks(postFling);
        handler.postDelayed(postFling, GESTURE_FLING_DELAY);    //dont allow a tap to be registered for a certain time
        boolean result = false;
        try {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();
            flingData(diffX, diffY);
            if (Math.abs(diffX) > Math.abs(diffY)) {

                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                    if (diffX > 0) {
                        onSwipeRight();
                    } else {
                        onSwipeLeft();
                    }
                    result = true;
                }
            }

        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return result;
    }
}

//Prevents a single tap being detected as well as a double tap    
private Handler handler = new Handler();
private Runnable checkTap = new Runnable() {

    public void run() {
        try {
            if(!mTapConsumed && mTapsDetected >= 1) {
                if(mTapsDetected > 1)
                    onTapDouble();
                else
                    onTapSingle();
                mTapsDetected = 0;
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

};

//Prevents a tap from being detected for a short duration after a fling has occurred
private Runnable postFling = new Runnable() {

    public void run() {
        try {
            mEnableTap = true;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
};



public void onSwipeRight() {
}

public void onSwipeLeft() {
}

public void onPinchScaleChange(float scale) {
}

public void onPinchFinal(float scale) {
}

public void onTapSingle() {
}

public void onTapDouble() {
}

public void flingData(float diffX, float diffY)
{

}

}

To be clear. The above code works but it is difficult for the User to activate a fling left or right because the ScrollView wants to scroll up or down. I need a way to make swiping left and right easier for the user. Any ideas?


Solution

  • Turns out that sometimes the easiest answer is the best. I applied the onTouchListener to the scrollview rather than the textview and all is good in the world. :)