I'm making video stories like instagram. So I met a problem with gestures.
The problem is that ACTION_CANCEL handled when I'm doing these moves and ACTION_UP doesn't call if I raise my finger
I'm in 1st page of ViewPager and I swipe left->right fastly (my finger still on screen)
I'm in the middle of ViewPager and I swipe left->right or right->left, but not finishing the swipe and I'm still in the current page and my finger on screen
I'm moving chaosly on screen (my finger still on screen)
So if I raise my finger after ACTION_CANCEL called, my video stay in "PAUSE" state
Finally, question is: How I can handle Action_Up event after Action_Cancel?
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
if (gestureDetector?.onTouchEvent(event) == true) return true
when (event?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
viewModel.videoPause()
}
MotionEvent.ACTION_UP -> {
viewModel.videoResume()
}
MotionEvent.ACTION_CANCEL -> {
// Handles when doing these moves and ACTION_UP doesn't call if I raise my finger
// 1. I'm in 1st page of ViewPager and I swipe left->right fastly (my finger still on screen)
// 2. I'm in the middle of ViewPager and I swipe left->right or right->left, but not finishing the swipe
// and I'm still in the current page and my finger on screen
// 3. I'm moving chaosly on screen (my finger still on screen)
// So if I raise my finger after ACTION_CANCEL called, my video stay in "PAUSE" state
}
else -> { }
}
return true
}
You don't get an ACTION_UP after an ACTION_CANCEL:
ACTION_CANCEL
public static final int ACTION_CANCEL
Constant for getActionMasked(): The current gesture has been aborted. You will not receive any more points in it. You should treat this as an up event, but not perform any action that you normally would.
Actually there is a way to do this.
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
performClick();
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
Log.d("coordinates: ","x : "+String.valueOf(x)+"y : " + String.valueOf(y));
break;
case MotionEvent.ACTION_UP:
Log.d("action","up")
}
return true;
}
Add this to your parent View. This will show you the location of your finger whenever it moves. And (I don't know how) but when you hold your finger, it also triggers the action up. So whenever the action up is triggered, you'll know that the user held his finger, or whenever the x or y coordinates are outside of your parent view, you can treat it as if the user held his finger. (I got this idea from a canvas question [https://stackoverflow.com/questions/21846584/android-canvas-draw-by-finger])
Related
I need scaling feature in my painting app, so I use ScaleGestureDetector, but when I perform two fingers touch action to zoom - before it triggers single touch action and draws something on Canvas, how can I check, that canvas is zooming now and ignore single touch action?
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleDetector.onTouchEvent(event)
event.transform(touchMatrix)
val x = event.x
val y = event.y
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
presenter.onPlotClick(x, y)
}
MotionEvent.ACTION_POINTER_DOWN -> {
}
MotionEvent.ACTION_MOVE -> {
presenter.onFingerMoveOnPlot(x, y)
}
MotionEvent.ACTION_POINTER_UP -> {
}
MotionEvent.ACTION_UP -> {
presenter.onFingerUpFromPlot()
}
}
invalidate()
return true
}
To my mind, the problem is that both fingers aren't likely to touch the screen at the same time when performing a two-finger gesture. But you still want single-finger actions to be displayed right away, so you can't wait to render the single-finger action or else the user will perceive lag. So, what I might try is to render the single-finger action immediately, and then if a second finger is detected within a certain timeframe AND the first finger hasn't moved beyond a certain distance, you undo the single-finger action and instead interpret it as a two-finger action.
I have a layout(A table layout). Whenever the user performs a "down" gesture, a specific function needs to be called. However,the ontouchlistener captures the event immediately instead of waiting until the user performs a proper "pull down" gesture.
My code is :
homePageTableLayout.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_DOWN)) {
startSyncData();
}
return true;
}
});
Currently, what's happening is that the startSyncData() function gets called immediately.
The end result should be somewhat similar to the commonly used "pulltorefresh" library for android but in this usecase, I don't have to update the view. I just have to post some data to the server.
I can't use the "pulltorefresh" library because it does not support "pulling" a tablelayout. I tried putting the tablelayout inside a scrollview but that only spoiled the UI.
You can try doing it in ACTION_MOVE or ACTION_UP - if you need it to be done exclusively on swipe down gesture, this should help, introduce four global floats: x, y, x1, y1, then in ACTION_DOWN:
x = event.getX();
y = event.getY();
ACTION_MOVE or ACTION_UP:
x1 = event.getX();
y1 = event.getY();
and then in ACTION_UP:
if (y1 > y) { //pointer moved down (y = 0 is at the top of the screen)
startSyncData();
}
Its not that easy. It works properly since you are catching MotionEvent.ACTION_DOWN. It doesn't mean a gesture like swipe down it means that user touched the screen (finger down, the touch or gesture just started). Now you need to catch MotionEvent.ACTION_UP (finger up, gesture or touch ends here) and decide if there was a gesture you need. http://developer.android.com/training/gestures/detector.html
http://developer.android.com/training/gestures/index.html
MotionEvent.ACTION_MOVE repeatedly gets called. So if you want startSyncData() to run after moving a certain distance, calculate how much you want the user to move before running the method.
The key is that ACTION_DOWN is not a downwards movement on the screen, but it means the user pressed down onto the screen.
homePageTableLayout.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_DOWN)) {
startFlg = 1;
} else if ((event.getAction() == MotionEvent.ACTION_MOVE)) {
if (startFlg==1){
// Calculate distance moved by using event.getRawX() and event.getRawX() and THEN run your method
startSyncData();
}
}
return true;
}
});
How do i detect swipe up and down on a listView. I have tried the following methods
Using onSimpleGestureListener,onFling() works for fling( i.e when i leave the screen after swipe). But it doesnt get called on swipe( finger not lifted from screen finally).
2.In onScroll() of onSimpleGestureListener, distanceY is not helpful for detecting up and down swipe. It works fine for fling detection, but fluctuates its values from negative to positive in a particular swipe.
Using onTouchListener
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
before = event.getY();
break;
case MotionEvent.ACTION_MOVE:
now = event.getY();
if (now < before) {
upSwipe()
} else if (now > before) {
downSwipe();
}
}
return false;
}
When i swipe up, variable now sometimes becomes greater than variable previous and sometimes small. So both upSwipe() and downSwipe() is called.
I am banging my head for hours. Not able to sole this.
Have you tried to return true instead of returning false.
Based on the Detecting Common Gestures documentation.
"Beware of creating a listener that returns false for the ACTION_DOWN event. If you do this, the listener will not be called for the subsequent ACTION_MOVE and ACTION_UP string of events. This is because ACTION_DOWN is the starting point for all touch events."
The problem that i am having is I am using a touch listener for the onTouch event it seems that the touch is being called more than once when i just touch it once.
The following is my code
final TextView tv = (TextView)findViewById(R.id.TextView01);
tv.setOnTouchListener(new View.OnTouchListener() {
int count1 =0;
int count2=0;
public boolean onTouch(View arg0, MotionEvent event) {
Log.d("Code777","Touch is being called finger");
int i = event.getPointerCount();
if(i==1 )
{
if(count1==0)
{
Log.d("Code777","Touched with 1 finger");
count1++;
count2=0;
}
}
if (i==2 )
{
if(count2==0)
{
Log.d("Code777","Touched with 2 fingers");
edit.append(tv.getText());
count2++;
count1=0;
}
}
return true;
}
});
Am i doing something wrong ??
It prints the log more than 3-4 times for both single touch and double touch
The problem updated problem is that both the events are getting fired now
if(event.getAction() == MotionEvent.ACTION_POINTER_2_DOWN)
{
Log.d("Code777","2 finger touch");
return true;
}else if(event.getAction() == MotionEvent.ACTION_DOWN)
{
Log.d("Code777","Touched with 1 finger");
return true;
}
You're code will execute during every touch event. The first time it activates, it's likely do to an ACITON_DOWN event (when the user first touches the screen). The second time, it is likely do to an ACTION_UP event (when the user lifts the finger from the screen). Likewise, if you were to swipe your finger around the screen, the same code will execute many times for an ACTION_MOVE event. You have to check for the types of touches that it is. Do something like this:
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
// Do something that should only happen when the user first touches the screen
break;
case MotionEvent.ACTION_MOVE:
// Do something that should only happen when the user is touching the screen
break;
case MotionEvent.ACTION_UP:
// Do something that should only happen when the user stops touching the screen
break;
}
EDIT:
Multitouch in Android is odd at best. Not all devices can handle it. Not all device can handle more than x number of touches, etc. If you want to handle DOWN cases for individual fingers, then you can use the ACTION_POINTER_X items. If you were to have this series of events like so:
1. User touches screen
2. User touches screen with second finger
3. User lifts finger 1
4. User touches screen with finger 1
5. User lifts finger 2
6. User touches screen with finger 2
7. User lifts both finger 1 and finger 2
8. User touches screen with only finger 2
9. User touches screen with finger 1
The events that will be fired will be like this:
1. ACTION_DOWN
2. ACTION_POINTER_2_DOWN
3. ACTION_POINTER_1_UP
4. ACTION_POINTER_1_DOWN
5. ACTION_POINTER_2_UP
6. ACTION_POINTER_2_DOWN
7. ACTION_UP (also, one of the pointers actions will fire depending on which finger was lifted first)
8. ACTION_DOWN
9. ACTION_POINTER_2_DOWN
And so on.
An onTouchListener sends you multiple actions via MotionEvents. You can get the action for the current event with MotionEvent.getAction().
What you notice here is most likely that you get one ACTION_DOWN (a finger has been placed on the display) event followed by some small ACTION_MOVE events when you move the finger(s) slightly. In the end you will get ACTION_UP (a finger has been lifted) You can filter these out by ignoring events with the wrong action.
For example just return from onTouch() when a move event occured.
public boolean onTouch(View arg0, MotionEvent event) {
Log.d("Code777","Touch is being called finger");
if(event.getAction() == MotionEvent.ACTION_MOVE) {
Log.d("Code777", "Move event detected, ignored!");
return false;
}
// Further processing ..
}
or use a switch to differentiate between all relevant events.
See the MotionEvent class documentation for a list of possible actions.
.
I have a simple application with two views, one is a TableView and the other is ListView. I use GestureDetector to detect the swipes across the screen similarly to how it is done here. Everything works OK, if the list view is populated with just a few items, however when the ListView fills up the whole screen the gesture detection stops working. Doing the swipe across the screen simply shows highlights one of the list items.
I think this is happening because ListView somehow steals the touch events from the GestureListener. Is there a way to prevent this?
I found that GestureDetector works fine in ListItems if you traverse a fairly accurate horizontal path. However if you stray slightly, the list scrolls and the gesture does not complete. What is going on is as follows:
The GestureDetector starts off happily taking MotionEvents from the onTouch you install in the ListItems via setOnTouchListener().
The ListView is meanwhile listening in on events sent to its child views via onInterceptTouchEvent()
The ListView detects that you have started to scroll and returns true from onInterceptTouchEvent().
From then on the MotionEvents are sent to the ListView and NOT to the original target... the ListView starts receiving MotionEvents in its onTouch handler. This continues until the final ACTION_UP. (Note that the MotionEvents from ACTION_DOWN through all the ACTION_MOVEs to ACTION_UP are considered a single gesture and everything starts again after the final ACTION_UP in the sequence)
The original target (ListItem) gets an ACTION_CANCEL MotionEvent and the GestureDetector in your ListItem bails out. This can be seen to happen if you paste the code for GestureDetector into your app and step through it.
I needed my app to behave as if the horizontal swipe continued even if the touch strays from horizontal slightly.
SOLUTION:
This involves ViewGroup.requestDisallowInterceptTouchEvent (boolean disallowIntercept) which stops the parent being able to peek at the motion events. The method involves implementing onTouchListener to detect a slight swipe (10 pixels or so) then stopping the parent intercepting motion events. The parent will then not scroll and the gesture detector continues to completion.
Here's the code:
private boolean mFlingInProgress = false;
private float mStartX = 0;
private final int FLING_TRIGGER_DISTANCE = 10;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
float currentX = event.getRawX();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartX = currentX;
break;
case MotionEvent.ACTION_MOVE:
if (false == mFlingInProgress) {
if (Math.abs(currentX - mStartX) > FLING_TRIGGER_DISTANCE) {
// stop the parent intercepting motion events
mLayout.getParent().requestDisallowInterceptTouchEvent(true);
mFlingInProgress = true;
}
}
break;
case MotionEvent.ACTION_UP:
mFlingInProgress = false;
break;
}
return mGestureDetector.onTouchEvent(event);
}
You could create a custom listview and then implement the gesture detector inside of this i.e. on each row of the list. Could be worth a try.