MotionEvent.ACTION_UP not called - android

Consider the following scheme below (for sake of better understanding of my problem).
As you can see, I am considering a list view surrounded by padding. Now, If a user presses a listview item, as the action I have provided it light blue background color. Now, My application is dealing with onTouch Events itself to determine actions like
Click
Left to Right Swipe
Right to Left Swipe
Here is my code.
public boolean onTouch(View v, MotionEvent event) {
if(v == null)
{
mSwipeDetected = Action.None;
return false;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getRawX();
downY = event.getRawY();
mSwipeDetected = Action.Start;
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = listView.getChildCount();
int[] listViewCoords = new int[2];
listView.getLocationOnScreen(listViewCoords);
int x = (int) event.getRawX() - listViewCoords[0];
int y = (int) event.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = listView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
return false; // allow other events like Click to be processed
}
case MotionEvent.ACTION_MOVE: {
upX = event.getRawX();
upY = event.getRawY();
float deltaX=0,deltaY=0;
deltaX = downX - upX;
deltaY = downY - upY;
if(deltaY < VERTICAL_MIN_DISTANCE)
{
setTranslationX(mDownView, -(deltaX));
setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / listView.getWidth())));
return false;
}
else
{
forceBringBack(v);
}
return false;
}
case MotionEvent.ACTION_UP:
{
stopX = event.getX();
float stopValueY = event.getRawY() - downY;
float stopValue = stopX - downX;
if(!mDownView.isPressed())
{
forceBringBack(mDownView);
return false;
}
boolean dismiss = false;
boolean dismissRight = false;
if(Math.abs(stopValue)<10)
{
mSwipeDetected = Action.Start;
}
else
{
mSwipeDetected = Action.None;
}
String log = "";
Log.d(log, "Here is Y" + Math.abs(stopValueY));
Log.d(log, "First Comparison of Stop Value > with/4" + (Math.abs(stopValue) > (listView.getWidth() /4)));
Log.d(log, "Second Comparison " + (Math.abs(stopValueY)<VERTICAL_MIN_DISTANCE));
Log.d(log, "Action Detected is " + mSwipeDetected + " with Stop Value " + stopValue);
if((Math.abs(stopValue) > (listView.getWidth() /4))&&(Math.abs(stopValueY)<VERTICAL_MIN_DISTANCE))
{
dismiss = true;
dismissRight = stopValue > 0;
if(stopValue>0)
{
mSwipeDetected = Action.LR;
}
else
mSwipeDetected = Action.RL;
}
Log.d(log, "Action Detected is " + mSwipeDetected + " with Stop Value after dissmiss" + stopValue);
if(dismiss)
{
if(dismissRight)
mSwipeDetected = Action.LR;
else
mSwipeDetected = Action.RL;
animate(mDownView)
.translationX(dismissRight ? listView.getWidth() : - listView.getWidth())
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation)
{
}
});
}
else
{
animate(mDownView)
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
break;
}
}
return false;
}
As you can see, I determine the performed action in MotionEvent.ACTION_UP and set the value of Enum Action accordingly. This logic works like a charm if the user does not crosses the list view boundary.
Now, if the user, while sliding (or specifically), moving his finger along the list item moves from blue to orange, the MotionEvent.ACTION_UP would not be given to listview, which causes my code not to make a decision and due to translationX() method and setAlpha(), since no Action is ever determined in this case, that particular list item gets blank.
The problem does not stops here, since, I am not inflating view each time, same translatedX() row gets inflated each time leading to multiple occurance of a blank/white list item.
Is there anything possible to do so that even if I didn't encounter MotionEvent.ACTION_UP, I could still make some decison ?

You should return true; in case MotionEvent.ACTION_DOWN:, so the MotionEvent.ACTION_UP will be handled.
As explained on View.OnTouchListener:
Returns:
True if the listener has consumed the event, false otherwise.
MotionEvent.ACTION_UP Won't get called until the MotionEvent.ACTION_DOWN occurred, a logical explanation for this is that it's impossible for an ACTION_UP to occur if an ACTION_DOWN never occurred before it.
This logic enables the developer to block further events after ACTION_DOWN.

Also note that under certain circumstances (eg. screen rotations) the gesture may be cancelled, in which case a MotionEvent.ACTION_UP will NOT be sent. Instead a MotionEvent.ACTION_CANCEL is sent instead. Therefore a normal action switch statement should look something like this:
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// check if we want to handle touch events, return true
// else don't handle further touch events, return false
break;
// ... handle other cases
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// finish handling touch events
// note that these methods won't be called if 'false' was returned
// from any previous events related to the gesture
break;
}

I don't think adding return true;to case MotionEvent.ACTION_DOWN: would eventually solve the problem. It just complicated the situation where return false could have done the job like a charm.
What to notice is: MotionEvent.ACTION_DOWN: /*something*/ return true; will block any other Listener callbacks available for the view, even onClickListenerm, while correctly return false in MotionEvent.ACTION_UP: can help the MotionEvent be propagated to the right destination.
Reference to his original code sourse: https://github.com/romannurik/android-swipetodismiss

As Danpe explained in his concise answer - I had to add the ACTION_DOWN code in order to have ACTION_UP recognized.
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
XyPos xyPos = new XyPos();
xyPos.x = last_x;
xyPos.y = last_y;
handleViewElementPositionUpdate(xyPos);
break;
I had the entire onTouch(..) method returning true anyway, so I'm not sure why that wasn't enough ... but nice to have this quick solution .. (thanks!)

Related

Badoo like Swipeable Cards

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.

How to create a movable button which doesn't move out of the screen

I'm using this code to move a button in the screen but when I reach the sides it is moving out of the screens.
private float mPrezX, mPrevY;
private static final int MAX_CLICK_DURATION = 200;
private long startClickTime;
#Override
public boolean onTouch(View view, MotionEvent event) {
int action = event.getActionMasked();
Button gvup = (Button)findViewById(R.id.giveup);
gvup.setBackground(getResources().getDrawable(R.drawable.btn));
switch (action ) {
case MotionEvent.ACTION_DOWN: {
mPrevX = view.getX() - event.getRawX();
mPrevY = view.getY() - event.getRawY();
startClickTime = Calendar.getInstance().getTimeInMillis();//!!
gvup.setBackground(getResources().getDrawable(R.drawable.btn1));
break;
}
case MotionEvent.ACTION_MOVE:
{
view.animate()
.x(event.getRawX() + mPrevX)
.y(event.getRawY() + mPrevY)
.setDuration(0)
.start();
gvup.setBackground(getResources().getDrawable(R.drawable.btn1));
break;
}
case MotionEvent.ACTION_CANCEL:
gvup.setBackground(getResources().getDrawable(R.drawable.btn1));
break;
case MotionEvent.ACTION_UP:
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
if(clickDuration < MAX_CLICK_DURATION) {
//click event has occurred
gvup.setBackground(getResources().getDrawable(R.drawable.btn));
giveUp();
}
break;
}
return true;
}
I'm also changing its background color if pressed and also made it clickable.
I need to keep that button inside the layout.
I have also tried this and this links but both of these approaches leading to squeeze the button when moved to the sides.
Please help.
Thanks in advance.
Well you need to get its X location and add its width to it to see where the "right" edge is and compare if it greater or equal to your viewport's width. I haven't written JAVA in a while but that should do the trick.
Are you checking its location?
Perhaps
int rightEdge = currentPos.X + itsWidth;
if(rightEdge >= screenWidth) // something here
Maybe in your animate() method do your checking. Its hard for me to guess withough looking at what is making this happen. What is firing the event? What is event.getRawX() equal to? Is that a touch point on the screen? Are you animating after the point is selected? Or you can define a min/max x and min/max y and not let your circle past that.
view.animate()
if( .x(event.getRawX() + mPrevX) > screenWidth ){
.x(screenwidth - circleWidth)
} // do the same for the Y - must check the max and min for both Y and X
.x(event.getRawX() + mPrevX)
.y(event.getRawY() + mPrevY)
.setDuration(0)
.start();

android detect no movement

I'm using motion event, action_down, action_move etc, to detect when there is no finger movement but the finger is still on the screen. For instance, the user moves in a vertical line from top of the screen to the bottom and then stops movement without lifting the finger. How do I detect when there is no movement but the finger is still on the screen after the drag/swipe?
EDIT: What I'm trying to do is count every time I change direction of movement in a vertical direction. And to do that I'm trying to detect when I stop movement against the change of movement. For instance, I move down the screen and then move back up, that counts as two counts. Here is my code, please don't provide me with code as a direct answer but hints or clues so that I can try and figure it out myself (my code might look a bit confusing, I'm just trying out different things):
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
oldX = event.getX();
oldY = event.getY();
oldSpeedY = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
posY = event.getY();
posSpeedY = System.currentTimeMillis();
float timeElapsed = (posSpeedY - oldSpeedY) / 1000;
float diffSpeed = posSpeedY - oldSpeedY;
if (changeOfMovement(posY, oldY)) {
//if (diffSpeed == 0)
count += 1;
}
break;
case MotionEvent.ACTION_UP:
// count seconds here
break;
}
Toast.makeText(getApplicationContext(), "Swipe: " + count,
Toast.LENGTH_SHORT).show();
return false;
}
public boolean changeOfMovement(float posY, float oldY) {
int newY = Math.round(posY);
double distance = Math.abs(newY - oldY);
oldY = newY;
//float speed = (float) (distance / time);
//if (distance < 25)
//return false;
//if (speed == 0)
//return true;
if (distance < 25)
return true;
return false;
}
The finger touches the screen until you receive either of the MotionEvent actions ACTION_UP or ACTION_CANCEL
The ACTION_DOWN Event is still valid until the finger is lifted from the screen or you can wait till a ACTION_UP Event is detected
I'm not sure if my situation is like yours, but mine I was want to detect if the user stopped moving inside circle within one second and starts moving again, by comparing between last two currentTimeMillis.
So, what I've done is I initialized fixed ArrayList to save the last two times in move event:
public class FixedArrayList extends ArrayList {
int fixedSize = 10;
public FixedArrayList(){}
public FixedArrayList(int fixedSize){
this.fixedSize = fixedSize;
}
#SuppressWarnings("All")
public void addItem(Object object){
if(size() < fixedSize){
add(object);
} else{
remove(0);
add(object);
}
}
}
Now, I've initialized my new class with fixed 2 items to be saved:
FixedArrayList savedLastMove = new FixedArrayList(2);
int secondsToWait = 1;
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case (MotionEvent.ACTION_MOVE):
currentSystemTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
savedLastMove.addItem(currentSystemTime);
if(savedLastMove.size() >= 2 && ((long)savedLastMove.get(1) - (long)savedLastMove.get(0)) >= secondsToWait){
//Do what you want after secondsToWait
}
return true;
}
I hope that will help! Because it solved my problem.

ListView onTouchListener is omitting fling

I've wrote this onTouchListener to hide/show actionBar on listview scroll, similar to current functionality in Instagram app. Everything works fine except listView will not fling. I know I've blocked that action somehow, but I'm still a novice and cant figure it out. Can anyone help me understand what's going on here?
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int newTextPosition = (int) (text.getY() * -1);
int move;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = event.getY();
maxPath = text.getHeight();
break;
case MotionEvent.ACTION_UP:
v.performClick();
break;
case MotionEvent.ACTION_MOVE:
newY = event.getY();
move = (int) (newY - startY);
// Going UP
if(newY < startY){
if((text.getY() * -1) == text.getHeight()){
startY = event.getY();
return false;
}
int maxPath = (int) (text.getHeight() + text.getY());
if((move * -1) > maxPath){
move = maxPath * -1;
}
text.setY(text.getY() + move);
list.setY(list.getY() + move);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) list.getLayoutParams();
params.height = list.getHeight() + (move * -1);
list.setLayoutParams(params);
break;
}
// Going DOWN
if(newY > startY){
if((text.getY() * -1) == 0){
startY = event.getY();
return false;
}
if(text.getY() >= 0){
move = 0;
}
if(move > newTextPosition)
move = newTextPosition;
text.setY(text.getY() + move);
list.setY(list.getY() + move);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) list.getLayoutParams();
params.height = list.getHeight() + (move * -1);
list.setLayoutParams(params);
break;
}
}
return true;
}
});
EDIT: From some reason returning false at the end did the job. Any explanation is welcome
Captain here with an explanation!
When you return true from overriding onTouchEvent() on Views or from the OnTouchListeners onTouch() method, you tell android that this touch event has been 'consumed'. This way the touch event won't be passed back to the underlying views and will stop there. This is good when you need to override what happens on a touch event, but if you need the default action to happen you must return false, this way you tell android that the event wasn't 'consumed' and it must notify every other listener that a touch event has happened.
So by returning true, you had overridden the default behavior, which was the swipe and so it wasn't called on any touch event, because you 'consumed' all touch events.

Detect touch on bitmap

Greets,
Does anyone how I go about detecting when a user presses on a bitmap which is inside a canvas?
Thanks
You should work with something like so:
public boolean onTouchEvent(MotionEvent event){
int action = event.getAction();
int x = event.getX() // or getRawX();
int y = event.getY();
switch(action){
case MotionEvent.ACTION_DOWN:
if (x >= xOfYourBitmap && x < (xOfYourBitmap + yourBitmap.getWidth())
&& y >= yOfYourBitmap && y < (yOfYourBitmap + yourBitmap.getHeight())) {
//tada, if this is true, you've started your click inside your bitmap
}
break;
}
}
That's a start, but you need to handle case MotionEvent.ACTION_MOVE and case MotionEvent.ACTION_UP to make sure you properly deal with the user input. The onTouchEvent method gets called every time the user: puts a finger down, moves a finger already on the screen or lifts a finger. Each time the event carries the X and Y coordinates of where the finger is. For example if you want to check for someone tapping inside your bitmap, you should do something like set a boolean that the touch started inside the bitmap on ACTION_DOWN, and then check on ACTION_UP that you're still inside the bitmap.
Steve,
Google has a great article and tutorial for handling UI Events # http://developer.android.com/guide/topics/ui/ui-events.html. This is what got me started and it was great for me :-)
This will detect a touch and check if it is not transparent. You need this if your bitmaps are not rectangles. myBitmap is just a simple container class I use.
private boolean clickOnBitmap(MyBitmap myBitmap, MotionEvent event) {
float xEnd = myBitmap.originX() + myBitmap.width();
float yEnd = myBitmap.originY() + myBitmap.height();;
if ((event.getX() >= myBitmap.originX() && event.getX() <= xEnd)
&& (event.getY() >= myBitmap.originY() && event.getY() <= yEnd) ) {
int pixX = (int) (event.getX() - myBitmap.originX());
int pixY = (int) (event.getY() - myBitmap.originY());
if (!(myBitmap.getBitmap().getPixel(pixX, pixY) == 0)) {
return true;
} else {
return false;
}
}
return false;
}
This is the on touch code for completeness
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (clickOnBitmap(dog, event)) {
Toast.makeText(getContext(), "dog", Toast.LENGTH_SHORT).show();
} else if (clickOnBitmap(mouse,event)) {
Toast.makeText(getContext(), "mouse", Toast.LENGTH_SHORT).show();
}
return true;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
return true;
}
return false;
}

Categories

Resources