How to implement swipe and hold gesture in Android? - android

I am trying to implement a "swipe and hold" gesture in Android where the user swipes from a point on the left to another point on the right, then continues to hold at the right point until a server-side action is complete. The idea being that if the user lets go of the hold, the server-side action will be cancelled.
I've been trying to go about this the following way but have encountered an issue when getting to the hold part of the gesture. What I'd like to have happen is that when the user reaches point B on the right, the ACTION_MOVE is somehow converted into an ACTION DOWN for the view on the right and from then on, as long as the event ACTION_DOWN continues the gesture is valid, but if the user lets go or leaves the view area, it is invalid. So the question is, is it possible to chain the ACTION_MOVE from left to right into a "hold" gesture once the user reaches point B on the right? I've tried adding an onTouch to point B after the swipe, but the only way that works is if the user lets go of the swipe, then presses point B again. Will this be possible, or should I go about this a different way, such as drag and drop?
Point A and B as found below are currently image views, but that's more placeholder than anything.
private boolean mValidGesture = false;
Rect outRect = new Rect();
int[] location = new int[2];
private boolean isViewInBounds(View view, int x, int y){
view.getDrawingRect(outRect);
view.getLocationOnScreen(location);
outRect.offset(location[0], location[1]);
return outRect.contains(x, y);
}
...
mPointA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int)event.getRawX();
int y = (int)event.getRawY();
if(event.getAction() == MotionEvent.ACTION_MOVE){
if (isViewInBounds(mPointB, x, y)) {
mPointB.dispatchTouchEvent(event);
Log.d(TAG, "onTouch ViewB");
Toast.makeText(MainActivity.this, "Swipe complete", Toast.LENGTH_SHORT).show();
//This is the part that happens only after you let go of the swipe :(
mPointB.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mValidGesture = true;
Log.d(TAG, "Gesture: " + String.valueOf(mValidGesture));
return true;
case MotionEvent.ACTION_UP:
mValidGesture = false;
Log.d(TAG, "Gesture: " + String.valueOf(mValidGesture));
return true;
}
return false;
}
});
} else if(isViewInBounds(mPointA, x, y)){
Log.d(TAG, "onTouch ViewA");
}
}
// Further touch is not handled
return false;
}
});

You don't need to put touchListener on pointB. Try something like this:
boolean serverActionStarted = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int)event.getRawX();
int y = (int)event.getRawY();
if(event.getAction() == MotionEvent.ACTION_MOVE){
if (isViewInBounds(mPointB, x, y)) {
// TODO : swipe completed. Start server action
serverActionStarted=true;
}
}else if(event.getAction() == MotionEvent.ACTION_UP && serverActionStarted){
// TODO : cancel the server action
serverActionStarted = false;
}
So basically, when the user swipes from pointA to pointB, you start the server action and set a boolean serverActionStarted to true. After that, if ACTION_UP event occurs, with the severAction having started, you cancel the server action.

Related

How to programmatically cancel the OnTouchListener callback even if the user haven't released the touch from a view in Android?

I am working on an Audio Recording App. It works in a way that when the user presses and moves the record button, the button moves along with the finger. I have created a boundary and when the finger crosses that boundary I want the button to perform the hide() animation and get back to it orginal position.
The whole process works fine if the MotionEvent.ACTION_UP or MotionEvent.ACTION_CANCEL event is occurred, but the hide() operation is not occurring even if the touch crosses the boundary. The button plays a back and forth motion sometimes when it is outside the boundary. The touch event is still being called even if I set the visibility of the view to false.
I get the output in the logcat as well (Log.e("MSG","boundary crossed");).
This is the code:
int recordButtonStartX;
microPhoneListner=new View.OnTouchListener() {
#Override
public boolean onTouch(View v, final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
recordButtonStartX = (int) event.getX();
this.floatingRecordButton.display(event.getX());
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
this.floatingRecordButton.hide(event.getX());
break;
case MotionEvent.ACTION_MOVE:
int tempX = (int) event.getX();
if ((recordButtonStartX - tempX) > 200) {
Log.e("MSG","boundary crossed");
this.floatingRecordButton.hide(event.getX());
}
else
{
this.floatingRecordButton.moveTo(event.getX());
}
break;
}
recordMsgButton.setOnTouchListener(microPhoneListner);
To release the onTouchListener for any View set the listener to null.
recordMsgButton.setOnTouchListener(null);
or
After your condition satisfied you can set other listener to that View.
Make another listener
public final OnTouchListener mTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent rawEvent) {
return false;
}
};
When you want to disable the listener then set other listener to that view
v.setOnTouchListener(mTouchListener);

How to detect a TOUCH event with another finger while an ACTION_MOVE is in process

I am developing a game and I need to be able to detect that one finger is performing a MOVE while posibly another finger can TOUCH another part of the screen.
With the following code I am able to detect both the ACTION_MOVE (on certain region of the screen) and the ACTION_DOWN
public boolean onTouch(View v, MotionEvent event) {
final int dest_x = (int) event.getX();
final int dest_y = (int) event.getY();
onTrackPad = dbSettings.TRACK_PAD.contains(dest_x, dest_y);
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (onTrackPad)
{
//move character
}
break;
case MotionEvent.ACTION_DOWN:
// Fire bullets
break;
}
//The event was consumed
return true;
}
The problem is that I am not able to move and fire at the same time (I need to stop moving in order to fire and viceversa)
I am aware that Android can handle multi-touch events but have not figure it how to use that to be able to process these events and the same time so that the player can move and fire at the same time
I have also try using the getActionMasked without any luck
After reading this question Android MotionEvent.getActionIndex() and MultiTouch
This is how I solved the problem
public boolean onTouch(View v, MotionEvent event) {
int dest_x ;
int dest_y ;
p = event.getActionIndex() ;
dest_x = (int) event.getX(p);
dest_y = (int) event.getY(p);
onTrackPad = dbSettings.TRACK_PAD.contains(dest_x, dest_y);
action = event.getActionMasked() ;
switch (action) {
case MotionEvent.ACTION_MOVE:
if (onTrackPad)
{
//move character
}
break;
case MotionEvent.ACTION_DOWN:
// Fire bullets
break;
}
//The event was consumed
return true;
}
MotionEvent has all information about touches that you need. You can get number of touches by executing event.getPointersCount(), and try to check MotionEvent.ACTION_POINTER_2_DOWN instead of MotionEvent.ACTION_DOWN. To get coordinates of each touch you can use event.getX(0) and event.getX(1), same is for y. If you have a case of MotionEvent.ACTION_MOVE with 2 touches, you will receive all this information in your motion event.
Try below code.
when multiple pointers touches on the screen,system generates the action events.we can keep track of individual pointers with in motion event using pointer id. Pointer id persists across touch events and also allows to track individual pointer across entire gesture.
#Override
public boolean onTouch(View v, MotionEvent event) {
int index = event.getActionIndex();
int pointerID = event.getPointerId(index);
int action = event.getActionMasked();
if(event.getPointerCount() > 1)
{
Log.i("TouchType ", "Multi Touch");
for(int i = 0; i < event.getPointerCount(); i++)
{
performAction(action);
}
}else
{
Log.i("TouchType ", "Single Touch");
performAction(action);
}
return true;
}
public void performAction(int action){
switch(action)
{
case MotionEvent.ACTION_DOWN :
Log.i("OnTouch ", "Pressed");
// Fire bullets
break;
case MotionEvent.ACTION_MOVE :
Log.i("OnTiouch", "move");
//move character
break;
case MotionEvent.ACTION_UP :
Log.i("OnTiouch", "Up");
break;
default:
Log.i("OnTiouch", "None");
}
}

Android - Getting the position that was touched and moving a view to that position

I'm kinda trying to clone the ripple effect found in Google's material design. Now, I've noticed that the ripple always starts from the position that was touched. For this, I've created a drawable which will be initially invisible and but will become visible once the view is touched. The ImageView containing the drawable will be then moved to the position that was touched and the ripple animation will start thereafter. Now, while trying to do this, I'm stumbling upon one thing which is I can't figure out how to find out the position that was touched. I came across several questions on stackoverflow which said that the touch position can be found out by using the following code.
#Override
public boolean onTouch(View v, MotionEvent e) {
int X = (int)(e.getX());
int Y = (int)(e.getY());
return false;
}
I did try to implement this code in my app but I ended up with a NullPointerException. Please tell me how to find the exact location of the screen that was touched and move the view there?
Use like this:
#Override
public boolean onTouch(View v, MotionEvent e) {
if(e.getAction() == MotionEvent.ACTION_DOWN) { //ACTION_DOWN for the position touched.
int X = (int)(e.getX());
int Y = (int)(e.getY());
}
return false;
}
You should get position for ACTION_DOWN event and then move your view
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float x = event.getX();
float y = event.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}

onTouch always shows Reaction to second event

in my App I have a Custom View in an Activity.
To detect touch events I have the method onTouchEvent() in both of them.
Custom View: The onTouchEvent()-method basically just marks the object which is touched.
#Override
public boolean onTouchEvent(MotionEvent event) {
int maskedAction = event.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
touchedXY[0] = event.getX();
touchedXY[1] = event.getY();
mMatrix.mapPoints(touchedXY); // this is just for
event.setLocation(touchedXY[0], touchedXY[1]); // rotation purposes
for(int i = 0; i < mPoints[i]; i++){
mPoints[i].isSelected(setIsSelected(touchedXY)); //using method to
//check if point
//is selected
}
break;
default:
return false;
}
invalidate();
return true;
}
Activity: If an Object is touched a LinearLayout should appear at the Bottom of the Activity with the number of touched Points.
int key = 0;
#Override
public boolean onTouch(View v, MotionEvent event) {
// get masked (not specific to a pointer) action
int maskedAction = event.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
List<AddressPoint> selectedPoints = Arrays.asList(mPoints);
if (key == 0) {
if (selectedPoints.size() > 0) {
key = 1;
test.setText(String.valueOf(selectedPoints.size()));
popup.setVisibility(View.VISIBLE);
} else {
test.setText(String.valueOf(selectedPoints.size()));
}
} else {
if (selectedPoints.size() > 0) {
test.setText(String.valueOf(selectedPoints.size()));
} else {
key = 0;
popup.setVisibility(View.GONE);
}
}
break;
default:
return false;
}
return false;
}
So, it is working but behaves not as desired. The Activity seems only to react to the second touchevent and refreshes every second touchevent.
Like for example:
TouchEvent = 4 Points --> Activity shows no popup.
TouchEvent = 3 Points --> Activity shows popup with the number "4"
TouchEvent = 1 Point --> Activity shows popup with the number "3"
.
. and so on
.
I hope you can help me and I appreciate your help!
The problem seems to occour because your view is redrawn before the points are saved.
Therefore it might be better to save the number of points in the onTouch onTouchEvent and then invoke another function to set the text accordingly.

How to get a continuous Touch Event?

My class extends View and I need to get continuous touch events on it.
If I use:
public boolean onTouchEvent(MotionEvent me) {
if(me.getAction()==MotionEvent.ACTION_DOWN) {
myAction();
}
return true;
}
... the touch event is captured once.
What if I need to get continuous touches without moving the finger?
Please, tell me I don't need to use threads or timers. My app is already too much heavy.
Thanks.
Use if(me.getAction() == MotionEvent.ACTION_MOVE). It's impossible to keep a finger 100% completely still on the screen so Action_Move will get called every time the finger moves, even if it's only a pixel or two.
You could also listen for me.getAction() == MotionEvent.ACTION_UP - until that happens, the user must still have their finger on the screen.
You need to set this properties for the element
android:focusable="true"
android:clickable="true"
if not, just produce the down action.
Her is the simple code snippet which shows that how you can handle the continues touch event. When you touch the device and hold the touch and move your finder, the Touch Move action performed.
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if(isTsunami){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Write your code to perform an action on down
break;
case MotionEvent.ACTION_MOVE:
// Write your code to perform an action on contineus touch move
break;
case MotionEvent.ACTION_UP:
// Write your code to perform an action on touch up
break;
}
}
return true;
}
Try this. It works to me:
public static OnTouchListener loadContainerOnTouchListener() {
OnTouchListener listener = new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
LinearLayout layout = (LinearLayout)v;
for(int i =0; i< layout.getChildCount(); i++)
{
View view = layout.getChildAt(i);
Rect outRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
if(outRect.contains((int)event.getX(), (int)event.getY()))
{
Log.d(this.getClass().getName(), String.format("Over view.id[%d]", view.getId()));
}
}
}
Remember: the listener you´ll set must be a container layout (Grid, Relative, Linear).
LinearLayout layout = findViewById(R.id.yourlayoutid);
layout.setOnTouchListener(HelperClass.loadContainerOnTouchListener());
This might help,
requestDisallowInterceptTouchEvent(true);
on the parent view, like this -
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().requestDisallowInterceptTouchEvent(true);
switch(motionEvent.getAction()){
}
return false;
}
I was making a game with a custom view used as a thumb control. . . here is what I did
float x = 0, y = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
// handle touch events with
switch( event.getActionMasked() ) {
case MotionEvent.ACTION_DOWN :
if(cont)
{
// remove any previous callbacks
removeCallbacks(contin);
// post new runnable
postDelayed(contin, 10);
}
invalidate();
return true;
case MotionEvent.ACTION_MOVE :
if(!cont && thumbing != null)
{
// do non-continuous operations here
}
invalidate();
return true;
case MotionEvent.ACTION_UP :
// set runnable condition to false
x = 0;
// remove the callbacks to the thread
removeCallbacks(contin);
invalidate();
return true;
default :
return super.onTouchEvent(event);
}
}
public boolean cont = false;
// sets input to continuous
public void set_continuous(boolean b) { cont = b; }
public Runnable contin = new Runnable()
{
#Override
public void run() {
if(x != 0)
{
// do continuous operations here
postDelayed(this, 10);
}
}
};
A quick note however, make sure in your main activity that is calling this view removes the callbacks manually via the onPause method as follows
#Override
protected void onPause() {
if(left.cont) left.removeCallbacks(left.contin);
if(right.cont) right.removeCallbacks(left.contin);
super.onPause();
}
That way if you pause and come back touch events aren't being handled twice and the view is free from it's thread's overhead.
** tested on Samsung Galaxy S3 with hardware acceleration on **
All these answer are partially correct but they do not resolve in the right way the problem.
First of all, for everyone out there that decide to track when the event is ACTION_MOVE. Well that works only guess when? When user move his finger, so could if you decide to implement a custom thumb control is okay but for a normal custom button that's not the case.
Second, using a flag inside ACTION_DOWN and check it in ACTION_UP seems the logic way to do it, but as Clusterfux find out if you implement a while(!up_flag) logic you get stuck into troubles ;)
So the proper way to do it is mentioned here:
Continuous "Action_DOWN" in Android
Just keep in mind that if the logic you're going to write during the continuous press has to modify the UI in some way, you have to do it from the main thread in all the other cases it's better use another thread.
You can use the below code snippet as a reference in which I used the background to detect if the screen is held or not...
Main_Layout.setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ResourceAsColor")
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Main_Layout.setBackgroundColor(R.color.green);
event.setAction(MotionEvent.ACTION_DOWN);
break;
default:
Main_Layout.setBackgroundColor(R.color.blue);
break;
}
return false;
}
});

Categories

Resources