16

So, I have this ScrollView with one child - a LinearLayout that has two children: a TextView and a ViewPager. ViewPager contains layout with many elements, that's why i need the ability to scroll vertically. Only pages in ViewPager may be scrolled horizontally (that is: I'd like to swipe horizontally only within the ViewPager). That one TextView must not scroll horizontally but should scroll together with my ViewPager.

Simple? No.

I've seen extremely similar issues at StackOverflow popping up (here, here and here and here). None of the suggested solutions work for me :(

What I see is this <- my sweet UI :), However I cannot scroll vertically :(

Embedding ScrollViews inside ViewPager is not an option - desing of the UI forbids this.

Maybe it's something with my programmatically filling each page in view pager? Hmmm...

Any suggestions would be appreciated.

My code:

activity_main.xml:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"

                android:id="@+id/scrollView"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:background="@android:color/white"
                android:fillViewport="true" >

                <LinearLayout
                    android:id="@+id/linearLayoutGeneral"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <TextView
                    android:id="@+id/tvText"
                    android:layout_width="fill_parent"
                    android:layout_height="200dp"
                    android:text="Test text" />

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


                </LinearLayout>
</ScrollView>

each page in ViewPager has this layout:

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

    <LinearLayout
        android:id="@+id/layoutData"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>

single element's layout in such a page:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="40dp"
    android:gravity="center"
    android:orientation="vertical"
    android:background="@android:color/white" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:background="@android:color/black"
        android:textColor="@android:color/white"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

A single page Fragment is also very simple:

public class DayFragment extends Fragment {

    private static final String TAG = DayFragment.class.getSimpleName();

    public String tag;

    LinearLayout data;

    View mView;

    final int ROWS_NUM = 60;

    public DayFragment() {

    }

    /**
     * (non-Javadoc)
     * 
     * @see android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
     *      android.view.ViewGroup, android.os.Bundle)
     */
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist. The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed. Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        mView = (LinearLayout) inflater.inflate(R.layout.day, container, false);

        setUpControls();

        generateData();

        String text = getArguments().getString("text");
        Log.d(TAG, "creating view with text: " + text);

        return mView;
    }

    private void setUpControls() {
        data = (LinearLayout) mView.findViewById(R.id.layoutData);
    }

    private void generateData() {
        for (int i = 0; i < ROWS_NUM; i++) {
            View v = createRow(i);
            data.addView(v);
        }
    }

    private View createRow(int num) {
        LayoutInflater inflater = (LayoutInflater) getActivity()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.row, null);

        TextView tv = (TextView) v.findViewById(R.id.textView);
        tv.setText("Data nr: " + num);

        return v;
    }

    public static DayFragment newInstance(String text) {
        Log.d(TAG, "newInstance with text: " + text);
        DayFragment f = new DayFragment();

        f.tag = text;

        // Supply text input as an argument.
        Bundle args = new Bundle();
        args.putString("text", text);
        f.setArguments(args);

        return f;
    }

}
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Genkaku
  • 191
  • 1
  • 2
  • 7

8 Answers8

29

I had the problem that ViewPager behaved weird and I found out the reason for that it is because sometimes the ScrollView regains focus and the ViewPager then loses focus. If you have a ViewPager in a ScrollView and you want it always to stay in focus when you touch it and the ScrollView never getting a focus, setting the requestDisallowInterceptTouchEvent does that. Did that help?

    mViewPager= (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(adapter);
    mViewPager.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Marius Hilarious
  • 855
  • 9
  • 18
  • In general, the ViewPager might not be a direct child of the ScrollView. I have found cases where it is better to call requestDisallowInterceptTouchEvent on the ScrollView instead of the parent. – Nick Snyder Mar 19 '13 at 23:35
  • 2
    I had Viewpager inserted as header of Listview, vertical scrolling was working fine. The problem was horizontal swiping of listview was not working. Hooked up this code to resolve that. Thanks! – Signcodeindie Feb 25 '14 at 07:42
  • Worked for me after lots of other solutions that didnt work from here, http://stackoverflow.com/questions/2646028/android-horizontalscrollview-within-scrollview-touch-handling and http://stackoverflow.com/questions/7381360/is-it-possible-to-have-a-viewpager-inside-of-a-scrollview – Joseph Mar 05 '15 at 11:39
  • i havparent layout as RelativeLayout->ScrollView->LinearLayout->LinearLayout (contains textview and imagevew)-> -----> ProgressBar->View->LinearLayout(contains tabs+pager)---->->-> – Erum Aug 25 '15 at 07:46
11

Extending on to Marius's answer to allow vertical scrolling on the parent:

I noticed that when you call the 'requestDisallowInterceptTouchEvent' method on the initial scroll, it will block scrolling vertically on the parent view when scrolling vertically on top of the view pager.

My solution was to only trigger that method after the user started scrolling horizontally for a specified distance (the 'margin' variable).

mViewPager.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent e) {
        // How far the user has to scroll before it locks the parent vertical scrolling.
        final int margin = 10;
        final int fragmentOffset = v.getScrollX() % v.getWidth();

        if (fragmentOffset > margin && fragmentOffset < v.getWidth() - margin) {
            mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }
});
elimirks
  • 1,452
  • 2
  • 17
  • 30
  • this is better, it means that you can still vertically scroll the page from a touch event that starts on the viewpager – Dean Wild Aug 28 '13 at 11:06
  • Also note that you may want to declare the margin variable in dp, as in the example it is in pixels. http://stackoverflow.com/questions/4605527/converting-pixels-to-dp-in-android – elimirks Aug 28 '13 at 13:24
  • it is only working when i have custom height of view pager.not for wrap and match parent, but contributing +1 to u. – Robi Kumar Tomar Jan 08 '15 at 13:53
  • Interesting... What is the output when you log "v.getScrollX()" and "v.getWidth()"? – elimirks Jan 08 '15 at 18:59
1
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;
    }
});
V-rund Puro-hit
  • 5,518
  • 9
  • 31
  • 50
Ravindra Kushwaha
  • 7,846
  • 14
  • 53
  • 103
1

I had same problem try my code to, you we should to disable vertical scroll and sent event to Scrollview :

private int dragThreshold = 10, int downX = 0, int downY = 0;

vPager.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP){
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                return false;
            }else if(event.getAction() == MotionEvent.ACTION_MOVE){
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                ExceptionHelpers.dLog("OnTouchListener", "distance X : "+distanceX+" , distance Y : "+distanceY);

                if(distanceY > distanceX && distanceY > dragThreshold){
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                    return true;
                }
            }
            v.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

Also you can set Min Horizontal Scroll By Add :

else {
      distanceX > distanceY && distanceX > dragThreshold)
}

And remember, Building workers do -> Try and Try and Try

developers (us) do -> Think And Think and Try

Good Lock

Hossein Kurd
  • 3,184
  • 3
  • 41
  • 71
1

Try this

    public  static class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return Math.abs(distanceY) < Math.abs(distanceX);
       }
   } 

and for the viewpager

    final GestureDetector mGestureDetector= new GestureDetector(this,new XScrollDetector());
    mViewPager.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            mScrollView.requestDisallowInterceptTouchEvent(mGestureDetector.onTouchEvent(event));
            return false;

        }
    });
zero323
  • 322,348
  • 103
  • 959
  • 935
user3024215
  • 196
  • 1
  • 7
0

I've tried a lot of things, but some things haven't worked for me and some are too complicated and have dirty codes.

I solved this ViewPager in ScrollView problem this way.
you can update the viewPager size in ViewPager.onMeasure() or ViewPager.OnPageChangeListener()

The method onMeasure() will be called everytime when the ui changes in fragment. But I had a lots of varying ui (e.g. TabButton), I updated the view pager size only when user scroll pages.

    mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            // will work when ViewPager shows first fragment.
            if (position != currentPosition) {
                currentPosition = position;
                updateViewPagerSize(position);
            }
        }

        @Override
        public void onPageSelected(int position) {

        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });

depending on the situations, you might have to give a focus something to make ScrollView works.

public void updateViewPagerSize(int position) {
    View view = mPager.getChildAt(position);
    view.measure(ViewPager.LayoutParams.WRAP_CONTENT, ViewPager.LayoutParams.WRAP_CONTENT);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, view.getMeasuredHeight());
    mPager.setLayoutParams(params);
    edtText.requestFocus(); //you might have to give a focus something to use scroll.
}
youngwoon
  • 363
  • 4
  • 14
-1

Horizontal and vertical scrolling in one Activity is normally a problem. The ViewPager handles this very well. I do not know if there is a good solution for your problem. But I would suggest to get rid of the ScrollView and TextView in your main layout.

Use the ViewPager as your starting point for your layout and put the vertical ScrollView inside of the ViewPager Fragments.

Konsumierer
  • 1,295
  • 1
  • 15
  • 33
  • Thank you for your input. I am aware that one can more easily embed ScrollView inside of the ViewPager, but this is the exact scenario I'm trying to avoid. – Genkaku Oct 01 '12 at 13:34
-1

Disallow the ScrollView to intercept touch events. This requires the user to touch outside the ViewPager to scroll up or down.

mScrollView = (ScrollView) findViewById(R.id.scroll_view); 
mViewPager= (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(adapter);
mViewPager.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mScrollView.requestDisallowInterceptTouchEvent(true);
        return false;
    }
});
Nick Snyder
  • 2,966
  • 1
  • 21
  • 23