开发者

android: ViewPager and HorizontalScrollVIew

开发者 https://www.devze.com 2023-03-25 22:43 出处:网络
I have a HorizontalScrollView inside my ViewPager. I set requestDisallowInterceptTouchEvent(true); for the HorizontalScrollView but the ViewPager is still sometimes开发者_运维知识库 intercepting touc

I have a HorizontalScrollView inside my ViewPager. I set requestDisallowInterceptTouchEvent(true); for the HorizontalScrollView but the ViewPager is still sometimes开发者_运维知识库 intercepting touch events. Is there another command I can use to prevent a View's parent and ancestors from intercepting touch events?

note: the HorizontalScrollView only occupies half the screen.


I had the same problem. My solution was:

  1. Make a subclass of ViewPager and add a property called childId.
  2. Create a setter for the childId property and set the id of the HorizontalScrollView.
  3. Override onInterceptTouchEvent() in the subclass of ViewPager and if the childId property is more than 0 get that child and if the event is in HorizontalScrollView area return false.

Code

public class CustomViewPager extends ViewPager {

    private int childId;    

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (childId > 0) {
            View scroll = findViewById(childId);
            if (scroll != null) {
                Rect rect = new Rect();
                scroll.getHitRect(rect);
                if (rect.contains((int) event.getX(), (int) event.getY())) {
                    return false;
                }
            }
        }
        return super.onInterceptTouchEvent(event);
    }

    public void setChildId(int id) {
        this.childId = id;
    }
}

In onCreate() method

viewPager.setChildId(R.id.horizontalScrollViewId);
adapter = new ViewPagerAdapter(this);
viewPager.setAdapter(adapter);

Hope this help


Thanks for the reply. I modified your solution a bit and managed to make nested ViewPagers work:

public class CustomViewPager extends ViewPager {
    private int childId;

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
     @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (childId > 0) {          
            ViewPager pager = (ViewPager)findViewById(childId);

            if (pager != null) {           
                pager.requestDisallowInterceptTouchEvent(true);
            }

        }

        return super.onInterceptTouchEvent(event);
    }

    public void setChildId(int id) {
        this.childId = id;
    }
}


So, I know this question is pretty old, but I've come with a solution which I think has some extra advantages over the one posted here. One situation that could happen is that I could have more than one HorizontalScrollView inside my ViewPager (which I do), and then I would need to provide tons of Ids to the ViewPager so it can always check for child touch conflicts.

I analyzed the source from the ViewPager, and found that they use this method called "canScroll" to determine if a child view can be scrolled. Luckily, it wasn't made static nor final, so I could just override it. Inside that method that the "boolean ViewCompat.canScrollHorizontally(View, int)" was called (the one that always returns false for SDK < 14). My solution then was:

public class CustomViewPager extends ViewPager {

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CustomViewPager(Context context) {
        super(context);
    }
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        return super.canScroll(v, checkV, dx, x, y) || (checkV && customCanScroll(v));
    }
    protected boolean customCanScroll(View v) {
        if (v instanceof HorizontalScrollView) {
            View hsvChild = ((HorizontalScrollView) v).getChildAt(0);
            if (hsvChild.getWidth() > v.getWidth())
                return true;
        }
        return false;
   }
}

So, if you have a different class of View inside your ViewPager that should also receive horizontal drags, just change the customCanScroll(View) so you can return if the touch should not be intercepted or not.

The boolean checkV is true if the current view should also be checked for 'scrollability', that's why we should also check for that.

Hope this helps future issues like this (or if there is a better solution, or an official one from the Android platform, let me know).


I don't know if you've solved the problem already or not. But none of the answers above work properly. So I think it's necessary for those people who might be searching the solutions with suffering.

In fact, the solution is quite simple if you read the official document about handling touch events in ViewGroup.

First you need to understand the usage of

ViewParent.requestDisallowInterceptTouchEvent(boolean), Call this upon a parent View to indicate that it should not intercept touch events with onInterceptTouchEvent(MotionEvent).

Then you need to intercept the events and make sure that the parent ViewGroup of your HorizontalScrollView won't dispatch the events any further.

So, set an OnToushListener for you HorizontalScrollView. Detecting the touch down and move events, then set the requestDisallowInterceptTouchEvent(true) to it's parent(ViewPager or sth else) to make sure the events would be handled by the HorizontalScrollView.

Of course you could put these code below into your CustomHorizontalScrollView components so that it can be reused.

Hoping it helps. ;-)

horizontalScrollView.setOnTouchListener(new View.OnTouchListener() {
float rawX;
int mTouchSlop =  ViewConfiguration.get(getActivity()).getScaledTouchSlop();

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            v.getParent().requestDisallowInterceptTouchEvent(true);
            rawX = event.getRawX();
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            v.getParent().requestDisallowInterceptTouchEvent(false);
            rawX = 0f;
            break;
        case MotionEvent.ACTION_MOVE:
            if (Math.abs(rawX - event.getRawX()) > mTouchSlop)
                v.getParent().requestDisallowInterceptTouchEvent(true);
            break;
    }
    return false;
}


Here is another solution that worked for me,

Create a custom ViewPager

public class NoScrollViewPager extends ViewPager {

    private boolean scrollDisable = false;


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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(scrollDisable) {
            return false;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    public void disableScroll() {
        scrollDisable = true;
    }

    public void enableScroll() {
        scrollDisable = false;
    }



}

And then add OnTouchListener to your item, whether it was a HorizontalScrollView or Gallery or anything else that you don't want the viewpager to scroll when it is touched.

gf.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View arg0,
            MotionEvent ev) {
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            viewPager.disableScroll();
            break;
        case MotionEvent.ACTION_UP:
            viewPager.enableScroll();
            break;
        }
        return false;
    }

});


Fede's solution didn't work very well for me. My HorizontalScrollVIew had areas where it didn't catch any inputs (click, swipe..). I guess getHitRect() have to do something with that. After replacing it with following code everything worked fine.

It is possible to add multiple HorizontalScrollVIew views or any other views that requires focus too.

public class CustomViewPager extends ViewPager {

    private ArrayList<Integer> children; 

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        children = new ArrayList<Integer>();
    }   

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (children.size() > 0) {
            for(int i=0; i<children.size(); i++){
                View scroll = findViewById(children.get(i));
                if (scroll != null & scroll.isPressed()) {
                    return false;
                }
            }
        }
        return super.onInterceptTouchEvent(event);
    }

    public void addChildId(int id) {
       children.add(id);
    }

}

In XML you will have something like this:

<form.paginator.lib.CustomViewPager
      android:id="@+id/pager"
      android:layout_width="fill_parent"
      android:layout_height="150dp" />

In onCreate() method:

mPager = (CustomViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.addChildId(R.id.avatarImageView);

In OnTouchListener() method for view you have added to CustomViewPager children:

case MotionEvent.ACTION_DOWN:
     v.setPressed(true);
     .
     .
     .
case MotionEvent.ACTION_UP:
     v.setPressed(false);


I was having trouble with a small gallery view on the foot of my first page in a viewpager group. On swiping the gallery view focus would jump between moving the gallery items or the viewpager.

Overriding ViewPager as listed above and setting requestDisallowInterceptTouchEvent solved my problem. The pages no longer change when swiping the galleryview. Think its an important solution for those using galleryviews with viewpager.


So, I modified Fede's answer because it was disabling the touch in the same area in the pages around the one I wanted. Also, I made it able to add multiple views because I'm dealing with a lot of graphics and interactive views that use horizontal drag/drop interactivity. This way you can add

public class CustomViewPager extends ViewPager {

private List<ViewFocus> lateralTouchViews = new ArrayList<>();
public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    for(int i = 0; i< lateralTouchViews.size(); i++){
        View scroll = findViewById(lateralTouchViews.get(i).id);
        if (scroll != null && getCurrentItem() == lateralTouchViews.get(i).itemPosition) {
            Rect rect = new Rect();
            scroll.getHitRect(rect);
            if (rect.contains((int) event.getX(), (int) event.getY())) {
                return false;
            }
        }
    }
    return super.onInterceptTouchEvent(event);
}

public void addConflict(int id, int pageIndex) {
    lateralTouchViews.add(new ViewFocus(id, pageIndex));
}
public class ViewFocus{
    int id;
    int itemPosition;

    public ViewFocus(int id, int itemPosition){
        this.id = id;
        this.itemPosition = itemPosition;
    }
}

}


after test a lot of solution, I find this. in MyPagerView:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (childId > 0) {
        View scroll = findViewById(childId);
        if (scroll != null && mGestureDetector.onTouchEvent(event)) {
            return false;
    }
}
return super.onTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (childId > 0) {
            HorizontalScrollView scroll = (HorizontalScrollView)findViewById(childId);
            if (scroll != null && mGestureDetector.onTouchEvent(event)) {
                return false;
            }
    }
    return super.onInterceptTouchEvent(event);
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号