我有一个环绕整个布局的ScrollView,这样整个屏幕都可以滚动。我在这个ScrollView中的第一个元素是一个HorizontalScrollView块,它具有可以水平滚动的功能。我已经添加了一个ontouchlistener到horizontalscrollview来处理触摸事件,并强制视图“捕捉”到ACTION_UP事件上最近的图像。

所以我想要的效果就像普通的android主屏幕,你可以从一个屏幕滚动到另一个屏幕,当你抬起手指时,它就会切换到一个屏幕。

这一切都很好,除了一个问题:我需要几乎完美地水平地从左向右滑动ACTION_UP才能注册。如果我至少垂直滑动(我认为许多人倾向于在手机上左右滑动时这样做),我将收到ACTION_CANCEL而不是ACTION_UP。我的理论是,这是因为horizontalscrollview在一个scrollview中,而scrollview劫持了垂直触摸来允许垂直滚动。

我如何才能禁用触摸事件为滚动视图从我的水平滚动视图,但仍然允许正常的垂直滚动在滚动视图的其他地方?

下面是我的代码示例:

   public class HomeFeatureLayout extends HorizontalScrollView {
    private ArrayList<ListItem> items = null;
    private GestureDetector gestureDetector;
    View.OnTouchListener gestureListener;
    private static final int SWIPE_MIN_DISTANCE = 5;
    private static final int SWIPE_THRESHOLD_VELOCITY = 300;
    private int activeFeature = 0;

    public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
        super(context);
        setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
        setFadingEdgeLength(0);
        this.setHorizontalScrollBarEnabled(false);
        this.setVerticalScrollBarEnabled(false);
        LinearLayout internalWrapper = new LinearLayout(context);
        internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
        addView(internalWrapper);
        this.items = items;
        for(int i = 0; i< items.size();i++){
            LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
            TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
            ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
            TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
            title.setTag(items.get(i).GetLinkURL());
            TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
            header.setText("FEATURED");
            Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
            image.setImageDrawable(cachedImage.getImage());
            title.setText(items.get(i).GetTitle());
            date.setText(items.get(i).GetDate());
            internalWrapper.addView(featureLayout);
        }
        gestureDetector = new GestureDetector(new MyGestureDetector());
        setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (gestureDetector.onTouchEvent(event)) {
                    return true;
                }
                else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
                    int scrollX = getScrollX();
                    int featureWidth = getMeasuredWidth();
                    activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
                    int scrollTo = activeFeature*featureWidth;
                    smoothScrollTo(scrollTo, 0);
                    return true;
                }
                else{
                    return false;
                }
            }
        });
    }

    class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                //right to left 
                if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
                    smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
                    return true;
                }  
                //left to right
                else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    activeFeature = (activeFeature > 0)? activeFeature - 1:0;
                    smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
                    return true;
                }
            } catch (Exception e) {
                // nothing
            }
            return false;
        }
    }
}

当前回答

我想我找到了一个更简单的解决方案,只是这使用了ViewPager的子类而不是(它的父类)ScrollView。

更新2013-07-16:我添加了一个覆盖onTouchEvent以及。它可能有助于解决评论中提到的问题,尽管YMMV。

public class UninterceptableViewPager extends ViewPager {

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean ret = super.onInterceptTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean ret = super.onTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }
}

这类似于android.widget中使用的技术。画廊onScroll()。 谷歌I/O 2013演示文稿为Android编写自定义视图进一步解释了这一点。

更新2013-12-10:Kirill Grouchnikov在一篇关于(当时的)Android Market应用的文章中也描述了类似的方法。

其他回答

这最终成为支持v4库NestedScrollView的一部分。所以,我猜在大多数情况下不再需要本地黑客了。

我想我找到了一个更简单的解决方案,只是这使用了ViewPager的子类而不是(它的父类)ScrollView。

更新2013-07-16:我添加了一个覆盖onTouchEvent以及。它可能有助于解决评论中提到的问题,尽管YMMV。

public class UninterceptableViewPager extends ViewPager {

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean ret = super.onInterceptTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean ret = super.onTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }
}

这类似于android.widget中使用的技术。画廊onScroll()。 谷歌I/O 2013演示文稿为Android编写自定义视图进一步解释了这一点。

更新2013-12-10:Kirill Grouchnikov在一篇关于(当时的)Android Market应用的文章中也描述了类似的方法。

我发现有时一个ScrollView恢复焦点,另一个失去焦点。你可以通过只授予一个scrollView焦点来防止这种情况:

    scrollView1= (ScrollView) findViewById(R.id.scrollscroll);
    scrollView1.setAdapter(adapter);
    scrollView1.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            scrollView1.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

感谢Neevek,他的回答对我有用,但当用户开始在水平方向滚动水平视图(ViewPager)时,它不会锁定垂直滚动,然后不抬起手指垂直滚动,它开始滚动底层容器视图(ScrollView)。我在Neevak的代码中做了一个小小的改变,从而解决了这个问题:

private float xDistance, yDistance, lastX, lastY;

int lastEvent=-1;

boolean isLastEventIntercepted=false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            xDistance = yDistance = 0f;
            lastX = ev.getX();
            lastY = ev.getY();


            break;

        case MotionEvent.ACTION_MOVE:
            final float curX = ev.getX();
            final float curY = ev.getY();
            xDistance += Math.abs(curX - lastX);
            yDistance += Math.abs(curY - lastY);
            lastX = curX;
            lastY = curY;

            if(isLastEventIntercepted && lastEvent== MotionEvent.ACTION_MOVE){
                return false;
            }

            if(xDistance > yDistance )
                {

                isLastEventIntercepted=true;
                lastEvent = MotionEvent.ACTION_MOVE;
                return false;
                }


    }

    lastEvent=ev.getAction();

    isLastEventIntercepted=false;
    return super.onInterceptTouchEvent(ev);

}

Neevek's solution works better than Joel's on devices running 3.2 and above. There is a bug in Android that will cause java.lang.IllegalArgumentException: pointerIndex out of range if a gesture detector is used inside a scollview. To duplicate the issue, implement a custom scollview as Joel suggested and put a view pager inside. If you drag (don't lift you figure) to one direction (left/right) and then to the opposite, you will see the crash. Also in Joel's solution, if you drag the view pager by moving your finger diagonally, once your finger leave the view pager's content view area, the pager will spring back to its previous position. All these issues are more to do with Android's internal design or lack of it than Joel's implementation, which itself is a piece of smart and concise code.

http://code.google.com/p/android/issues/detail?id=18990