Search code examples
androidandroid-activityswipeontouchlistener

End up activity on swipe right?


I have to finish Activity when user offer a right swipe anywhere in the screen. I have tried with GestureDetector and that is works fine if there is neither ScrollView nor RescyclerView exists in the Activity and in addition views that have onClickListener also doesn't allow to detect swipe over them. So I had tried a different way by overlaying a view into the layout at the top of all them programmatically then tried to detect the swipe event over it.

private void swipeOverToExit(ViewGroup rootView) {

        OverlayLayout child = new OverlayLayout(this);

        ViewGroup.LayoutParams layoutParams =
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

        child.setLayoutParams(layoutParams);

        rootView.addView(child);

}

OverlayLayout

public class OverlayLayout extends RelativeLayout {

    private float x1, x2;
    private final int MIN_DISTANCE = 150;

    public OverlayLayout(Context context) {
        super(context);
    }

    public OverlayLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public OverlayLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public OverlayLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onTouchEvent will be called and we do the actual
         * logic there.
         */

        final int action = MotionEventCompat.getActionMasked(event);

        Logger.logD("Intercept===", action + "");


        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_DOWN) {
            return true; // Intercept touch event, let the parent handle swipe
        }

        Logger.logD("===", "Out side" + action + "");


        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.
        return false;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                break;
            case MotionEvent.ACTION_UP:

                x2 = event.getX();
                float deltaX = x2 - x1;

                if (Math.abs(deltaX) > MIN_DISTANCE) {

                    Logger.logD("Swipe Right===", MIN_DISTANCE + "");
                    return true;

                } else {

                    Logger.logD("Tap===", "Tap===");
                    return super.onTouchEvent(event);
                }
        }

        return true;

    }
}

The logic is to intercept touch event to other view if swipe action performs over the OverlayLayout then further end up the Activity. However, now I can detect the swipe event on OverlayLayout but other views couldn't respond even though I had return return super.onTouchEvent(event); in else condition of onTouchEvent as u can figure out there in my code. Any one please help me to make it . I'm pinned here and super excited to learn the trick :)


Solution

  • What you are trying to do is basically default behavior in Android Wear and its is consider as standard practices in Android Watches to exit an app. In Android wear DismissOverlayView does all heavy lifting for you.

    Smartphones have back button while Wear rely on long press or swipe dismiss pattern for to exit screen. You should dismiss Activity on back press, mixing wear pattern in Android Smartphones will make user confused. At Least show a warning dialog to avoid accidental exit.

    Solution

    As I see this question is tagged with Android Activity I would suggest you to make an Base Activity which will take care of swipe gesture and finish() itself on left to right swipe.

    The base activity class should look like this :-

       public abstract class SwipeDismissBaseActivity extends AppCompatActivity {
        private static final int SWIPE_MIN_DISTANCE = 120;
        private static final int SWIPE_MAX_OFF_PATH = 250;
        private static final int SWIPE_THRESHOLD_VELOCITY = 200;
        private GestureDetector gestureDetector;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            gestureDetector = new GestureDetector(new SwipeDetector());
        }
    
        private class SwipeDetector extends GestureDetector.SimpleOnGestureListener {
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    
                // Check movement along the Y-axis. If it exceeds SWIPE_MAX_OFF_PATH,
                // then dismiss the swipe.
                if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
                    return false;
    
                // Swipe from left to right.
                // The swipe needs to exceed a certain distance (SWIPE_MIN_DISTANCE)
                // and a certain velocity (SWIPE_THRESHOLD_VELOCITY).
                if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    finish();
                    return true;
                }
    
                return false;
            }
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            // TouchEvent dispatcher.
            if (gestureDetector != null) {
                if (gestureDetector.onTouchEvent(ev))
                    // If the gestureDetector handles the event, a swipe has been
                    // executed and no more needs to be done.
                    return true;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return gestureDetector.onTouchEvent(event);
        }
    }
    

    Now you can make other activities extend this base Activity and they will Inheritance will automatically make them adopt swipe to dismiss behavior .

    public class SomeActivity extends SwipeDismissBaseActivity {
    

    Advantages of this way

    • Purely OOPS approach
    • Clean code - no need to write swipe listener in each type of layout used in project (Relative,Linear etc)
    • Work perfectly in ScrollView