I have a scrollview in my layout and I want to be able to check if the user is scrolling. I checked the Scrollview in Android Docs, but I don't see anything like isScrolling() that returns a boolean.
My question is: is there a way to check if the user is attempting to scroll, as opposed to clicking a button? Could there be some other class in the Android core that is monitoring the scroll event?
There could be a better way but here is how you can figure it out.
You get a MotionEvent object inside the onTouchEvent(MotionEvent ev) method.
check the action of the motion event as ev.getAction().
If the action is ACTION_MOVE it implies that a scrolling is taking place.
Note: Scrolling is different than a fling. So the same logic cannot be used to determine if the fling is over.
Related
The problem is the the scrolling will intercept with touch event has set to the parent layout.
Can I keep the onTouch event with the scroll in ScrollView ?
This is a very tricky part. There is an overriden method from Activity which is: public boolean onTouchEvent(MotionEvent event)
This is the general method that interprets all the touch events from the whole screen. And you could say, "ok, I can implement this and I am good to go..". And here comes the difficult part on how android works.
As you know every View has its own onTouchEvent() method that you could implement in order to add some custom implementation. So which method will listen? The ScrollView or the Activity? It appears that these touch events go from the "inside" elements to the "outside" elements. I mean parent-child relations.
Another thing to take into account is that the onTouchEvent method returns a boolean. This boolean parameter determines whether the touch event should go one level up or it is handled by the current View. Meaning that if you have a CustomViewA that implements the onTouchEvent() and CustomViewB implementing its own touch event, and the A is a child in B then the touch event would go through A first and if it is not handled it would go to B.
So basically yes it could be done. It depends on what touch event you wanted to do.
So in our case, the ScrollView returns true when the touch events are a horizontal. The activity's touch event will be handled only if the ScrollView touch event is not handled by itself then you are fine. Otherwise you have to override and implement the on touch event of scroll view and in some cases you have to return false so as for the whole layout to implement it. Good luck with the last part. I started to implement a fling effect but came up with some difficulties so I have implemented a 2 finger move with scroll view in it and it works like a charm.
This is about a week of research and experimenting and it is an overview of what I came up with. if you find anything else please let me know. Hope it helped.
I have implemented custom view with some children. The view can be scrolled using standard drag gesture. Also every child can be clicked. The problem is, that when I start dragging the view, one of children gets 'down' event and it changes its state to 'pressed' for a second. I would prefer standard listview behavior - the child goes into pressed state when the user keeps pressing this child with his/her finger for like 50ms. It would reduce blinking caused by misread press event.
I know, that I need at least 2 events to detect if the user is tapping or dragging the view. For now I'm using TimerTask to shedule 'down' event. When I get 'move' event before my 'down' event is executed, I know that the user is dragging and I can cancel the sheduled event.
I know it's quite hacky. I also tried gesturedetector to detect drag and tap events, but it needs some additional work to properly implement changing view state from pressed to default when the user moves finger and starts to drag the view.
My question is - how this is implemented in android listview? I tried to copy their solution from listview implementation, but it's so huge I can't handle it. Simply I don't see the code responsible for handling such situation.
I managed to understand a gesture detection logic in ListView and, in general, in android views. I wrote my own gesture detector, which is somewhat better than the original one. It reports more gestures (multiple taps, dragging) and has some configurables (timeouts, move epsilon). You can find it open-sourced here: Better Gesture Detector on code.google
The library uses Handler class and postDelayed()/removeCallbacks() method combination to detect, handle and cancel motion events and gestures. It's quite simple and one should be able to get the idea by just reading the code.
This repository also contains a simple demo. Please note that this code is provided 'as is', contains some useless comments, logs and should be cleaned up a bit.
I have two scrollviews side by side, I want the user to be able to drag list items back and forth from left to right scrollviews. However, I can't find a way to handle the touch events. I can't set a touch listener for each scrollview seperately as the drag gesture gets dropped when passing from one to another. I tried creating an absolute layout over the top of both, which works from the drag and drop perspective, but it stops me from being able to scroll the scrollviews. Is there a simple solution to this? can anyone help me out?
Generally, onTouchListener returns a boolean that indicates whether the touch has been handled. It's up to you to decide whether the touch was handled or not. When the user touches a View, Android will call it's touch listener. If the touch listener returns true, then it regards the touch as handled then moves on. If the touch listener returns false, then it will go up one to the parent view (in this case whatever your ScrollView is). Then the parent view's touch listener is called and must decide how to handle the touch. It will keep cascading up the parent views until a true is returned or until it reaches the end.
In your case, you may have to decide what the user has to do in order to drag & drop vs. scrolling. Perhaps the user must do a long press on an item before he/she can drag it or something.
Alright, this is giving me headache for over an hour. I've got several ScrollViews in ViewFlipper. I want to implement swipe left/right gesture while letting the scrollview to scroll. The idea is quite simple - intercept the event and viewflipper, parse it and pass it to the children no matter what happened. This way I can easily detect the swipe without making mess with the events.
Theoretically all I have to do is to handle onTouch or something like that and return false, so the event will be dispatched to children. But the Android is so smart he won't send my any other events than ACTION_DOWN if I return false, because it thinks I don't want them.
So how can I capture all the motion events coming to the ViewFlipper and it's children and still dispatch them to the children? I can handle detecting the swipe myself.
Android is very nasty about this kind of things. You have to subclass and implement dispatchTouchEvent the way you need. Or you can grab some android code and modify it. I tried to use onInterceptTouchEvent() but found it unusable.
check out onInterceptTouchEvent() for a view group, if you wish to analyze the touches on view flipper and return false to pass the actions to the child views. look at the following link for more information.
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
I'm pretty new to Android app development, and I've been playing around with swipe gestures using Android's SimpleOnGestureListener and a ViewFlipper. There are 3 children of the ViewFlipper, and each is a ScrollView. They're all dynamically populated when the Activity loads, and they don't change after that. The ScrollView is where the SimpleOnGestureListeners are attached.
Here's the layout I'm using:
+ViewFlipper
++ScrollView (x3, one for each page, each with the following:)
+++LinearLayout (vertical)
++++TextView
++++TableLayout (dynamically populated w/TableRows)
++++View
I extended the onFling method with the common tutorial code you can find anywhere online, and it works great--except when one of the ScrollViews doesn't contain enough content to scroll.
I've narrowed the problem down to touch detection by overriding and calling super on every one of the SimpleOnGestureListener's methods to add a print-to-log.
When I swipe on a page that scrolls, I get something full of "in onClick" "in onScroll" "in onFling" etc. On a page that's too short to scroll, I get "in onClick" "in onShowPress" "in onLongPress", and that's only if I'm touching the content within the too-short scrollview's children--if I touch elsewhere I get no events at all.
Ideas on what's wrong, or how to detect the swipe gesture no matter how big the ScrollView is?
EDIT: I've determined that when I run this on an Android 2.2 emulator, as opposed to the Android 2.1u1 DroidX emulator I've been using, it goes away. This is reproducible across multiple environments.
I have some more insight on this; it seems as though onInterceptTouchEvent is not called for every motion event when a scrollview is contained within a flipper (or a WorkspaceView).
In particular, the behavior I found while modifying another view class to fix this very same issue (it is not unique to flippers) was as follows--note that this is Android 2.1 only:
If the scrollview is long enough to scroll, the ACTION_DOWN motion event is caught by the ScrollView, and every subsequent ACTION_MOVE event goes through onInterceptTouchEvent of the flipper, where it is intercepted and handled appropriately. In android 2.2, this behavior happens regardless of the scroll length.
Back to 2.1: If the scrollview is not long enough to scroll, the ACTION_DOWN motion event is not caught by the scrollview, but instead comes back to the onTouchEvent of the flipper. All subsequent ACTION_MOVE events of the same gesture skip the onInterceptTouchEvent function and go straight to the onTouchEvent function!
The way I resolved this was to take the functionality I had in onTouchEvent for ACTION_MOVE events and refactor it into its own method. In this way, I can have onTouchEvent call onInterceptTouchEvent followed by that functionality if it detects that the event has previously gone unhandled.
case MotionEvent.ACTION_MOVE:
if (touchState == TOUCH_STATE_SCROLLING) {
handleScrollMove(ev);
} else {
// Log.d("workspace","caught a move touch event but not scrolling");
//NOTE: We will never hit this case in Android 2.2. This is to fix a 2.1 bug.
//We need to do the work of interceptTouchEvent here because we don't intercept the move
//on children who don't scroll.
Log.d("workspace","handling move from onTouch");
if(onInterceptTouchEvent(ev) && touchState == TOUCH_STATE_SCROLLING){
handleScrollMove(ev);
}
}
break;
This is from WorkspaceView.java (a modification of Android's Workspace.java, found at the andro-views project on google code, and now here: Horizontal "tab"ish scroll between views ). In the case that we receive a move event, and we are scrolling (which only happens if we have deliberately chosen to intercept it--ie, it's set in the intercept function, so we've been to the intercept function already) we perform the move behavior we desire. If we receive a move event here and we are not scrolling, then we send the event back through onIntercept, and then see if we're now set to scrolling. If so, we perform the action.
It's not elegant, but it works!
I needed to create a new class that extended ScrollView, and used this:
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return gestureDetector.onTouchEvent(event);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
gestureDetector.onTouchEvent(ev);
super.dispatchTouchEvent(ev);
return true;
}
I have no idea why, but if I try to return anything but true in dispatchTouchEvent (the logical thing would have been to
return (gestureDetector.onTouchEvent(ev) || super.dispatchTouchEvent(ev));
if I understand properly), it doesn't work, and this does.
Try setting android:fillViewport="true" in your layout xml for each of the ScrollViews. That tells the ScrollView to be as large as the view it's contained in.
Had the same issue. You need to intercept the touch event on the children of the ScrollView when it's too short to have a scrollbar.