ScrollView and Gallery interfering

I have a Gallery composed of many ScrollViews each of which occupies the whole screen. problem is the ScrollViews' onTouchEvent returns true and therefore prevent any other view in the DOM to handle the same event (which is swallowed after being processed at the ScrollView level). As a result the Gallery doesn't scroll anymore. On the other hand if I override onTouchEvent like this:

   @Override
   public boolean onTouchEvent(MotionEvent ev) {
      super.onTouchEvent(ev);
      return false; // <<<<<<<<<<<<<<<<<
   }   

then the Gallery receives its on event to process but the SrollView doesnt scroll anymore. Either way you lose! or do you?

problem sounds puzzling but I am sure if u stumbled upon it in the past ur gonna recognize it straight away as it a freaking damn one!

thanks


Here's my attempt at gallery that works with vertical ScrollViews .

It uses its own instance of GestureDetector and feeds it with MotionEvents from onInterceptTouchEvent .

When gesture detector recognizes a scroll, we determine whether it's horizontal or vertical and lock on the direction until the gesture is finished. This avoids diagonal scrolling.

If it's a horizontal scroll, onInterceptTouchEvent will return true so that future motion events go to inherited Gallery.onTouchEvent to do the actual scrolling.

Gallery 's own gesture detector ( mGestureDetector in Gallery.java ) doesn't get all motion events and thus sometimes reports huge sudden scrolls that cause gallery to jump around. I've put in aa nasty hack that discards those.

The code:

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.Gallery;

public class BetterGallery extends Gallery {
    /* This gets set when we detect horizontal scrolling */
    private boolean scrollingHorizontally = false;

    /* This gets set during vertical scrolling. We use this to avoid detecting
     * horizontal scrolling when vertical scrolling is already in progress
     * and vice versa. */
    private boolean scrollingVertically = false;

    /* Our own gesture detector, Gallery's mGestureDetector is private.
     * We'll feed it with motion events from `onInterceptTouchEvent` method. */
    private GestureDetector mBetterGestureDetector;

    public BetterGallery(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mBetterGestureDetector = new GestureDetector(new BetterGestureListener());
    }

    public BetterGallery(Context context, AttributeSet attrs) {
        super(context, attrs);
        mBetterGestureDetector = new GestureDetector(new BetterGestureListener());
    }

    public BetterGallery(Context context) {
        super(context);
        mBetterGestureDetector = new GestureDetector(new BetterGestureListener());
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        // Limit velocity so we don't fly over views
        return super.onFling(e1, e2, 0, velocityY);
    } 

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // Documentation on this method's contract:
        // http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)

        // Reset our scrolling flags if ACTION_UP or ACTION_CANCEL
        switch (ev.getAction()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            scrollingHorizontally = false;
            scrollingVertically = false;
        }       

        // Feed our gesture detector
        mBetterGestureDetector.onTouchEvent(ev);

        // Intercept motion events if horizontal scrolling is detected
        return scrollingHorizontally;
    }


    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // Hack: eat jerky scrolls caused by stale state in mGestureDetector
        // which we cannot directly access
        if (Math.abs(distanceX) > 100) return false;

        return super.onScroll(e1, e2, distanceX, distanceY);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Reset our scrolling flags if ACTION_UP or ACTION_CANCEL
        switch(event.getAction()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            scrollingHorizontally = false;
            scrollingVertically = false;
        }

        super.onTouchEvent(event);
        return scrollingHorizontally;
    }

    private class BetterGestureListener implements GestureDetector.OnGestureListener {

        @Override
        public boolean onDown(MotionEvent arg0) {
            return false;
        }

        @Override
        public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent arg0) {
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (scrollingHorizontally || scrollingVertically) {
                // We already know we're scrolling, ignore this callback.
                // This avoids changing scrollingHorizontally / scrollingVertically
                // flags mid-scroll.
                return false;
            }

            scrollingHorizontally |= Math.abs(distanceX) > Math.abs(distanceY);
            // It's a scroll, and if it's not horizontal, then it has to be vertical
            scrollingVertically = !scrollingHorizontally;

            return false;
        }

        @Override
        public void onShowPress(MotionEvent arg0) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent arg0) {
            return false;
        }
    }
}

Warning: word "Better" in class name is likely misleading!

Update:

Forgot to mention, I've also set the activity to forward its onTouchEvent to gallery:

@Override
public boolean onTouchEvent(MotionEvent event) {
    return mGallery.onTouchEvent(event);
}

Update 2:

I've made some improvements to this code and put it up on bitbucket. There's also an example app. It demonstrates that this widget has issues with ListView as child :-/

在这里输入图像描述

Update 3:

Switched from Gallery to HorizontalScrollView as the base class for my custom widget. More on this here. Flings work, ListViews and ExpandableListViews as children work, tested on Android 1.6, 2.2, 2.3.4. Its behaviour is now quite close to that of Google apps, IMO.

Update 4:

Google has published ViewPager!


This has given me headaches and I thought I'd post a solution based on chrisschell's answer because it helped me a lot.

Here's the new and improved gallery.

public class FriendlyGallery extends Gallery {
    FriendlyScrollView currScrollView;

    public FriendlyGallery(Context context) {
        super(context);
    }
    public FriendlyGallery(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);  
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        currScrollView = getCurrScrollView();
        return super.onInterceptTouchEvent(ev);     
    }
    @Override
    public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if(currScrollView != null)
            currScrollView.scrollBy(0, (int) distanceY);
        return super.onScroll(e1, e2, distanceX, distanceY);
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if(currScrollView != null)
            currScrollView.fling(-(int) distanceY);
        return super.onFling(e1, e2, distanceX, distanceY);     
    }

    private FriendlyScrollView getCurrScrollView() {
        //I have a load more button that shouldn't be cast to a scrollview
        int pos = getFirstVisiblePosition();
        if(pos != getAdapter().getCount()-1)
            return (FriendlyScrollView)this.getSelectedView();
        else
            return null;
    }
}

And the scrollview.

public class FriendlyScrollView extends ScrollView {

    public FriendlyScrollView(Context context) {
        super(context);
    }
    public FriendlyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;       
    }
}

Hope this helps and thanks again to chrisschell for pointing me in the right direction.


I wrote this custom class to be able to deal with that problem, and so far it seems to be a decent solution. It's not perfect and I haven't stuck a fork in it to call it done, but hopefully this may be of some use to someone :-)

import android.content.Context;
import android.view.MotionEvent;
import android.widget.Gallery;
import android.widget.ScrollView;

public class GalleryFriendlyScrollView extends ScrollView{

    private static int NONE = -1;
    private static int DOSCROLLVIEW = 0;
    private static int DOGALLERY = 1;

    private float lastx = 0;
    private float lasty = 0;
    private float firstx = 0;
    private float firsty = 0;
    private float lastRawx = 0;
    private float lastRawy = 0;
    private int gestureRecipient = NONE;
    private boolean donewithclick = true;
    private Gallery parent = null;
    private boolean firstclick = true;

    public GalleryFriendlyScrollView(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean retthis = true;
        //Indicating a fresh click
        if(donewithclick){
            firstx = ev.getX();
            firsty = ev.getY();
            lastx = firstx;
            lasty = firsty;
            lastRawx = ev.getRawX();
            lastRawy = ev.getRawY();
            donewithclick = false;
            firstclick = true;
        }
        //We don't know where these gesture events are supposed to go to. 
        //We have movement on the x and/or why axes, so we can determine where they should go now.
        if((gestureRecipient == NONE) && (lastx != ev.getX() || lasty != ev.getY())){
            //Determine whether there's more movement vertically or horizontally
            float xdiff = ev.getX() - lastx;
            float ydiff = ev.getY() - lasty;
            if(xdiff < 0)xdiff = xdiff * -1;
            if(ydiff < 0)ydiff = ydiff * -1;
            if((xdiff) > (ydiff)){
                gestureRecipient = DOGALLERY;
            } else {
                gestureRecipient = DOSCROLLVIEW;
            }
        }
        if(gestureRecipient == DOGALLERY){
            if(!firstclick){
                //After you drag the screen left or right a bit, the baseline for where it calculates
                //x and y from changes, so we need to adjust the offset to our original baseline
                float offsetx = (((ev.getX() - lastx) - (ev.getRawX() - lastRawx)) * -1);
                float offsety = (((ev.getY() - lasty) - (ev.getRawY() - lastRawy)) * -1);
                ev.offsetLocation(offsetx, offsety);
            }
            retthis = getGallery().onTouchEvent(ev);
            firstclick = false;
        } else if(gestureRecipient == DOSCROLLVIEW){
            retthis = super.onTouchEvent(ev);
        }
        if(ev.getAction() == MotionEvent.ACTION_UP){
            //User's finger has been lifted
            if(((firstx == ev.getX()) && (firsty == ev.getY()))){
                //Since there isn't any movement in either direction, it's a click
                getGallery().onSingleTapUp(ev);
                super.onTouchEvent(ev);
            }
            donewithclick = true;
            gestureRecipient = NONE;
        }
        //And record our coordinate data
        lastx = ev.getX();
        lasty = ev.getY();
        lastRawx = ev.getRawX();
        lastRawy = ev.getRawY();
        return retthis;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        getGallery().onTouchEvent(ev);
        return super.onInterceptTouchEvent(ev);
    }

    private Gallery getGallery(){
        //Gets the gallery, in this case assuming the ScrollView is the direct child in the gallery.
        //Adjust as needed
        if(parent == null){
            parent = (Gallery) this.getParent();
        }
        return parent;
    }
}

I'd love to hear of people's experiences with this, and any suggestions you have.

链接地址: http://www.djcxy.com/p/91246.html

上一篇: 滚动视图太短而不能滚动时不滚动

下一篇: 滚动查看和图库干扰