0

I am trying to build a component where cards can be swiped in all the direction and can be brought back. Which translates to following:

  • Left to right: bring back the previous card
  • Right to left: bring new card underneath
  • Top to bottom: bring back the previous card
  • Bottom to top: bring new card underneath

I decided that the best way to move forward will be if I extend Viewpager and combine both vertical and horizontal Viewpager implementation. It will solve all the animation related issues as well as fragment management work.

Eventually, I was able to create what I wanted but it is now stuck at last problem. When the Viewpager is swiped vertically it has the marked zone which behaves fluidly but the dark zone does not swipe correctly(image below). For vertical swipe implementation, I have used the touch coordinate swapping approach and mapped the coordinates. I have tried to look at the mapping for swapping and can't see where am I going wrong.

It will be a great help if someone can point me in the right direction:

Here are the code pieces:

AllDirectionViewPager.java

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class AllDirectionViewPager extends ViewPager {
    public static String TAG = "AllDirectionViewPager";
    private float startX, startY, mHeight, mWidth;
    int DIRECTION_LOCKING_THRESHOLD_X=5, DIRECTION_LOCKING_THRESHOLD_Y=5, mDirection=-1;
    boolean directionLocked=false;
    VDepthPageTransformer mVerticalTransformer;
    HDepthPageTransformer mHorizontalTransformer;

    public AllDirectionViewPager(Context context) {
        super(context);
        init();
    }

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

    private void init() {
        setOverScrollMode(OVER_SCROLL_NEVER);
        mVerticalTransformer = new VDepthPageTransformer();
        mHorizontalTransformer = new HDepthPageTransformer();
    }

    /**
     * Swaps the X and Y coordinates of touch event.
     */
    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        Log.i(TAG, "swap xy called. init: "+ev.getX()+","+ev.getY()+" final: "+newX+","+newY+" with height: "+height);
        ev.setLocation(newX, newY);

        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev){
        boolean intercepted = false;
        int direction;
        direction = getDirection(ev);
        Log.i(TAG, "viewpager intercepted: "+mDirection);
        if(mDirection==0) {
            intercepted = super.onInterceptTouchEvent(swapXY(ev));
            swapXY(ev); // return touch coordinates to original reference frame for any child views
        } else if (mDirection==1) {
            intercepted = super.onInterceptTouchEvent(ev);
        }

        return intercepted;
    }

    //1: Left-Right; 0: UP-DOWN; -1: continuing
    int getDirection(MotionEvent ev) {
        float dirX=0, dirY=0;
        boolean continuing = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                mDirection = -1;
                directionLocked = false;
                Log.d(TAG, "swipe start");
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = ev.getX();
                float endY = ev.getY();
                Log.d(TAG, "direction locked: "+directionLocked+" diffY: "+(endY - startY)+ " diffX: "+(endX - startX));
                if (!directionLocked && ((Math.abs(endX - startX)>DIRECTION_LOCKING_THRESHOLD_X)||(Math.abs(endY - startY) > DIRECTION_LOCKING_THRESHOLD_Y))) {
                    dirX = (endX - startX) / getHeight();
                    dirY = (endY - startY) / getWidth();
                    directionLocked = true;
                    if (Math.abs(dirX) < Math.abs(dirY)) {
                        mDirection = 0;
                        this.setPageTransformer(true, mVerticalTransformer);
                        Log.d(TAG, "Vertical set");
                    }
                    else if (Math.abs(dirX) > Math.abs(dirY)) {
                        mDirection = 1;
                        this.setPageTransformer(true, mHorizontalTransformer);
                        Log.d(TAG, "Horizontal set");
                    }
                    else {
                        mDirection = -1;
                        directionLocked = false;
                        Log.d(TAG, "nothing found");
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "swipe done");
                directionLocked = false;
                break;
            default:
                continuing = true;
        }
        if(continuing)
            return -1;

        return -1;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int direction = getDirection(ev);
        Log.i(TAG, "viewpager touch: "+mDirection);
        Log.i(TAG, "Touch coords: "+ev.getX()+","+ev.getY());
        if(mDirection==0) {
            return super.onTouchEvent(swapXY(ev));
        }else/* if(mDirection==1) */{
            return super.onTouchEvent(ev);
        }
//        return false;
    }
}

HDepthPageTransformer.java

import android.support.v4.view.ViewPager;
import android.view.View;

public class HDepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.85f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

VDepthPageTransformer.java

import android.support.v4.view.ViewPager;
import android.view.View;

public class VDepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.85f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the top page
            view.setAlpha(1);
            // Counteract the default slide transition
            view.setTranslationX(view.getWidth() * -position);

            //set Y position to swipe in from top
            float yPosition = position * view.getHeight();
            view.setTranslationY(yPosition);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

Image not to scale to show problem area (Sorry for bad artwork): Vertical swipe issue area. Image not to scale.

The depth page transformers are sourced from the android developers website. Used some part from here for vertical scrolling: Android: Vertical ViewPager

Community
  • 1
  • 1
user1802486
  • 64
  • 1
  • 7

1 Answers1

0

As no one was answering this, I dived into the android source code of viewpager and realized that although the observation is bizarre, the reason is incorrect initialization.

Because onInterceptTouchEvent is fired before any drag actually occurs(ie before onTouchEvent). It was important to initialize the process correctly before lending control to onTouchEvent. That is all one needs to do in the above code. Hopefully, soon I'll post a library for the same over here :) but if someone needs it before that, just leave a comment.

user1802486
  • 64
  • 1
  • 7