एक स्क्रॉल दृश्य के अंदर ViewPager correclty को स्क्रॉल नहीं करता है


88

मेरे पास एक 'पृष्ठ' है, जिसमें इस पर कई घटक हैं, और जो सामग्री डिवाइस की ऊंचाई से अधिक लंबी है। ठीक है, बस लेआउट के सभी (पूरे पृष्ठ) के अंदर एक ScrollView, कोई समस्या नहीं है।

घटकों में से एक एक है ViewPager। यह सही ढंग से प्रस्तुत करता है, लेकिन एक स्वाइप / फ़्लिंग की प्रतिक्रिया सही ढंग से प्रदर्शन नहीं कर रही है, यह चिड़चिड़ा है और हमेशा काम नहीं करता है। ऐसा लगता है कि यह 'भ्रमित' हो रहा है ScrollView, इसलिए केवल 100% काम करता है जब आप एक सटीक क्षैतिज रेखा में भागते हैं।

यदि मैं हटाता हूं ScrollView, तो ViewPager पूरी तरह से प्रतिक्रिया करता है।

मैंने आसपास खोज की है और इसे ज्ञात दोष के रूप में नहीं पाया है। क्या किसी और को भी इसका अनुभव हुआ है?

  • प्लेटफार्म संस्करण: 1.6
  • संगतता पुस्तकालय v4।
  • डिवाइस: एचटीसी इनक्रेडिबल एस

नीचे आप के साथ परीक्षण करने के लिए कुछ उदाहरण कोड ScrollViewहैं, इसे सही ढंग से काम करने के लिए देखें।

गतिविधि:

package com.ss.activities;

import com.ss.R;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.TextView;

public class PagerInsideScollViewActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ViewPager vp = (ViewPager) findViewById(R.id.viewpager);
        vp.setAdapter(new MyPagerAdapter(this));
    }
}

class MyPagerAdapter extends PagerAdapter {

    private Context ctx;

    public MyPagerAdapter(Context context) {
        ctx = context;
    }

    @Override
    public int getCount() {
        return 2;
    }

    @Override
    public Object instantiateItem(View collection, int position) {

        TextView tv =  new TextView(ctx);
        tv.setTextSize(50);
        tv.setTextColor(Color.WHITE);
        tv.setText("SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE");

        ((ViewPager) collection).addView(tv);

        return tv;

    }

    @Override
    public void destroyItem(View collection, int position, Object view) {
         ((ViewPager) collection).removeView((View) view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
    }

    @Override
    public void startUpdate(View arg0) {
    }

    @Override
    public void finishUpdate(View arg0) {
    }
}

लेआउट:

<?xml version="1.0" encoding="utf-8"?>
    <ScrollView
         xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" >

            <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="fill_parent"
                android:layout_height="300dp" />

        </LinearLayout>

    </ScrollView>


Please show my answer in this link: stackoverflow.com/questions/7381360/…
Geral Alexander Torres

जवाबों:


60

I had the same problem. My solution was to call requestDisallowInterceptTouchEvent when the ViewPager scroll started.

Here is my code:

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

pager.setOnPageChangeListener(new SimpleOnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        pager.getParent().requestDisallowInterceptTouchEvent(true);
    }
});

1
this is kind of working. I would like scroll view to go vertically even if touch is starting on view pager if the move is "mostly" vertical
Nemanja Kovacevic

3
is it necessary to have requestDisallowInterceptTouchEvent in both onTouch and onPageScrolled or is that just overkill? Could we get the same results by moving requestDisallowInterceptTouchEvent to only onPageScrollStateChanged?
craigrs84

1
I have same problem, and found this best solution, hope this work for you. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

how can i still override onClick event? it conflicts with on Touch?
Kiddo

4
what is the pager and mpager here i need to give the input?
Sujithrao

30

Further reading has revealed that there are issues with scrolling components inside scrolling components.

One solution is to 'disable' the vertical scrolling of the ScrollView on the area of the contained scrollable component, in my case a ViewPager.

The code for this solution is detailed here (and its simply brilliant!)


which solution in that page should i refer to ?
Harsha M V

1
requestDisallowInterceptTouchEvent is the one you seek.
yahya

1
I have same problem, and found this best solution, hope this work for you. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

thanks for the link, the solution i found there still gave me problems with not perfectly horizontal swipes in viewpager, so I modified it a little to get it work: stackoverflow.com/a/33696740/2093236
Dmide

15

Here's a solution:

    mPager.setOnTouchListener(new View.OnTouchListener() {

        int dragthreshold = 30;
        int downX;
        int downY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                if (distanceY > distanceX && distanceY > dragthreshold) {
                    mPager.getParent().requestDisallowInterceptTouchEvent(false);
                    mScrollView.getParent().requestDisallowInterceptTouchEvent(true);
                } else if (distanceX > distanceY && distanceX > dragthreshold) {
                    mPager.getParent().requestDisallowInterceptTouchEvent(true);
                    mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                mPager.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }
            return false;
        }
    });

Basically setting the X,Y values when you press down, and calculating the distance while dragging to determine which way we'd like to go. Play around with the dragthreshold to optimize for your case.


I had the same problem.This is the only solution worked. Good work...:)
rawcoder064

This is the best solution for me. It won't disturb the ScrollView sliding up and down.
Lei Guo

This is the best solution for me. Also we have the option to configure the threshold value in this solution.
Vinayak

doesn't work for me, because the scrollview intercepts the touch before this cancelling statement is reached
AdrianoCelentano

Great work, and if you want to use this with Wrapcontentviewpager then also it is working very well
Silvans Solanki

4

With a ViewPager, you can capture page-change events and prevent the ScrollView from intercepting the touch event that caused the page change.

This is very simple, using ViewGroup.requestDisallowInterceptTouchEvent(boolean). It also lets the user drag the ScrollView even if they start a drag on the ViewPager, but horizontal dragging on the pager will still work without the ScrollView interfering.

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Add android:id for your ScrollView in your layout
        final ScrollView sv = (ScrollView) findViewById(R.id.scrollview);
        final ViewPager vp = (ViewPager) findViewById(R.id.viewpager);
        vp.setAdapter(new MyPagerAdapter(this));

        // Use a page-change listener to respond to begin-drag events:
        vp.setOnPageChangeListener(new OnPageChangeListener() {
            @Override
            public void onPageSelected(int newState) {
                if (newState == ViewPager.SCROLL_STATE_DRAGGING) {
                    // Prevent the ScrollView from intercepting this event now that the page is changing.
                    // When this drag ends, the ScrollView will start accepting touch events again.
                    sv.requestDisallowInterceptTouchEvent(true);
                }
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });

    }

This works for me with the Android Support v4 library on Android 2.3.4 and 4.2.1.


1
is it supposed to be onPageScrollStateChanged instead of onPageSelected?
craigrs84

2

I adapted the solution from @Michael Herbig The advantage to this is that it works on any view that allows setOnTouchListener, not just a ViewPager (for example ViewPagerIndicator) and it is a self-contained class.

Example usage:

// runStatsPager is a android.support.v4.view.ViewPager;
runStatsPager.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPager));

// runStatsPagerIndicator is a com.viewpagerindicator.TitlePageIndicator
runStatsPagerIndicator.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPagerIndicator));

And the class:

class ViewInScrollViewTouchHelper implements View.OnTouchListener {

    private final ScrollView scrollView;
    private final View viewInScrollView;
    int dragthreshold = 30;
    int downX;
    int downY;

    public ViewInScrollViewTouchHelper(View viewInScrollView) {

        if (viewInScrollView == null) {
            throw new IllegalArgumentException("viewInScrollView cannot be null.");
        }

        ViewParent parent = viewInScrollView.getParent();
        ScrollView scrollView = null;
        do {
            if (parent instanceof ScrollView) {
                scrollView = (ScrollView) parent;
                break;
            }
        } while(parent != null && (parent = parent.getParent()) != null);

        if (scrollView == null) {
            throw new IllegalArgumentException("View does not have a ScrollView in its parent hierarchy.");
        }

        this.scrollView = scrollView;
        this.viewInScrollView = viewInScrollView;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                if (distanceY > distanceX && distanceY > dragthreshold) {
                    viewInScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                    scrollView.getParent().requestDisallowInterceptTouchEvent(true);
                } else if (distanceX > distanceY && distanceX > dragthreshold) {
                    viewInScrollView.getParent().requestDisallowInterceptTouchEvent(true);
                    scrollView.getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                scrollView.getParent().requestDisallowInterceptTouchEvent(false);
                viewInScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return false;
    }
}

1
public class WrapContentHeightViewPager extends ViewPager {

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

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

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int height = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

        int h = child.getMeasuredHeight();
        if (h > height) height = h;
    }

    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

}

@Override public boolean onTouch(View v, MotionEvent event) {

    int dragthreshold = 30;
    int downX = 0;
    int downY = 0;

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) event.getRawX();
            downY = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            int distanceX = Math.abs((int) event.getRawX() - downX);
            int distanceY = Math.abs((int) event.getRawY() - downY);

            if (distanceY > distanceX && distanceY > dragthreshold) {
                mViewPager.getParent().requestDisallowInterceptTouchEvent(false);
                mScrollView.getParent().requestDisallowInterceptTouchEvent(true);
            } else if (distanceX > distanceY && distanceX > dragthreshold) {
                mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            mViewPager.getParent().requestDisallowInterceptTouchEvent(false);
            break;
    }
    return false;

}

Use above two and it will worked very nicely. I have used in my code as well.


1

So with this approach I let the ViewPager scroll in the X direction without having to worry about the ScrollView (parent) stealing the event and canceling the current scroll. More importantly, this also leaves the area where the ViewPager is located scrollable in the Y direction.

public class CustomViewPager extends ViewPager {

private GestureDetector     mGestureDetector;
View.OnTouchListener        mGestureListener;

public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    mGestureDetector = new GestureDetector(context, new Detector());
}

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {

    mGestureDetector.onTouchEvent(motionEvent);
    return super.onTouchEvent(motionEvent);
}

class Detector extends SimpleOnGestureListener {

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

        requestDisallowInterceptTouchEvent(true);

        return false;
    }
}
}

Kind of unfortunate to subclass ViewPager here. I imagine this could be adapted to a View.OnTouchListener. I'll upvote for now!
Jon Willis

0

Neither of solutions here worked well for me because it's either the modification of ViewPager's touch logic or ScrollView's logic. I had to implement both, now it works like a charm.

public class TouchGreedyViewPager extends ViewPager {
    private float xDistance, yDistance, lastX, lastY;

    public TouchGreedyViewPager(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) / 3; // favor X events
                lastX = curX;
                lastY = curY;
                if (xDistance > yDistance) {
                    return true;
                }
        }

        return super.onInterceptTouchEvent(ev);
    }
}

public class TouchHumbleScrollView extends ScrollView {
    private float xDistance, yDistance, lastX, lastY;

    public TouchHumbleScrollView(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) / 3; // favor X events
                lastX = curX;
                lastY = curY;
                if (xDistance > yDistance) {
                    return false;
                }
        }

        return super.onInterceptTouchEvent(ev);
    }
}

-1

You could override instantiateItem method in you PagerAdapter class by adding a scrollView to each page. Here is a code:

@Override
public Object instantiateItem(View collection, int position) {
    ScrollView sc = new ScrollView(cxt);
    sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    sc.setFillViewport(true);
    TextView tv = new TextView(cxt);
    tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    tv.setText(pages[position]);
    tv.setPadding(5,5,5,5);         
    sc.addView(tv);
    ((ViewPager) collection).addView(sc);

    return sc;
    }

Please sum up or quote the most important points right here. Link rot happens.
ЯegDwight
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.