Can I use view pager with views (not with fragments)
Asked Answered
J

9

150

I am using ViewPager for swiping between Fragments, but can I use ViewPager to swipe between Views simple XML layout?

This is my page Adapter for the ViewPager which is used to swipe between Fragments:

import java.util.List;

import com.app.name.fragments.TipsFragment;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.view.ViewGroup;

public class PageAdapter extends FragmentPagerAdapter {

    /**
     *
     */
    List<Fragment> fragments;
    public PageAdapter(FragmentManager fm,List<Fragment> frags) {
        super(fm);
        fragments = frags;

    }

    @Override
    public Fragment getItem(int arg0) {
        // TODO Auto-generated method stub
        return TipsFragment.newInstance(0, 0);
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return 4;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        FragmentManager manager = ((Fragment) object).getFragmentManager();
        FragmentTransaction trans = manager.beginTransaction();
        trans.remove((Fragment) object);
        trans.commit();

        super.destroyItem(container, position, object);
    }

}

And this is my tip fragment:

public class TipsFragment extends Fragment
{
    public static TipsFragment newInstance(int image,int content)
    {
        TipsFragment fragment = new TipsFragment();
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.tip_layout, null);
        return view;
    }
}

How can I modify my code to work with Views instead of Fragment?

Johnajohnath answered 10/9, 2013 at 4:8 Comment(5)
also have a look at this example https://mcmap.net/q/160410/-correctly-implementing-pageradapter-in-androidIslas
Why no using fragments? What will we achieve or lose if we use or don't use fragments?Gradygrae
@Gradygrae Fragments => Complex LifeCycle => More Bugs => ChaosHautevienne
@HarshilPansare Yes, I went through all of these disasters after I asked this questions in February and I will not use fragments in my projects anymore. I had no choice but to clean all the fragments from the ViewPager on onDestroy thus on onResume activity there will be no need to retrieve all 3 fragments that may no longer available. Just wanted to mention one of the problems.Gradygrae
Cheers to fragments-free life!Hautevienne
C
101

You need to override these two methods rather than getItem():

@Override
public Object instantiateItem(ViewGroup collection, int position) {
    View v = layoutInflater.inflate(...);
    ...
    collection.addView(v,0);
    return v;
}

@Override
public void destroyItem(ViewGroup collection, int position, Object view) {
    collection.removeView((View) view);
}
Collyrium answered 10/9, 2013 at 4:16 Comment(11)
can u add the full code for fragment page adapter... because i over ride this methods as u said.... but... the app got crash...Johnajohnath
nice one.. help me a lot... i extended PageAdapter instead of FragmentPageAdapter........ now its work fine.....Johnajohnath
For a complete working example, check out the code found on this question: https://mcmap.net/q/57702/-why-pageradapter-notifydatasetchanged-is-not-updating-the-viewMufti
I can find nothing on that link that has to do with this answer.Blanchblancha
Out of curiosity, why addView is required. The fragment adapter just asked for it return the view.Hardbitten
you dont need to cast to ViewPager at all as you are dealing with the ViewGroup interfaceCarden
@AmitGupta you're required to add the view to the container because you may not be returning the view here. instantiateItem must return an object associated with that view, which can be a different object if you like; it's a key. Whatever you return, you just make sure that your implementation of isViewFromObject can match the two up, key to view. However, the most common implementation is to just return the view as the key as well. FragmentPageAdapter handles all the key-to-view stuff for you and thus just asks you to create a fragment instead.Rochelrochell
@Gradygrae Viewpager provides these two ways, you can choose any of them. Generally when there is only informative things and no user interaction you can use view else go with Fragments if there is so many user interactions and more code to manage.Collyrium
@BirajZalavadia this was working when the compile sdk version is 15, but when it is set to 23, i get classcast exception saying the container cannot be cast to fragment. Could you tell me what can i change to make it work?Inborn
@hemanthkumar you need to use latest(at least 23.x) supporting library/jar for viewpager.Collyrium
Please how to make the height of the viewPager is equal to the heighst item in Pager Adapter ?Prosthodontist
U
72

Use this example

You can use a single XML layout nesting the children views.

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

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

            <LinearLayout
                android:id="@+id/page_one"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
                        <TextView
                        android:text="PAGE ONE IN"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:textColor="#fff"
                        android:textSize="24dp"/>
            </LinearLayout>

            <LinearLayout
                android:id="@+id/page_two"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
                        <TextView
                        android:text="PAGE TWO IN"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:textColor="#fff"
                        android:textSize="24dp"/>
            </LinearLayout>

    </android.support.v4.view.ViewPager>
</LinearLayout>

BUT... you need handle this with an adapter also. Here we return the finded view ID without inflate any other layout.

class WizardPagerAdapter extends PagerAdapter {

    public Object instantiateItem(ViewGroup collection, int position) {

        int resId = 0;
        switch (position) {
        case 0:
            resId = R.id.page_one;
            break;
        case 1:
            resId = R.id.page_two;
            break;
        }
        return findViewById(resId);
    }

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

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == arg1;
    }

    @Override public void destroyItem(ViewGroup container, int position, Object object) {
        // No super
    }
}

// Set the ViewPager adapter

WizardPagerAdapter adapter = new WizardPagerAdapter();
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
Uvarovite answered 10/9, 2013 at 4:14 Comment(11)
hey frnd... the findViewById() method is not defined hereJohnajohnath
@user1672337 findViewById is accessible from Activity class. So make internal (non-static) class in your Actvitiy. Or you have to pass activity instance to adapterRecapture
my app doesn't have fragments, how can i start my activity everytime I swipe a page ?Pre
This doesn't appear to work if the ViewPager has more than 2 child views. For example, if I have three RelativeLayouts in my ViewPager and then try to swipe to the third page, the third page shows up as blank. Any idea what gives?Kelcy
@NathanWalters, I had the same issue today, have resolved it increasing OffscreenPageLimit property of ViewPager. Probably it worth to update an answer with this information.Spermicide
@Spermicide that worked perfectly, thank you so much! This should definitely be added to the answer.Kelcy
@Nathan Walters Beware of outofmemory error this solution could lead. If view pager have many pages and a big setOffscreenPageLimit value an exception could be thrown especially on low memory devices. I faced this on a typical guide viewpager with a several images. So i think a dynamic view creation/destroying or a FragmentPagerAdapter should are used in this case. developer.android.com/reference/android/support/v4/view/… also warns about thatQuita
Frankly nothing but this works for me. The OffscreenPageLimit trick that @Spermicide suggested is also a must do for this to work. thank you both.Catholicon
you can use return collection.findViewById(resId); If you dont want to pass an activity instanceSemipalatinsk
Please add this code @Override public void destroyItem(ViewGroup container, int position, Object object) {}Depreciatory
Here text color is given as white and parent has no background, so not able to see the output, thought it was not working. Just remove the text color you will see your view pager, it will workSouthdown
P
13

We have build a very simple subclass of the ViewPager that we use sometimes.

/**
 * View pager used for a finite, low number of pages, where there is no need for
 * optimization.
 */
public class StaticViewPager extends ViewPager {

    /**
     * Initialize the view.
     *
     * @param context
     *            The application context.
     */
    public StaticViewPager(final Context context) {
        super(context);
    }

    /**
     * Initialize the view.
     *
     * @param context
     *            The application context.
     * @param attrs
     *            The requested attributes.
     */
    public StaticViewPager(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Make sure all are loaded at once
        final int childrenCount = getChildCount();
        setOffscreenPageLimit(childrenCount - 1);

        // Attach the adapter
        setAdapter(new PagerAdapter() {

            @Override
            public Object instantiateItem(final ViewGroup container, final int position) {
                return container.getChildAt(position);
            }

            @Override
            public boolean isViewFromObject(final View arg0, final Object arg1) {
                return arg0 == arg1;

            }

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

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

}

This class does not need a adapter as it will load the views from the layout. In order to use it your projects, just use it instead of the android.support.v4.view.ViewPager.

All the fancy stuff will still work, but you do not need to be bothered with the adapters.

Pepsinogen answered 22/7, 2015 at 15:20 Comment(2)
This almost worked for me, but I had to change onAttachedToWindow() to onFinishInflate().Nitrometer
@Gradygrae It is easier without fragments and you write less code, which means that is easier to read and understand later on and less prone to bugsPepsinogen
S
12

Based on the previous answers, I made the following class to achieve that in a proper and clearest way (I hope):

public class MyViewPagerAdapter extends PagerAdapter {

    ArrayList<ViewGroup> views;
    LayoutInflater inflater;

    public MyViewPagerAdapter(ActionBarActivity ctx){
        inflater = LayoutInflater.from(ctx);
        //instantiate your views list
        views = new ArrayList<ViewGroup>(5);
    }

    /**
     * To be called by onStop
     * Clean the memory
     */
    public void release(){
     views.clear();
        views = null;
    }

    /**
     * Return the number of views available.
     */
    @Override
    public int getCount() {
        return 5;
    }

    /**
     * Create the page for the given position. The adapter is responsible
     * for adding the view to the container given here, although it only
     * must ensure this is done by the time it returns from
     * {@link #finishUpdate(ViewGroup)}.
     *
     * @param container The containing View in which the page will be shown.
     * @param position The page position to be instantiated.
     * @return Returns an Object representing the new page. This does not
     *         need to be a View, but can be some other container of
     *         the page.  ,container
     */
    public Object instantiateItem(ViewGroup container, int position) {
        ViewGroup currentView;
        Log.e("MyViewPagerAdapter", "instantiateItem for " + position);
        if(views.size()>position&&views.get(position) != null){
            Log.e("MyViewPagerAdapter",
                  "instantiateItem views.get(position) " +
                  views.get(position));
            currentView = views.get(position);
        }
        else{
            Log.e("MyViewPagerAdapter", "instantiateItem need to create the View");
            int rootLayout = R.layout.view_screen;
            currentView = (ViewGroup) inflater.inflate(rootLayout, container, false);

            ((TextView)currentView.findViewById(R.id.txvTitle)).setText("My Views " + position);
            ((TextView)currentView.findViewById(R.id.btnButton)).setText("Button");
            ((ImageView)currentView.findViewById(R.id.imvPicture)).setBackgroundColor(0xFF00FF00);
        }
        container.addView(currentView);
        return currentView;
    }

    /**
     * Remove a page for the given position. The adapter is responsible
     * for removing the view from its container, although it only must ensure
     * this is done by the time it returns from {@link #finishUpdate(ViewGroup)}.
     *
     * @param container The containing View from which the page will be removed.
     * @param position The page position to be removed.
     * @param object The same object that was returned by
     * {@link #instantiateItem(View, int)}.
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View)object);

    }

    /**
     * Determines whether a page View is associated with a specific key object
     * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is
     * required for a PagerAdapter to function properly.
     *
     * @param view   Page View to check for association with <code>object</code>
     * @param object Object to check for association with <code>view</code>
     * @return true if <code>view</code> is associated with the key object <code>object</code>
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view==((View)object);
    }
}

And you have to set it in your activity:

public class ActivityWithViewsPaged extends ActionBarActivity {

    /**
     * The page Adapter: Manage the list of views (in fact here, its fragments)
     * And send them to the ViewPager
     */
    private MyViewPagerAdapter pagerAdapter;

    /**
     * The ViewPager is a ViewGroup that manage the swipe from left
     * to right to left.
     * Like a listView with a gesture listener...
     */
    private ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_with_views);

        // Find the viewPager
        viewPager = (ViewPager) super.findViewById(R.id.viewpager);

        // Instantiate the PageAdapter
        pagerAdapter = new MyViewPagerAdapter(this);

        // Affectation de l'adapter au ViewPager
        viewPager.setAdapter(pagerAdapter);
        viewPager.setClipToPadding(false);
        viewPager.setPageMargin(12);

        // Add animation when the page are swiped
        // this instanciation only works with honeyComb and more
        // if you want it all version use AnimatorProxy of the nineoldAndroid lib
        //@see:https://mcmap.net/q/160411/-backwards-compatible-pagetransformer
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            viewPager.setPageTransformer(true, new PageTransformer());
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        pagerAdapter.release();
    }

Where the XML files are obvious view_screen.xml:

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

 <TextView
        android:id="@+id/txvTitle"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp"
        android:shadowColor="#FF00FF"
        android:shadowDx="10"
        android:shadowDy="10"
        android:shadowRadius="5"
        android:textSize="32dp"
        android:textStyle="italic"
        android:background="#FFFFF000"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFF00F0">
        <TextView
            android:id="@+id/txvLeft"
            android:layout_width="wrap_content"
            android:layout_gravity="left"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/txvRight"
            android:layout_width="wrap_content"
            android:layout_gravity="right"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"/>
    </LinearLayout>
    <Button
        android:id="@+id/btnButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
    <ImageView
        android:id="@+id/imvPicture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"/>
</LinearLayout>

And ActivtyMain has the following layout:

<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingLeft="24dp"
    android:paddingRight="24dp"
    android:id="@+id/viewpager"
    android:background="#FF00F0F0">
</android.support.v4.view.ViewPager>

Big thanks to Brian and Nicholas for your answer, I hope I add some clearest information and hightlight some good practices for this feature.

Smallsword answered 22/5, 2015 at 9:59 Comment(1)
Not very good, you are saving all the created views. It would be better if it only saved the visible views. (similar to convertview in listview)Grory
B
6

I would like to add my solution here. Given that you don't need to use fragments, you can still create a PagerAdapter which attaches views instead of fragments to the ViewPager.

Extend PagerAdapter instead of FragmentPagerAdapter

public class CustomPagerAdapter extends PagerAdapter {

  private Context context;

  public CustomPagerAdapter(Context context) {
    super();
    this.context = context;
  }


  @Override
  public Object instantiateItem(ViewGroup collection, int position) {
    LayoutInflater inflater = LayoutInflater.from(context);
    View view = null;
    switch (position){
      case 0:
        view = MemoryView.getView(context, collection);
        break;
      case 1:
        view = NetworkView.getView(context, collection);
        break;
      case 2:
        view = CpuView.getView(context, collection);
        break;
    }

    collection.addView(view);
    return view;
  }

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

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

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

Now you need to define three classes which will return the views to be inflated in the viewpager. Similar to CpuView you will have MemoryView and NetworkView classes. Each of them will inflate their respective layouts.

public class CpuView {

public static View getView(Context context, ViewGroup collection) {

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context
        .LAYOUT_INFLATER_SERVICE);
    return inflater.inflate(R.layout.debugger_cpu_layout, collection, false);
  }
}

And finally a layout which will be inflated in each of the views

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000"
        android:text="CPU"/>
</LinearLayout>

P.S.: The reason I wrote this answer is because all the solutions provided here seems to be working fine, but they are inflating the layouts in the PagerAdapter class itself. For large projects it becomes difficult to maintain if their is a lot of code related to the layouts inflated. Now in this example all the views have separate classes and separate layouts. So the project can be easily maintained.

Barter answered 13/2, 2016 at 15:48 Comment(1)
IMO, activities are easier to useUproar
S
4

I'd like to elaborate on @Nicholas answer, you can get the views by id or if they're dynamically added just get the view directly given its position

class WizardPagerAdapter extends PagerAdapter {

    public Object instantiateItem(View collection, int position) {

        View v = pager.getChildAt(position);

        return v;
    }

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

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == ((View) arg1);
    }
}
Siddra answered 22/8, 2014 at 6:53 Comment(0)
A
4

If you use ViewPager2, according to the documentation, you can use a standard RecyclerView.Adapter:

If you're planning to use Fragments as pages, implement FragmentStateAdapter. If your pages are Views, implement RecyclerView.Adapter as usual.

https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2#setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter)

Alegar answered 28/5, 2021 at 13:25 Comment(0)
U
0

yes...you can use View instead of Fragment in viewpager. Here you can Find Whole example that will help you to achieve Viewpager without Fragment. Go through this documentation.

https://www.bignerdranch.com/blog/viewpager-without-fragments/

Uda answered 24/8, 2018 at 13:20 Comment(0)
L
0

In ViewPager2 you do not need to use Fragments at all. Just create an Adapter as you would a RecyclerView Adapter. and set it to ViewPager2.

One thing to keep in mind however is layout height of the view you want to inflate should be match_parent and not wrap_content as you would for a recyclerView.

The Snap behavior is also the same.

Lambert answered 13/9, 2022 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.