So I've been in this for a while now and have been unable to figure out how to implement 4-way swipe recognition in android. I've found different links pointing to different solutions but none of them fulfill my requirements.
What I wish to accomplish is to attain a "Badoo" like swipe gesture. Badoo is a Tinder like application. On swiping up/down the user's pictures are scrolled, and on left/right, the card is liked/disliked.
The animation I wish to achieve is that on left/right the card follows the set left/right path and moves to like/dislike. On up/down the pictures snap to their next/previous ones.
I've tried countless libraries over github that for swipeable cards and am now trying to implement a custom view for my requirements.
Currently I'm trying to insert a VerticalViewPager inside a CardView. The problem here is that the VerticalViewPager is intercepting all touch events therefore I am unable to swipe my card right/left.
This is my OnTouch event for the CardView item:
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
pager.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
pager.requestDisallowInterceptTouchEvent(false);
break;
}
return true;
}
This is what I'm trying to achieve. This is what Badoo looks like:
Badoo Screenshot 1: https://drive.google.com/open?id=0BzGcu10X5GRrTDZHRm4xT0tnQ1k
Badoo Screenshot 2: https://drive.google.com/open?id=0BzGcu10X5GRrc2pvRTZKS2p2UEU
If anyone knows of any similar libraries with a similar 4-way swipe gesture please let me know.
EDIT:
I've made a parent view for the card and the ViewPager, the CardView (cardViewParent) has a child cardView and cardView's child is a VerticalViewPager (mViewPager):
cardViewParent.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
Toast.makeText(context, "incardviewPARENT", Toast.LENGTH_SHORT).show ();
float rawX = 0, rawY = 0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//gesture has begun
float x;
float y;
//cancel any current animations
mActivePointerId[0] = event.getPointerId(0);
x = event.getX();
y = event.getY();
rawX = event.getRawX();
rawY = event.getRawY();
initialXPress[0] = x;
initialYPress[0] = y;
initialRawXPress[0] = rawX;
initialRawYPress[0] = rawY;
break;
case MotionEvent.ACTION_MOVE:
//gesture is in progress
final int pointerIndex = event.findPointerIndex(mActivePointerId[0]);
//Log.i("pointer index: " , Integer.toString(pointerIndex));
if (pointerIndex < 0 || pointerIndex > 0) {
break;
}
final float xMove = event.getX(pointerIndex);
final float yMove = event.getY(pointerIndex);
float rawDY = Math.abs(event.getRawY() -
initialRawYPress[0]);
float rawDX = Math.abs(event.getRawX() - initialRawXPress[0]);
//calculate distance moved
final float dx = xMove - initialXPress[0];
final float dy = yMove - initialYPress[0];
Log.d("RAW DY:", "" + rawDY);
if(rawDY > 100 && !animationStart[0])
{
// vertical swipe detected
mViewPager.setSwipeable(true);
cardStack.setSWIPE_ENABLED(false);
// mViewPager.dispatchTouchEvent(event);
return false;
}
if((rawDY <100 && rawDX > 100) ||
animationStart[0]) {
//horizontal swipe
animationStart[0] = true;
cardStack.setSWIPE_ENABLED(true);
mViewPager.setSwipeable(false);
// cardStack.dispatchTouchEvent(event);
return false;
}
}
return true;
}
});
If an Upwards Swipe Motion is detected then I'm enabling the ViewPager's swipe motion, else the CardStack's (swipe deck) swipe is enabled. The problem is that the swipe motion doesn't start in any direction.
Related
I used this link to create a view to swipe up and down views! but there is one problem! after I slide up the view and I want to slide down to close it, I have to slide the parent view!! and the view itself can't detect the sliding!
I've tried adding this code to my mainActivity :
sliderView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
float touchedArea = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mViewHeight = sliderView.getHeight();
x1 = event.getX();
y1 = event.getY();
mViewStartPositionY = sliderView.getTranslationY();
mCanSlide |= sliderView.getBottom() - sliderView.getHeight() <= touchedArea;
t1 = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
float difference = event.getRawY() - y1;
y2 = event.getRawY();
float moveTo = mViewStartPositionY + difference;
if(moveTo > 0 && mCanSlide) {
sliderView.setTranslationY(moveTo);
}
}
return true;
}
});
but when I slide down, it has some bugs and the sliderView freeze sometimes!
how can I fixed this issue?!
or if there are any tutorials or other useful libraries, please let me know!
also, I used this library, but It has some issues too!
I have a circle and triangle images inside a view pager.
I am trying to move the triangle image along the circle image using on-touch listener.
But when i move the triangle, the screen/fragment also moves to the next as i am using a view pager.
What do i do so that when the on-touch listener of the triangle or circle is called then view pager on touch listener is not called.
UPDATE: ADDING CODE
mCurrTempIndicator.setOnTouchListener(new MyOnTouchListener());
private class MyOnTouchListener implements OnTouchListener {
private double startAngle;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mThermostatCentreXOnScreen == 0 || mThermostatCentreYOnScreen == 0){
int centerXOnImage=mThermostatBgrd.getWidth()/2;
int centerYOnImage=mThermostatBgrd.getHeight()/2;
mThermostatCentreXOnScreen=mThermostatBgrd.getLeft()+centerXOnImage;
mThermostatCentreYOnScreen=mThermostatBgrd.getTop()+centerYOnImage;
mStartPosX = event.getRawX();
mStartPosY = event.getRawY();
float dx = event.getX() - mThermostatCentreXOnScreen;
float dy = event.getY() - mThermostatCentreYOnScreen;
float r = FloatMath.sqrt((dx * dx) + (dy * dy));
////
break;
case MotionEvent.ACTION_MOVE:
dx = event.getX() - mThermostatCentreXOnScreen;
dy = event.getY() - mThermostatCentreYOnScreen;
r = FloatMath.sqrt((dx * dx) + (dy * dy));
mEndPosX = event.getX();
mEndPosY = event.getY();
case MotionEvent.ACTION_UP:
allowRotating = true;
break;
}
return true;
}
Thanks and regards,
Sunny
If you return true in your onTouchListener for the triangle and circle it will consume the event and the parent won't receive it.
I've created an onTouchListener for dragging Views. Images drag smoothly if I use getRawX() and getRawY(). The problem with that is the image will jump to the second pointer when you place a second pointer down then lift the first pointer.
This onTouchListener attempts to fix that issue by keeping track of the pointerId. The problem with this onTouchListener is while dragging an ImageView, the ImageView jumps around pretty crazily. The getX() and getY() values jump around.
I feel like I'm doing this correctly. I don't want to have to write a custom view for this because I've already implemented a scaleGestureDetector and written a custom rotateGestureDetector that work. Everything works fine but I need to fix the issue I get when using getRawX() and getRawY().
Does anybody know what I'm doing wrong here?
Here's my onTouchListener:
final View.OnTouchListener onTouchListener = new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
relativeLayoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
final float x = event.getX();
final float y = event.getY();
// Where the user started the drag
lastX = x;
lastY = y;
activePointerId = event.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
{
// Where the user's finger is during the drag
final int pointerIndex = event.findPointerIndex(activePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Calculate change in x and change in y
final float dx = x - lastX;
final float dy = y - lastY;
// Update the margins to move the view
relativeLayoutParams.leftMargin += dx;
relativeLayoutParams.topMargin += dy;
v.setLayoutParams(relativeLayoutParams);
// Save where the user's finger was for the next ACTION_MOVE
lastX = x;
lastY = y;
v.invalidate();
break;
}
case MotionEvent.ACTION_UP:
{
activePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL:
{
activePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP:
{
// Extract the index of the pointer that left the touch sensor
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if(pointerId == activePointerId)
{
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastX = (int) event.getX(newPointerIndex);
lastY = (int) event.getY(newPointerIndex);
activePointerId = event.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
};
image1.setOnTouchListener(onTouchListener);
The issue was simple, but unexpected. getRawX/Y() returns absolute coordinates, while getX/Y() returns coordinates relative to the view. I would move the view, reset lastX/Y, and the image wouldn't be in the same spot anymore so when I get new values they'd be off. In this case I only needed where I originally pressed the image (not the case when using `getRawX/Y').
So, the solution was to simply remove the following:
// Save where the user's finger was for the next ACTION_MOVE
lastX = x;
lastY = y;
I hope this will help somebody in the future, because I've seen others with this problem, and they had similar code to me (resetting lastX/Y)
After a lot of research, I found this to be the issue, getRawX is absolute and getX is relative to view. hence use this to transform one to another
//RawX = getX + View.getX
event.getRawX == event.getX(event.findPointerIndex(ptrID1))+view.getX()
I have One tip & think it will help.
invalidate() : does only redrawing a view,doesn't change view size/position.
What you should use is
requestLayout(): does the measuring and layout process. & i think requestLayout() will be called when call setLayoutParams();
So Try by removing v.invalidate()
or try using view.layout(left,top,right,bottom) method instead of setting layoutParams.
We have a longpress detection on a button. When detected, the view should be dismissed (back to the game view) and the object chosen with the button shall be at the coordinates of the longpress. That's the easy part, the hard part is to then change the coordinates with the finger, without releasing it.
Okay so i'm trying to reformulate this: We have two views displayed on top of eachother. What we want is a longclick detected in the top view, to then dismiss the top view, and without releasing the finger, get touchevents in the bottom view.
This is our class gameView: (this code contains a lot of code unneccesay for the problem)
#Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
gestureHandler.setTranslation(translateX, translateY);
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2)
);
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
scaleToX = event.getX();
scaleToY = event.getY();
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so let's save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
int x = (int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX));
int y = (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY));
map.click(getContext(), x, y);
for(CastleTile castleTile : map.getCastleTiles()) {
if(castleTile.getRect().contains((int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX)), (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY)))){
Log.d("Castle to your service", "Boes");
castleSettings.show();
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactorX != 1f && scaleFactorY != 1f && dragged) || mode == ZOOM) {
invalidate();
}
return true;
}
#Override
public boolean onLongClick(View v) {
castleSettings.dismiss();
return true;
}
And this is the gestureHandler:
public void onLongPress(MotionEvent event) {
map.longClick(context, (int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX)), (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY)));
}
Sorry for eventual lack of information or bad explaination :)
Any suggestions for how to make an activity slider with "slide left" and "slide right" like a typical image slider??
I tried with: (implements OnTouchListener)
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
// code
break;
}
case MotionEvent.ACTION_UP:
{
// code
break;
}
case MotionEvent.ACTION_MOVE:
{
// code
break;
}
}
return true;
}
but don't have LEFT, RIGHT choice.
I don't need use buttons, just I need do somethings like the image slider for ipad2 but with activities for a customer app.
Thanks
You need to calculate by your own for slide left and right movement
MotionEvent.ACTION_UP
A pressed gesture has finished, the motion contains the final release location as well as any intermediate points since the last down or move event.
MotionEvent.ACTION_DOWN
A pressed gesture has started, the motion contains the initial starting location.
Use onTouchEvent(), and calculate difference in X and difference in Y by where the user presses down and lifts up. Use these values to figure out the direction of the move.
float x1, x2, y1, y2;
String direction;
switch(event.getAction()) {
case(MotionEvent.ACTION_DOWN):
x1 = event.getX();
y1 = event.getY();
break;
case(MotionEvent.ACTION_UP) {
x2 = event.getX();
y2 = event.getY();
float differenceInX = x2-x1;
float differenceInY = y2-y1;
// Use dx and dy to determine the direction
if(Math.abs(differenceInX) > Math.abs(differenceInY)) {
if(differenceInX > 0) direction = "right";
else direction = "left";
} else {
if(differenceInY > 0) direction = "down";
else direction = "up";
}
}
}