ScrollView和Gallery干扰

时间:2010-07-03 12:17:14

标签: android

我有一个由许多ScrollView组成的图库,每个ScrollView都占据整个屏幕。问题是ScrollViews的onTouchEvent返回true,因此阻止DOM中的任何其他视图处理相同的事件(在ScrollView级别处理后被吞下)。因此,Gallery不再滚动。另一方面,如果我像这样重写onTouchEvent:

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

然后Gallery接收其on事件进行处理,但SrollView不再滚动。无论哪种方式,你输了!或者你呢?

问题听起来很令人费解,但我相信如果你在过去偶然发现它,你会立即认出它,因为它真是一个该死的!

感谢

5 个答案:

答案 0 :(得分:6)

以下是我在使用纵向ScrollViews的图库中的尝试。

它使用自己的GestureDetector实例,并使用MotionEvents中的onInterceptTouchEvent提供。

当手势检测器识别出滚动时,我们确定它是水平还是垂直并锁定方向,直到手势结束。这避免了对角线滚动。

如果是水平滚动,onInterceptTouchEvent将返回true,以便将来的动作事件转到继承Gallery.onTouchEvent进行实际滚动。

Gallery自己的手势检测器(mGestureDetector中的Gallery.java)未获得所有动作事件,因此有时会报告导致画廊跳转的巨大突然滚动。我投入了一个令人讨厌的黑客,丢弃了那些。

代码:

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;
        }
    }
}

警告:类名中的“更好”一词可能会产生误导!

<强>更新

忘记提及,我还设置了将onTouchEvent转发到相册的活动:

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

更新2:

我对此代码进行了一些改进,并将其放在bitbucket上。 还有一个示例应用程序。它演示了此窗口小部件与ListView一起出现问题: - /

enter image description here

更新3:

Gallery切换到HorizontalScrollView作为我的自定义小部件的基类。 More on this here。随着孩子的工作,Flings工作,ListViews和ExpandableListViews,在Android 1.6,2.2,2.3.4上测试。它的行为现在非常接近谷歌应用程序,IMO。

更新4:

Google发布了ViewPager

答案 1 :(得分:4)

这让我很头疼,我想我会根据chrisschell的回答发布一个解决方案,因为它对我有很多帮助。

这是新的和改进的画廊。

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;
    }
}

滚动视图。

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;       
    }
}

希望这有助于并再次感谢chrisschell指出我正确的方向。

答案 2 :(得分:1)

我写了这个自定义类来处理这个问题,到目前为止它似乎是一个不错的解决方案。它并不完美,我没有用叉子来调用它,但希望这对某人有用: - )

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;
    }
}

我很想听听人们对此的体验,以及您的任何建议。

答案 3 :(得分:1)

我发现画廊滚动 - atraudes解决方案的行为不是最优,所以我寻找其他解决方案。最后我找到了一个适合我的方法:

  1. 通过扩展Gallery并覆盖onTouchEvent,onInterceptTouchEvent,onScroll和onFling来创建一个FriendlyGallery类。使用onInterceptTouchEvent使用FriendlyGallery“注册”当前显示的ScrollView,并在onScroll和onGling of FriendlyGallery中使用“ScrollView”(使用它的scrollBy和fling方法)“遥控”(onFling的velocityY = -1 * rapidY of fling !! !)。对onTouchEvent返回true以在此处捕获Touch事件!
  2. 通过扩展ScrollView并覆盖onTouchEvent创建一个FriendlyScrollView类,onInterceptTouchEvent只返回“false”,以防止Touch事件干扰您“远程控制”ScrollView。

答案 4 :(得分:0)

此解决方案如何:只需处理ScrollViewGallery中的事件:

public class GalleryFriendlyScrollView extends ScrollView {
    private Gallery fParent;


    public GalleryFriendlyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean superDone = super.onTouchEvent(ev);
        // correct the location of the event because the gallery
        // might have moved the scroll pane horizontally
        ev.setLocation(ev.getX() + getLeft(), ev.getY());
        // dispatch the event also to the gallery
        boolean galleryDone = getGallery().onTouchEvent(ev);
        return superDone || galleryDone;
    }

    private Gallery getGallery() {
        if (fParent == null) {
            fParent = (Gallery)this.getParent();
        }
        return fParent;
    }
}