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):
The depth page transformers are sourced from the android developers website. Used some part from here for vertical scrolling: Android: Vertical ViewPager