Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scrollview doesn't swipe when it's too short to scroll

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!

like image 215
Jon O Avatar asked Aug 24 '10 04:08

Jon O


2 Answers

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.

like image 161
Jon O Avatar answered Nov 04 '22 19:11

Jon O


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.

like image 34
Rich Schuler Avatar answered Nov 04 '22 19:11

Rich Schuler