46

After trying out the Gallery and Horizontal Scroll View, I found that the View Pager does what I need but with one minor thing missing. Can the View Pager have multiple views per page?

I know that View Pager shows only 1 view/page per swipe. I was wondering if I can limit my views width so my 2nd view following it will show?

For example: I have 3 views and I want the screen to show view 1 and part of view 2 so the user knows there is more content so they can swipe to view 2.

|view 1|view 2|view 3|
|screen   |
Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
CLDev
  • 1,076
  • 3
  • 12
  • 20
  • Have you tried doing a mockup to see if this works? Seems like it wouldn't take long to just code it and try it. – dymmeh Feb 27 '12 at 16:40
  • The View Pager itself limits the each view to the whole screen. The reason I made this post is to see if anyone has experience with the View Pager and if showing more than two views within the same screen is possible. – CLDev Feb 27 '12 at 18:10
  • 1
    use [this link](http://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html) , and override the method [onInterceptTouchEvent](http://developer.android.com/reference/android/support/v4/view/ViewPager.html#onInterceptTouchEvent%28android.view.MotionEvent%29) and return false there . – android developer Aug 28 '12 at 14:57
  • possible duplicate of [Multiple pages at the same time on a ViewPager](http://stackoverflow.com/questions/8836323/multiple-pages-at-the-same-time-on-a-viewpager) – Cassio Landim Apr 11 '14 at 14:49

9 Answers9

61

I discovered that a perhaps even simpler solution through specifying a negative margin for the ViewPager. I've created the MultiViewPager project on GitHub, which you may want to take a look at:

https://github.com/Pixplicity/MultiViewPager

Although MultiViewPager expects a child view for specifying the dimension, the principle revolves around setting the page margin:

ViewPager.setPageMargin(
    getResources().getDimensionPixelOffset(R.dimen.viewpager_margin));

I then specified this dimension in my dimens.xml:

<dimen name="viewpager_margin">-64dp</dimen>

To compensate for overlapping pages, each page's content view has the opposite margin:

android:layout_marginLeft="@dimen/viewpager_margin_fix"
android:layout_marginRight="@dimen/viewpager_margin_fix"

Again in dimens.xml:

<dimen name="viewpager_margin_fix">32dp</dimen>

(Note that the viewpager_margin_fix dimension is half that of the absolute viewpager_margin dimension.)

We implemented this in the Dutch newspaper app De Telegraaf Krant:

Phone example in De Telegraaf KrantTablet example

Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
  • 1
    I can't put it to work with your solution, can you post a example? thx – Marckaraujo Sep 06 '13 at 13:55
  • I can't get this to work either. Specifically, while the viewpager's margin appears to get set, compensating with android:layout_marginRight & Left in my FrameLayout doesn't appear to do anything. All I see is overlapping pages...Android Studio's layout preview shows that the margins should have been applied though. (note that I am using android-support-v4) – qix Oct 17 '13 at 09:43
  • Depending on how you've composed the layout inside each page, you may instead want to apply `android:paddingLeft` and `android:paddingRight`. Neighboring views can share margins and pages could overlap by up to half of the desired margin. – Paul Lammertsma Oct 31 '13 at 22:49
  • The above values does work in portrait mode and doesn't work in landscape mode! is there anyway to accomplish those values in landscape mode too? – iSun Jan 06 '14 at 20:44
  • There should be no reason that this doesn't work in landscape. Are you sure your values aren't being restricted by resource qualifiers? – Paul Lammertsma Jan 07 '14 at 08:40
  • @PaulLammertsma Uhmm, Yes I just used the above code and now I get different result on different device - e.g the image gap between my GS 3 and my NEXUS 7 is not same! – iSun Jan 07 '14 at 11:38
  • @iSun Note that you aren't specifying the *gap*; you're specifying the *overlap*. This dimension is provided as an absolute amount. If you wish to specify different amounts of overlap between device classes, you should do so using resource qualifiers. – Paul Lammertsma Jan 07 '14 at 12:04
  • @PaulLammertsma What exactly you mean by resource qualifiers? do you mean xhdpi, hddpi and other postfixes? – iSun Jan 07 '14 at 16:22
  • @PaulLammertsma Can we calculate this overlap values dynamically without using resource qualifiers? – iSun Jan 07 '14 at 16:31
  • @iSun Yes, those are some examples of qualifiers. If you want the amount of overlap to be determined based off the size of the pages, I'm afraid you'll need to override some behavior in ViewPager. I've done this myself, but I'm afraid I don't have the time to open source this at the moment. It's relatively easy to do: when the ViewPager measures its first child, simply subtract the child's width from the ViewPager's width and divide by two. Make sure to execute this only once (unless the adapter is replaced) before issuing `requestLayout()` or it may enter a layout loop. – Paul Lammertsma Jan 07 '14 at 23:45
  • 3
    Great technique but unfortunately the adapter's itemSelected(int position) method always fires with the position of the centered page even when I tap on a page either side of the centered page. – Oliver Pearmain Mar 04 '14 at 16:18
  • @HaggleLad You'll have to write the logic for switching pages yourself. The ViewPager is simply showing the fragment; it can't guess what you want it to do. A simple solution would be to intercept touch events to check if the tapped area belongs to the currently centered page. – Paul Lammertsma Mar 04 '14 at 16:20
  • @PaulLammertsma Can you please share your workaround about calculating the above values dynamically? you said you already done this on your project, Unfortunately I have a same issue like iSun. – Ali Sep 13 '14 at 22:17
  • @PaulLammertsma I'm really need your help, please explain more about `when the ViewPager measures its first child, simply subtract the child's width from the ViewPager's width and divide by two`. – Ali Sep 15 '14 at 01:48
  • @NullPointer I've created [this Gist with a simple implementation](https://gist.github.com/pflammertsma/754d55a11e4329baee77). I'll create a project with a basic example some time later this week. – Paul Lammertsma Sep 15 '14 at 10:53
  • @PaulLammertsma I'm really appreciate you for this great project, thank you very much :-) – Ali Sep 15 '14 at 19:44
  • 1
    @NullPointer I'm glad you like it! Feel free to share! – Paul Lammertsma Sep 15 '14 at 22:06
  • Hello @PaulLammertsma! Thanks for the great work! One problem only: when the animation for changing the page finishes, the page after that is abruptly created. (if I'm at page 0, I swipe to page 1, and page 2 is created at the end of the animation and put suddenly). Using PagerAdapter. How have you resolved that? Thanks! – Danail Apr 21 '15 at 15:14
  • @Danail You're observing what happens under the hood of the ViewPager. You can manually set the offpage limit on the ViewPager. I'm afraid there's no facility to calculate the number of pages needed automatically, but hey, pull requests are welcome! – Paul Lammertsma Apr 21 '15 at 15:29
  • Just found how to fix that, setOffscreenPageLimit(4) is good enough :) Thanks again! – Danail Apr 21 '15 at 15:32
  • @PaulLammertsma please check my question here : MultiViewPager+ZoomOutPageTransformer not working probably http://stackoverflow.com/q/30827063/1724749?sem=2 your answer is much appreciated – Mahdi Giveie Jun 14 '15 at 07:48
  • Thanks @PaulLammertsma. I have just one comment about your approach, the fixes "viewpager_margin_fix" doesn't worked for me. I don't why my margin's settings didnt worked, I tried to configure each fragment (page) in vain. I even tried to change the layout params of ViewPager but it changed unsatisfactorily all pages settings. Conclusion: I had to work with padding unitarily per page. Anyway, your answer helped a lot. – Agna JirKon Rx Sep 11 '15 at 15:01
  • @Trinity I encourage you to look at the GitHub project as it's likely more up to date. Please file an issue there so I can take a closer look. – Paul Lammertsma Sep 11 '15 at 15:42
32

Mark Murphy has an interesting blog post addressing precisely this problem. Although I ended up using my own solution in this thread, it's worthwhile looking at Dave Smith's code, which Mark references in the blog post:

https://gist.github.com/8cbe094bb7a783e37ad1/

Warning! Before you take this approach, beware of some very serious issues with this approach, mentioned both at the end of this post and in the comments below.

You'll end up with this:

Screenshot of Dave Smith's PagerContainer

It effectively works by wrapping a ViewPager into a subclass of FrameLayout, setting it to a specific size, and calling setClipChildren(false). This inhibits Android from clipping the views that exceed beyond the boundaries of the ViewPager, and visually accomplishes what you want.

In XML, it's very simple:

<com.example.pagercontainer.PagerContainer
    android:id="@+id/pager_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#CCC">
    <android.support.v4.view.ViewPager
        android:layout_width="150dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal" />
</com.example.pagercontainer.PagerContainer>

Add in a little code for handling touch events from outside of the ViewPager and invalidating the display when scrolling, and you're done.

That being said, and while this works great in general, I did notice that there is an edge-case that isn't solved with this fairly simple construction: when calling setCurrentPage() on the ViewPager. The only way I could find to resolve this was by subclassing ViewPager itself and having its invalidate() function also invalidate the PagerContainer.

Community
  • 1
  • 1
Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
  • @Pual Lammertsma : can you please tell me where to call invalidate() in activity containing viewpager .. also if you can share your viewpagers code that will be great. – Shruti Jun 06 '13 at 06:34
  • There are various problems with this technique, including touch events. I'm afraid I can't share the code as it's in a proprietary project, but would instead recommend you to go for [the other solution](http://stackoverflow.com/questions/9468581/android-can-view-pager-have-multiple-views-in-per-page/12286698#answer-15006910). – Paul Lammertsma Jun 06 '13 at 08:42
  • 1
    In my experience, the animations/response for this technique isn't very crisp. Not sure if something is getting redrawn an extra time or what. I don't believe I was using a very complicated content view either. This seems like such a simple view/component... disappointing Android doesn't have a native non-deprecated solution :( – loeschg Jan 25 '14 at 05:58
  • Hi @PaulLammertsma I used above solution & facing big problem with setCurrentItem(2) method. Can you please help me out to solve the issue. – TNR Jul 15 '14 at 12:26
  • Very nicely documented blog. Helped me out with certain variations I was looking for. @Paul Lammertsma thanks for redirecting to that blog. Really appreciated – Jimit Patel Dec 26 '16 at 11:31
  • I have setup click listeners for those big textviews there. I don't know why but in my case if I click on central view, it works fine. But if I click on left or right view (the partially hidden ones), click event of only right fires (in both the cases) unless I manually scroll and bring the left one to center. Why? @Paul – mehulmpt Mar 07 '17 at 11:09
  • Likely because this is a buggy and terrible solution. I suggest using the LinearSnapHelper or MultiViewPager. – Paul Lammertsma Mar 07 '17 at 14:00
  • mmm... why you do not use RecyclerView ? you can make it vertical or horizontal. – Abdalrhman Alkhulaqi Sep 27 '17 at 20:20
  • @AbdalrhmanAlkhulaqi The recommendation for LinearSnapHelper is for RecyclerView. MultiViewPager behaves a bit differently and is a viable alternative. Bear in mind this answer is from 2012 and quite a bit older than RecyclerView. – Paul Lammertsma Sep 27 '17 at 21:18
  • honestly I did not focus on history. Sorry! – Abdalrhman Alkhulaqi Sep 27 '17 at 22:01
18

It is possible to show more than one page on the same screen. One of the ways is by overriding the getPageWidth() method in the PAgerAdapter. getPageWidth() returns a float number between 0 and 1 indicating how much width of the Viewpager should the page occupy. By default it is set to 1. So, you can change this to the width you wish. You can read more about this here & github project.

andro
  • 977
  • 1
  • 10
  • 21
  • 6
    This works, but bear in mind that it won't center the pages inside your view; i.e. the left-most page will be flush against the left side of the ViewPager. – Paul Lammertsma Feb 18 '13 at 17:41
  • So, can we do it in the center? – gaurav414u May 26 '15 at 20:54
  • simple one line solve my problem.. In my case I want to show 3 items.. so pagewidth=0.33... sample code for future users @Override public float getPageWidth(int position) { return 0.33f; } – Ranjithkumar Oct 26 '16 at 15:05
17

This is how I got it:

<android.support.v4.view.ViewPager
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_gravity="center"
    android:layout_marginBottom="8dp"
    android:clipToPadding="false"
    android:gravity="center"
    android:paddingLeft="36dp"
    android:paddingRight="36dp"/>

and in activity,i use this :

markPager.setPageMargin(64);

hope it helps!

howerknea
  • 355
  • 3
  • 12
9

I had the same problem with the only difference that i needed to show 3 pages at once (previous, current and next pages). After a really long research for the best solution i think i found it. The solution is a mix of few of the answers here:

As @Paul Lammertsma's answer pointed out - Dave Smith's code in Mark Murphy's blog is the basis for the solution. The only problem for me was that the ViewPager was only on the top part of the screen due to the size they give it in the xml file:

 android:layout_width="150dp"
 android:layout_height="100dp"

Which wasn't good for my purpose since i was looking for something that will spread all over the screen. So i changed it to wrap the content as you can see here:

  <com.example.nutrino_assignment.PagerContainer
    android:id="@+id/pager_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#CCC">
    <android.support.v4.view.ViewPager
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
  </com.example.nutrino_assignment.PagerContainer>

Now I lost all the effect of what the tutorial was trying to do. Using @andro's answer i was able to show more then 1 page at a time: exactly 2! The current and the next. Did so by overriding as follow:

        @Override 
    public float getPageWidth(int position) {
        return(0.9f); 
    }

That was almost what i needed... (even though i think its enough for what you were asking), but for others who might need something like what i was needed: For the last part of the solution i used the idea in this answer, again by @Paul Lammertsma. In Dave Smith's code you will find in the onCreate method this line:

   //A little space between pages
    pager.setPageMargin(15);

which i replaced with:

   //A little space between pages
    pager.setPageMargin(-64);

now on the first page looks:

|view 1|view 2|view 3|
|screen   |

while on the 2nd it looks like:

|view 1|view 2|view 3|
     |screen    |

Hope it will help someone! I wasted like 2 days on it... Good luck.

Community
  • 1
  • 1
goldengil
  • 1,007
  • 9
  • 25
1
viewPager.setPageMargin(-18);// adjust accordingly ,-means less gap

in imageadapter

private class ImagePagerAdapter2 extends PagerAdapter {
    private int[] mImages = new int[] {

            R.drawable.add1,
            R.drawable.add3,
            R.drawable.add4,
            R.drawable.add2,
    };
    @Override
    public float getPageWidth(int position) {
        return .3f;
    }

adjust return value...lesser means more image......0.3 means atleast 3 images at a time.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • It's a good solution for more than one page at a time. but not complete. Better look at this post: https://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html. But the main problem is, It does not answers OP's question. – Arash Mar 09 '17 at 19:32
  • Now Its much better to replace ViewPager with RecyleView with horizental or vertical mode. – Raktim Bhattacharya Mar 10 '17 at 07:06
0
LayoutParams lp = new LayoutParams(width,height);
viewpager.setLayoutParams(lp);
IntelliJ Amiya
  • 74,896
  • 15
  • 165
  • 198
0

In xml file using this code(Main Activity)

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="130dp"
                    android:layout_marginLeft="5dp"
                    android:layout_marginRight="5dp"
                    android:orientation="vertical"
                    android:weightSum="1">
                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="130dp">
                        <com.wonderla.wonderla.muthootpathanamthitta.activity_muthootpathanm.PagerContainer
                            android:id="@+id/pager_container"
                            android:layout_width="match_parent"
                            android:layout_height="fill_parent">
                            <android.support.v4.view.ViewPager
                                android:id="@+id/viewpager"
                                android:layout_width="100dip"
                                android:layout_height="100dip"/>
                      </com.wonderla.wonderla.muthootpathanamthitta.activity_muthootpathanm.PagerContainer>
                    </RelativeLayout>
                </LinearLayout>
0

Main activity xml file add this code

      <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="130dp"
                    android:layout_marginLeft="5dp"
                    android:layout_marginRight="5dp"
                    android:orientation="vertical"
                    android:weightSum="1">
                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="130dp">
                        <com.wonderla.wonderla.muthootpathanamthitta.activity_muthootpathanm.PagerContainer
                            android:id="@+id/pager_container"
                            android:layout_width="match_parent"
                            android:layout_height="fill_parent">
                            <android.support.v4.view.ViewPager
                                android:id="@+id/viewpager"
                                android:layout_width="100dip"
                                android:layout_height="100dip"/>
                      </com.wonderla.wonderla.muthootpathanamthitta.activity_muthootpathanm.PagerContainer>
                    </RelativeLayout>
                </LinearLayout>

Main Activity code

public class MainActivity extends Activity{
 final Integer[] XMEN2= {R.mipmap.bookticket,R.mipmap.safty,R.mipmap.privacy};
 private ArrayList<Integer> XMENArray2 = new ArrayList<Integer>();
 PagerContainer mContainer;
 int currentPage2 = 0;
 private static int NUM_PAGES2 = 0;
 ViewPager mPager2;

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        initData2();}
 private void initViews() {
  mPager2 = (ViewPager)findViewById(R.id.viewpager);
  mContainer = (PagerContainer)findViewById(R.id.pager_container);
  mPager2.setOffscreenPageLimit(5);
  mPager2.setPageMargin(15);
  mPager2.setClipChildren(false);
 }

   private void initData2() {

        for(int i=0;i<XMEN2.length;i++)
            XMENArray2.add(XMEN2[i]);
        mPager2.setAdapter(new Sliding_Adaptertwo(getActivity(),XMENArray2));
        NUM_PAGES2 =XMEN2.length;
        final Handler handler = new Handler();
        final Runnable Update = new Runnable() {
            public void run() {
                if (currentPage2 == NUM_PAGES2) {
                    currentPage2= 0;
                }mPager2.setCurrentItem(currentPage2++, true);
            }
        };
        Timer swipeTimer = new Timer();
        swipeTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                handler.post(Update);
            }
        }, 3000, 3000);

    }



}

Pager View pagercontainer class


import android.content.Context;

import android.graphics.Point;

import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import android.widget.FrameLayout;


public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {

    private ViewPager mPager;
    boolean mNeedsRedraw = false;

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

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

    public PagerContainer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        //Disable clipping of children so non-selected pages are visible
        setClipChildren(false);

        //Child clipping doesn't work with hardware acceleration in Android 3.x/4.x
        //You need to set this value here if using hardware acceleration in an
        // application targeted at these releases.
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        try {
            mPager = (ViewPager) getChildAt(0);
            mPager.setOnPageChangeListener(this);
        } catch (Exception e) {
            throw new IllegalStateException("The root child of PagerContainer must be a ViewPager");
        }
    }

    public ViewPager getViewPager() {
        return mPager;
    }

    private Point mCenter = new Point();
    private Point mInitialTouch = new Point();

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mCenter.x = w / 2;
        mCenter.y = h / 2;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //We capture any touches not already handled by the ViewPager
        // to implement scrolling from a touch outside the pager bounds.
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mInitialTouch.x = (int)ev.getX();
                mInitialTouch.y = (int)ev.getY();
            default:
                ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
                break;
        }

        return mPager.dispatchTouchEvent(ev);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        //Force the container to redraw on scrolling.
        //Without this the outer pages render initially and then stay static
        if (mNeedsRedraw) invalidate();
    }

    @Override
    public void onPageSelected(int position) { }

    @Override
    public void onPageScrollStateChanged(int state) {
        mNeedsRedraw = (state != ViewPager.SCROLL_STATE_IDLE);
    }
}

and its Adapter

public class Sliding_Adaptertwo extends PagerAdapter {


    private ArrayList<Integer> IMAGES;
    private LayoutInflater inflater;
    private Context context;


    public Sliding_Adaptertwo(Context context, ArrayList<Integer> IMAGES) {
        this.context = context;
        this.IMAGES=IMAGES;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    @Override
    public int getCount() {
        return IMAGES.size();
    }

    @Override
    public Object instantiateItem(ViewGroup view, int position) {
        View imageLayout = inflater.inflate(R.layout.sliding_layout, view, false);

        assert imageLayout != null;
        final ImageView imageView = (ImageView) imageLayout
                .findViewById(R.id.image);


        imageView.setImageResource(IMAGES.get(position));

        view.addView(imageLayout, 0);

        return imageLayout;
    }

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

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

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


}

xml file of adapter class

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

    <ImageView
        android:id="@+id/image"
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:adjustViewBounds="true"
        android:layout_gravity="center"
        android:scaleType="fitXY"
        android:src="@drawable/ad1"
       />
</FrameLayout>

it works fine