133

Is there a way to make a ViewPager that does not scroll horizontally, but vertically?!

Daniel Nugent
  • 40,780
  • 13
  • 103
  • 126
user1709805
  • 1,988
  • 3
  • 17
  • 26
  • 1
    There is a new unofficial open source implementation of a vertical ViewPager: **Announcement:** https://plus.google.com/107777704046743444096/posts/1FTJfrvnY8w **Code:** https://github.com/LambergaR/VerticalViewPager/ – Vasily Sochinsky Jun 04 '13 at 14:13
  • There is a new implementation based on the 19 support library: https://github.com/castorflex/VerticalViewPager – Vasily Sochinsky Jan 06 '14 at 00:20
  • This is not the same as as https://github.com/castorflex/VerticalViewPager , despite having a similar name. – Flimm Aug 18 '16 at 13:19
  • Vertical view pager implementation by Antoine Merle: https://github.com/castorflex/VerticalViewPager – micnoy Jan 05 '14 at 16:20
  • 1
    There is control called [ViewPager2](https://developer.android.com/jetpack/androidx/releases/viewpager2) check here for demo https://stackoverflow.com/a/54643817/7666442 – AskNilesh Feb 21 '19 at 04:32

14 Answers14

230

You can use a ViewPager.PageTransformer to give the illusion of a vertical ViewPager. To achieve scrolling with a vertical instead of a horizontal drag you will have to override ViewPager's default touch events and swap the coordinates of MotionEvents prior to handling them, e.g.:

/**
 * Uses a combination of a PageTransformer and swapping X & Y coordinates
 * of touch events to create the illusion of a vertically scrolling ViewPager. 
 * 
 * Requires API 11+
 * 
 */
public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        super(context);
        init();
    }

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

    private void init() {
        // The majority of the magic happens here
        setPageTransformer(true, new VerticalPageTransformer());
        // The easiest way to get rid of the overscroll drawing that happens on the left and right
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    private class VerticalPageTransformer implements ViewPager.PageTransformer {

        @Override
        public void transformPage(View view, float position) {

            if (position < -1) { // [-Infinity,-1)
                // This page is way off-screen to the left.
                view.setAlpha(0);

            } else if (position <= 1) { // [-1,1]
                view.setAlpha(1);

                // Counteract the default slide transition
                view.setTranslationX(view.getWidth() * -position);

                //set Y position to swipe in from top
                float yPosition = position * view.getHeight();
                view.setTranslationY(yPosition);

            } else { // (1,+Infinity]
                // This page is way off-screen to the right.
                view.setAlpha(0);
            }
        }
    }

    /**
     * Swaps the X and Y coordinates of your touch event.
     */
    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        ev.setLocation(newX, newY);

        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev){
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev); // return touch coordinates to original reference frame for any child views
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapXY(ev));
    }

}

Of course you can tweak these settings as you see fit. Ends up looking like this:

VerticalViewPager demo

justasm
  • 739
  • 8
  • 11
Brett
  • 2,407
  • 1
  • 11
  • 5
  • 4
    @Der Golem how to use layout for this? – bShah Jun 16 '14 at 09:17
  • What is the exact question? By the way, I didn't post the answer, just added the referred image. – Phantômaxx Jun 16 '14 at 09:21
  • Wouldn't this example give trouble when width is not set to the parent width? – Matthias Van Parijs Sep 17 '14 at 15:28
  • Thanks. I got it to work using this. To show different things per page, do i just create a different layout and assign it? – kevinl Apr 15 '15 at 16:56
  • And you can use this as `` in your layout xml file. – Apurva Aug 17 '15 at 06:36
  • 1
    @Brett in this example I want to perform some action on swipe left and right. how to do that – Prabs Nov 18 '15 at 07:06
  • This answer is so much nicer than the other duplicate question I was looking at. I would upvote on the sample animation alone. Thank you – Script Kitty Jan 01 '16 at 02:40
  • I was doing something wrong when implementing this and it would unpredictably sometimes scroll and sometimes not. If anyone else has that issue, you can always return true on onInterceptTouchEvent, but I think you'll always let the parent handle it and sacrifice never being able to let the child views handle it. – Script Kitty Jan 01 '16 at 19:57
  • What if you want to add a negative page margin so you can see the adjacents pages? `adapter.pagemargin(-margin)` wouldn't work since it modifies the width: `public void setPageMargin(int marginPixels) { final int oldMargin = mPageMargin; mPageMargin = marginPixels; final int width = getWidth(); recomputeScrollPosition(width, width, marginPixels, oldMargin); requestLayout(); }` Trying to modify this leads to copy the `recomputeScrollPosition`method, which then leads to copy many other private methods since they are not accessible for overriding.. – Javier Mendonça Jan 05 '16 at 09:18
  • 1
    @Brett it works as expected, transition is taking vertically. But the problem is i have to swipe right/left to do transition and on swiping top/bottom no transition take place. – Karan Khurana Feb 12 '16 at 13:15
  • Getting an error with the following statement: boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); Error: Cannot resolve method onInterceptTouchEvent(android.View.MotionEvent) – webgenius Apr 21 '16 at 12:28
  • As no smooth fading in is done here, what's the point of setting alpha to 0 when the screen is offscreen? – Overclover Jun 04 '16 at 08:01
  • This should be the accepted answer – Hitesh Sahu Jul 13 '16 at 07:23
  • @Brett When I use your custom viewpager in another normal viewpager, the normal viewpager won't allow me to scroll to the right anymore (while scrolling to the left still works). Any idea how I could fix this? – Todd Sewell Dec 09 '16 at 21:46
  • It's work, I just wanted to modify animation how can I do that, I want to give https://developer.android.com/training/animation/screen-slide.html#DepthPageTransformer "DepthPageTransformer" Animation. how that is possible as it is also extends PageTransformer. – Hitesh Dhamshaniya Jan 05 '17 at 12:41
  • Perfect, except that `OffscreenPageLimit` doesn't seem to work if greater than 1... – Donkey Jan 12 '17 at 13:06
  • I have added 2 fragments to my adapter but it is showing only one. Unable to swipe down for the other. Why ? Can anyone plz help – Gaurav Arora Mar 06 '17 at 08:05
  • How would you stop scrolling after certain point? overriding `getPageWidth` doesn't work... – Daniil Orekhov May 31 '17 at 21:58
  • I can't understand the logic used in `transformPage()` method here, can somebody go through it step by step and explain it to me? May be in a separate answer? I have posted question here as well: https://stackoverflow.com/questions/50284470/need-a-detailed-explanation-of-the-transformpage-method – Hannan May 11 '18 at 03:37
  • 3
    @Brett I was using your solution. but now I am getting the swipping issue in andorid pie devices. Has any one facing same issue? – Jishant Jan 03 '19 at 10:38
  • @Ancee try this: https://stackoverflow.com/a/54896137/3115881 It isn't nice solution :( but works. – Pody01 Feb 27 '19 at 12:12
  • This is not smooth at all, especially on a taller device. also we need to scroll only in 90 degree, any deviation on that blocks the scrolls – droidev May 10 '19 at 05:19
  • pixel android 9 does not work – thecr0w Sep 04 '19 at 16:28
25

Vertical ViewPager

The other answers here all seemed to have some problems with them. Even the recommended open source projects weren't merging recent pull requests with bug fixes. Then I found a VerticalViewPager from Google that they used some DeskClock app. I trust using Google's implementation a lot more than the other answers floating around here. (Google's version is similar to the current top-voted answer, but more thorough.)

Full Example

This example is almost exactly the same as my Basic ViewPager Example, with a few minor changes to make use of the VerticalViewPager class.

enter image description here

XML

Add the xml layouts for the main activity and for each page (fragment). Note that we use VerticalViewPager rather than the standard ViewPager. I'll include the code for that below.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.verticalviewpager.MainActivity">

    <com.example.myapp.VerticalViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

fragment_one.xml

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

    <TextView
        android:id="@+id/textview"
        android:textSize="30sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>

Code

The code for the VerticalViewPager is not mine. It is just a stripped down version (without comments) from the Google source code. Check out that one for the most up to date version. The comments there are also helpful for understanding what is happening.

VerticalViewPager.java

import android.support.v4.view.ViewPager;    

public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        this(context, null);
    }

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

    @Override
    public boolean canScrollHorizontally(int direction) {
        return false;
    }

    @Override
    public boolean canScrollVertically(int direction) {
        return super.canScrollHorizontally(direction);
    }

    private void init() {
        setPageTransformer(true, new VerticalPageTransformer());
        setOverScrollMode(View.OVER_SCROLL_NEVER);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final boolean toIntercept = super.onInterceptTouchEvent(flipXY(ev));
        flipXY(ev);
        return toIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final boolean toHandle = super.onTouchEvent(flipXY(ev));
        flipXY(ev);
        return toHandle;
    }

    private MotionEvent flipXY(MotionEvent ev) {
        final float width = getWidth();
        final float height = getHeight();
        final float x = (ev.getY() / height) * width;
        final float y = (ev.getX() / width) * height;
        ev.setLocation(x, y);
        return ev;
    }

    private static final class VerticalPageTransformer implements ViewPager.PageTransformer {
        @Override
        public void transformPage(View view, float position) {
            final int pageWidth = view.getWidth();
            final int pageHeight = view.getHeight();
            if (position < -1) {
                view.setAlpha(0);
            } else if (position <= 1) {
                view.setAlpha(1);
                view.setTranslationX(pageWidth * -position);
                float yPosition = position * pageHeight;
                view.setTranslationY(yPosition);
            } else {
                view.setAlpha(0);
            }
        }
    }
}

MainActivity.java

I only swapped out VerticalViewPager for ViewPager.

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;

public class MainActivity extends AppCompatActivity {

    static final int NUMBER_OF_PAGES = 2;

    MyAdapter mAdapter;
    VerticalViewPager mPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAdapter = new MyAdapter(getSupportFragmentManager());
        mPager = findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public Fragment getItem(int position) {

            switch (position) {
                case 0:
                    return FragmentOne.newInstance(0, Color.WHITE);
                case 1:
                    // return a different Fragment class here
                    // if you want want a completely different layout
                    return FragmentOne.newInstance(1, Color.CYAN);
                default:
                    return null;
            }
        }
    }

    public static class FragmentOne extends Fragment {

        private static final String MY_NUM_KEY = "num";
        private static final String MY_COLOR_KEY = "color";

        private int mNum;
        private int mColor;

        // You can modify the parameters to pass in whatever you want
        static FragmentOne newInstance(int num, int color) {
            FragmentOne f = new FragmentOne();
            Bundle args = new Bundle();
            args.putInt(MY_NUM_KEY, num);
            args.putInt(MY_COLOR_KEY, color);
            f.setArguments(args);
            return f;
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mNum = getArguments() != null ? getArguments().getInt(MY_NUM_KEY) : 0;
            mColor = getArguments() != null ? getArguments().getInt(MY_COLOR_KEY) : Color.BLACK;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.fragment_one, container, false);
            v.setBackgroundColor(mColor);
            TextView textView = v.findViewById(R.id.textview);
            textView.setText("Page " + mNum);
            return v;
        }
    }
}

Finished

You should be able to run the app and see the result in the animation above.

See also

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
  • 3
    Code is woking fine,just need 1 small help.In current Code we need to swipe from bottom to top for getting next page,but i want even small swipe also should get the next page. – AMIT Mar 26 '19 at 09:21
  • @AMIT have you found solution for that? Have the same issue – Yupi Dec 14 '20 at 16:04
20

I have a solution that works for me in two steps.

  1. onInstantiateItem() of PagerAdapter, create the view and rotate it by -90:

    view.setRotation(-90f)
    

    If you are using FragmentPagerAdapter, then:

    objFragment.getView().setRotation(-90)
    
  2. Rotate ViewPager view by 90 degree:

    objViewPager.setRotation(90)
    

Works like a charm at least for my requirement.

Michael Celey
  • 12,087
  • 6
  • 53
  • 60
Kulai
  • 580
  • 1
  • 5
  • 12
  • 4
    this solution works, BUT you still have to swipe horizontally whereas the viewPager now scrolls vertically – leochab Jan 09 '14 at 12:48
  • @Kulai i am not clear what to do here can you please explain little bit as per my understanding we have public Object instantiateItem(ViewGroup container, int position) here we need to do container.setRotation(-90f); – varun Jan 29 '14 at 12:03
  • 1
    Doing so works like a charm! Althoug the view gets cut in the top and bottom part, and I can't figure out why :-/ – Nivaldo Bondança Apr 28 '14 at 16:55
  • 2
    It works properly for a square image and not for a rectangular. The dimensions go wrong when a rectangle is rotated. – Kulai May 09 '14 at 05:02
  • 2
    I'm not sure who up-voted this answer. this is not VerticalViewPager, this is ViewPager with 90 degree rotated, the gesture and the transition is just opposite. and it is not at all natural – droidev Jul 12 '19 at 03:19
14

For someone who is struggling with how to make vertical ViewPager works inside horizontal, just do following stuff:

Vertical ViewPager

public class VerticalViewPager extends ViewPager {
    public VerticalViewPager(Context context) {
        super(context);
        init();
    }

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

    private void init() {
        setPageTransformer(true, new VerticalPageTransformer());
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    private class VerticalPageTransformer implements ViewPager.PageTransformer {

        @Override
        public void transformPage(View view, float position) {

            if (position < -1) {
                view.setAlpha(0);
            } else if (position <= 1) {
                view.setAlpha(1);

                view.setTranslationX(view.getWidth() * -position);

                float yPosition = position * view.getHeight();
                view.setTranslationY(yPosition);
            } else {
                view.setAlpha(0);
            }
        }
    }

    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        ev.setLocation(newX, newY);

        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev);
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapXY(ev));
    }

}

Horizontal ViewPager

public class HorizontalViewPager extends ViewPager {
    private GestureDetector xScrollDetector;

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

    public HorizontalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        xScrollDetector = new GestureDetector(getContext(), new XScrollDetector());
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (xScrollDetector.onTouchEvent(ev)) {
            super.onInterceptTouchEvent(ev);
            return true;
        }

        return super.onInterceptTouchEvent(ev);
    }

}
Divers
  • 9,063
  • 7
  • 41
  • 87
8

A slight extension this answer helped me to achieve my requirement (Vertical View Pager to animate similar like in Inshorts - News in 60 words)

public class VerticalViewPager extends ViewPager {

public VerticalViewPager(Context context) {
    super(context);
    init();
}

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

private void init() {
    // The majority of the magic happens here
    setPageTransformer(true, new VerticalPageTransformer());
    // The easiest way to get rid of the overscroll drawing that happens on the left and right
    setOverScrollMode(OVER_SCROLL_NEVER);
}

private class VerticalPageTransformer implements ViewPager.PageTransformer {
 private static final float MIN_SCALE = 0.75f;
    @Override
    public void transformPage(View view, float position) {

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        }  else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            // Counteract the default slide transition
            view.setTranslationX(view.getWidth() * -position);

            //set Y position to swipe in from top
            float yPosition = position * view.getHeight();
            view.setTranslationY(yPosition);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // [0,1]
            view.setAlpha(1);

            // Counteract the default slide transition
            view.setTranslationX(view.getWidth() * -position);


            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }

    }

}

/**
 * Swaps the X and Y coordinates of your touch event.
 */
private MotionEvent swapXY(MotionEvent ev) {
    float width = getWidth();
    float height = getHeight();

    float newX = (ev.getY() / height) * width;
    float newY = (ev.getX() / width) * height;

    ev.setLocation(newX, newY);

    return ev;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
    boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
    swapXY(ev); // return touch coordinates to original reference frame for any child views
    return intercepted;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    return super.onTouchEvent(swapXY(ev));
}
}

I hope this might be helpful to someone else.

Community
  • 1
  • 1
Amrut Bidri
  • 5,898
  • 6
  • 31
  • 76
  • 1
    Ideal value for MIN_SCALE ? – divyenduz Dec 02 '15 at 10:52
  • Didn't you find problem of with swiping up fast the transition is not smooth – Praneeth Feb 26 '16 at 10:31
  • Why on earth would someone swipe up fast? – Amrut Bidri Feb 28 '16 at 09:16
  • Just a comment to clarify: This solution changes the animation in a way that the new page does not come from below but rather from behind the old page. So basically you don't have pages laying next to each other but rather a deck of pages, laying on top of each other. So, this solution might not be what the original question is trying to do. – Benjamin Basmaci May 13 '19 at 15:19
4

There are a few open source projects which claim to do this. Here they are:

These have been marked deprecated by their authors:

And one more thing:

Flimm
  • 97,949
  • 30
  • 201
  • 217
3

Check this out : https://github.com/JakeWharton/Android-DirectionalViewPager

Or the following question may help you: Vertical 'Gridview with pages' or 'Viewpager'

Community
  • 1
  • 1
sdabet
  • 17,379
  • 11
  • 73
  • 143
  • 2
    Something to keep in mind is that the `DirectionalViewPager` hasn't been updated for quite a while and hence is missing some of the newer features of the `ViewPager` in the support library; i.e. `setPageMargin(int)`. Generally it should do the job though. And if not, it's not too tricky to grab the source code for `ViewPager` and swap out all x/y and width/height logic. – MH. Nov 20 '12 at 18:29
  • I have already take into consideration the `DirectionalViewPager` but, as MH said, it is not updated (its developer consider it as DEPRECATED)!! It's incredible that Android developers has not provided to community a vertical `ViewPager`, but only horizantal. I'm newer to Android, develop my vertical `ViewPager` swapping out x/y is not so easy for me.. i'd prefer an already built solution.. anyway, thank you – user1709805 Nov 21 '12 at 08:13
  • 2
    hi, did you managed to do the vertical viewpager?? thansk – Paul Feb 14 '13 at 18:14
3

Just an improvement on the answers from @Brett and @Salman666 to correctly transform coordinates (X,Y) into (Y,X) since device displays are rectangular:

...

/**
 * Swaps the X and Y coordinates of your touch event
 */
@Override
public boolean onTouchEvent(MotionEvent ev) {
    return super.onTouchEvent(swapXY(ev));
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return super.onInterceptTouchEvent(swapXY(ev));
}

private MotionEvent swapXY(MotionEvent ev) {

    //Get display dimensions
    float displayWidth=this.getWidth();
    float displayHeight=this.getHeight();

    //Get current touch position
    float posX=ev.getX();
    float posY=ev.getY();

    //Transform (X,Y) into (Y,X) taking display dimensions into account
    float newPosX=(posY/displayHeight)*displayWidth;
    float newPosY=(1-posX/displayWidth)*displayHeight;

    //swap the x and y coords of the touch event
    ev.setLocation(newPosX, newPosY);

    return ev;
}

...

However, something still needs to be done to better the touchscreen responsiveness. The issue might be related to what @msdark commented on @Salman666's answer.

Community
  • 1
  • 1
Anonymous
  • 31
  • 1
  • I have some improvements by always returning `true` in `onInterceptTouchEvent` and I've also modified the Key bindings, so that using a DPAD it triggers scroll with UP/DOWN instead of left/right: https://gist.github.com/zevektor/fbacf4f4f6c39fd6e409 – Vektor88 Jul 08 '15 at 08:03
  • @Vektor88 always returning true will prevent inner views from receiving touch events .. best solution is as in brett answer .. – Mohammad Zekrallah Dec 08 '15 at 10:04
2
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        super(context);
        init();
    }


    public VerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    private void init() {

        setPageTransformer(true, new VerticalPageTransformer());

        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    private class VerticalPageTransformer implements PageTransformer {

        @Override
        public void transformPage(View view, float position) {
            int pageWidth = view.getWidth();
            int pageHeight = view.getHeight();

            if (position < -1) {

                view.setAlpha(0);

            } else if (position <= 1) {
                view.setAlpha(1);


                view.setTranslationX(pageWidth * -position);


                float yPosition = position * pageHeight;
                view.setTranslationY(yPosition);

            } else {

                view.setAlpha(0);
            }
        }
    }
        @Override
    public boolean onTouchEvent(MotionEvent ev) {

        ev.setLocation(ev.getY(), ev.getX());

        return super.onTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ev.setLocation(ev.getY(), ev.getX());
        return super.onInterceptTouchEvent(ev);
    }
}
Salman666
  • 81
  • 3
1

I ended up creating a new DirectionalViewPager that can scroll either vertically or horizontally because all of the solutions I've seen here have flaws:

  1. Existing VerticalViewPager implementations (by castorflex and LambergaR)

    • They are based on very old support library versions.
  2. Transformation trick with coordinate swapping

    • The overscroller is still shown from the left/right edges.
    • Page flinging doesn't work properly, because even with the coordinates swapped, VelocityTracker.computeCurrentVelocity still calculates the velocity with the X axis, probably because this internally uses a native call that ignores the coordinate swap.
  3. View rotation

    • Hack that needs every child view to be rotated too.
    • If you want to read coordinates for something else you have to swap the axis.
inorichi
  • 406
  • 7
  • 12
0

There's actually already one in the Android source. I've modified it to work better though:

package com.crnlgcs.sdk.widgets;



import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;

public class VerticalViewPager extends ViewPager {
    private static final String TAG = "VerticalViewPager";
    private static final boolean DEBUG = true;

    private float mLastMotionX;
    private float mLastMotionY;
    private float mTouchSlop;
    private boolean mVerticalDrag;
    private boolean mHorizontalDrag;

    // Vertical transit page transformer
    private final ViewPager.PageTransformer mPageTransformer = new ViewPager.PageTransformer() {
        @Override
        public void transformPage(View view, float position) {
            final int pageWidth = view.getWidth();
            final int pageHeight = view.getHeight();
            if (position < -1) {
                // This page is way off-screen to the left.
                view.setAlpha(0);
            } else if (position <= 1) {
                view.setAlpha(1);
                // Counteract the default slide transition
                view.setTranslationX(pageWidth * -position);
                // set Y position to swipe in from top
                float yPosition = position * pageHeight;
                view.setTranslationY(yPosition);
            } else {
                // This page is way off-screen to the right.
                view.setAlpha(0);
            }
        }
    };

    public VerticalViewPager(Context context) {
        super(context, null);
    }

    public VerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
        init();
    }

    private void init() {
        // Make page transit vertical
        setPageTransformer(true, mPageTransformer);
        // Get rid of the overscroll drawing that happens on the left and right (the ripple)
        setOverScrollMode(View.OVER_SCROLL_NEVER);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        final float x = ev.getX();
        final float y = ev.getY();

        if (DEBUG) Log.v(TAG, "onTouchEvent " + x + ", " + y);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLastMotionX = x;
                mLastMotionY = y;
                if (!super.onTouchEvent(ev))
                    return false;
                return verticalDrag(ev);
            }
            case MotionEvent.ACTION_MOVE: {
                final float xDiff = Math.abs(x - mLastMotionX);
                final float yDiff = Math.abs(y - mLastMotionY);
                if (!mHorizontalDrag && !mVerticalDrag) {
                    if (xDiff > mTouchSlop && xDiff > yDiff) { // Swiping left and right
                        mHorizontalDrag = true;
                    } else if (yDiff > mTouchSlop && yDiff > xDiff) { //Swiping up and down
                        mVerticalDrag = true;
                    }
                }
                if (mHorizontalDrag) {
                    return super.onTouchEvent(ev);
                } else if (mVerticalDrag) {
                    return verticalDrag(ev);
                }
            }
            case MotionEvent.ACTION_UP: {
                if (mHorizontalDrag) {
                    mHorizontalDrag = false;
                    return super.onTouchEvent(ev);
                }
                if (mVerticalDrag) {
                    mVerticalDrag = false;
                    return verticalDrag(ev);
                }
            }
        }
        // Set both flags to false in case user lifted finger in the parent view pager
        mHorizontalDrag = false;
        mVerticalDrag = false;

        return false;
    }


    private boolean verticalDrag(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        ev.setLocation(y, x);
        return super.onTouchEvent(ev);
    }
}
phreakhead
  • 13,409
  • 4
  • 36
  • 36
  • this is not working properly .. even the android source version doesn't.. its glitchy and only works for right/left gestures .. if you do up/down it doesn't scroll .. at least for me .. – Mohammad Zekrallah Feb 07 '16 at 08:26
0

This is A vertical scrollable ViewPager implementation.Works well with RecyclerView and ListView. guochong/VerticalViewPager.

use ViewPager.PageTransformer to make ViewPager scroll vertically, and also provide a View.OnTouchListener to deal with the scroll conflict.

/**
 * 1.dispatch ACTION_DOWN,ACTION_UP,ACTION_CANCEL to child<br>
 * 2.hack ACTION_MOVE
 *
 * @param v
 * @param e
 * @return
 */
@Override
public boolean onTouch(View v, MotionEvent e) {
    Log.i(TAG, "onTouchEvent " + ", action " + e.getAction() + ", e.rawY " + e.getRawY() + ",lastMotionY " + lastMotionY + ",downY " + downY);
    switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downY = e.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:

            if (downY == Float.MIN_VALUE && lastMotionY == Float.MIN_VALUE) {
                //not start from MOTION_DOWN, the child dispatch this first motion
                downY = e.getRawY();
                break;
            }

            float diff = e.getRawY() - (lastMotionY == Float.MIN_VALUE ? downY : lastMotionY);
            lastMotionY = e.getRawY();
            diff = diff / 2; //slow down viewpager scroll
            Log.e(TAG, "scrollX " + dummyViewPager.getScrollX() + ",basescrollX " + dummyViewPager.getBaseScrollX());

            if (dummyViewPager.getScrollX() != dummyViewPager.getBaseScrollX()) {
                if (fakeDragVp(v, e, diff)) return true;
            } else {
                if (ViewCompat.canScrollVertically(v, (-diff) > 0 ? 1 : -1)) {
                    Log.e(TAG, "scroll vertically  " + diff + ", move.lastMotionY " + e.getY());
                    break;
                } else {
                    dummyViewPager.beginFakeDrag();
                    fakeDragVp(v, e, diff);
                    adjustDownMotion(v, e);
                    return true;
                }
            }

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (dummyViewPager.isFakeDragging()) {
                try {
                    dummyViewPager.endFakeDrag();
                } catch (Exception e1) {
                    Log.e(TAG, "", e1);
                }
            }
            reset();
            break;
    }

    return false;
}
chad
  • 1
  • 2
  • Welcome to StackOverflow and thanks for your help. You might want to make your answer even better by adding some code. – Elias MP Sep 05 '17 at 11:45
0

Even I was facing the same problem with making ViewPager vertical by swapping X and Y. It was not at all smooth.

That's because it used to work only when the angle of swiping was less than 7.125 degrees = arctan (1/8) while intercept and 14 degrees = arctan (1/4) while touch; as original ViewPager horizontal one work when the angle of swiping is less than 26.565 degrees = arctan (1/2) while intercept and 45 degrees = arctan (1) while touch.

That's why I copied the code of Android support-core-ui and moved the values to variables, which I multiplied using reflection.

Please find the code and README at https://github.com/deepakmishra/verticalviewpager and VerticalViewPager at https://github.com/deepakmishra/verticalviewpager/blob/master/app/src/main/java/com/viewpager/VerticalViewPager.java

Deepak
  • 451
  • 4
  • 10
0

the better way is to rotate the viewpager by 90 degree, and use constraintLayout to adjust the location.

JackHui
  • 1
  • 3