HorizontalScrollView within ScrollView Touch Handling

前端 未结 8 1775
轮回少年
轮回少年 2020-11-22 05:28

I have a ScrollView that surrounds my entire layout so that the entire screen is scrollable. The first element I have in this ScrollView is a HorizontalScrollView block tha

相关标签:
8条回答
  • 2020-11-22 05:39

    This finally became a part of support v4 library, NestedScrollView. So, no longer local hacks is needed for most of cases I'd guess.

    0 讨论(0)
  • 2020-11-22 05:46

    Update: I figured this out. On my ScrollView, I needed to override the onInterceptTouchEvent method to only intercept the touch event if the Y motion is > the X motion. It seems like the default behavior of a ScrollView is to intercept the touch event whenever there is ANY Y motion. So with the fix, the ScrollView will only intercept the event if the user is deliberately scrolling in the Y direction and in that case pass off the ACTION_CANCEL to the children.

    Here is the code for my Scroll View class that contains the HorizontalScrollView:

    public class CustomScrollView extends ScrollView {
        private GestureDetector mGestureDetector;
    
        public CustomScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
            mGestureDetector = new GestureDetector(context, new YScrollDetector());
            setFadingEdgeLength(0);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
        }
    
        // Return false if we're scrolling in the x direction  
        class YScrollDetector extends SimpleOnGestureListener {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {             
                return Math.abs(distanceY) > Math.abs(distanceX);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:53

    It wasn't working well for me. I changed it and now it works smoothly. If anyone interested.

    public class ScrollViewForNesting extends ScrollView {
        private final int DIRECTION_VERTICAL = 0;
        private final int DIRECTION_HORIZONTAL = 1;
        private final int DIRECTION_NO_VALUE = -1;
    
        private final int mTouchSlop;
        private int mGestureDirection;
    
        private float mDistanceX;
        private float mDistanceY;
        private float mLastX;
        private float mLastY;
    
        public ScrollViewForNesting(Context context, AttributeSet attrs,
                int defStyle) {
            super(context, attrs, defStyle);
    
            final ViewConfiguration configuration = ViewConfiguration.get(context);
            mTouchSlop = configuration.getScaledTouchSlop();
        }
    
        public ScrollViewForNesting(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public ScrollViewForNesting(Context context) {
            this(context,null);
        }    
    
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {      
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDistanceY = mDistanceX = 0f;
                    mLastX = ev.getX();
                    mLastY = ev.getY();
                    mGestureDirection = DIRECTION_NO_VALUE;
                    break;
                case MotionEvent.ACTION_MOVE:
                    final float curX = ev.getX();
                    final float curY = ev.getY();
                    mDistanceX += Math.abs(curX - mLastX);
                    mDistanceY += Math.abs(curY - mLastY);
                    mLastX = curX;
                    mLastY = curY;
                    break;
            }
    
            return super.onInterceptTouchEvent(ev) && shouldIntercept();
        }
    
    
        private boolean shouldIntercept(){
            if((mDistanceY > mTouchSlop || mDistanceX > mTouchSlop) && mGestureDirection == DIRECTION_NO_VALUE){
                if(Math.abs(mDistanceY) > Math.abs(mDistanceX)){
                    mGestureDirection = DIRECTION_VERTICAL;
                }
                else{
                    mGestureDirection = DIRECTION_HORIZONTAL;
                }
            }
    
            if(mGestureDirection == DIRECTION_VERTICAL){
                return true;
            }
            else{
                return false;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:53

    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

    0 讨论(0)
  • 2020-11-22 05:57

    Thank you Joel for giving me a clue on how to resolve this problem.

    I have simplified the code(without need for a GestureDetector) to achieve the same effect:

    public class VerticalScrollView extends ScrollView {
        private float xDistance, yDistance, lastX, lastY;
    
        public VerticalScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @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(xDistance > yDistance)
                        return false;
            }
    
            return super.onInterceptTouchEvent(ev);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:57

    I've found out that somethimes one ScrollView regains focus and the other loses focus. You can prevent that, by only granting one of the scrollView focus:

        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;
            }
        });
    
    0 讨论(0)
提交回复
热议问题