Back to august 2010, Rich Hyndman posts an entry on the android developers’ blog entitled “Horizontal View Swiping with ViewPager”. I already was really enthusiastic about the Android Support Package at that time because of the introduction of the LoaderManager which save us big headaches on the proper cursor management (but that’s another story). In an application of mine, I’ve used an onFling/swipe gesture based on some 2009 codes found on the Web (codeshogun.com and/or ceveni.com for example).
The main drawbacks on my onFling approach and implementation are:
That is based on a threshold velocity
Once initiated, the animation goes on and can’t be reversed
The gesture is the trigger of a startActivity call and each of those activities are… activities!
Surely my onFling implementation was too dumb and since I read Rich’s post, I have the secret personal project to use the ViewPager and one of the underlying adapters to reimplement that ugly onFling part of our application.
Well, the time has come with the Christmas holidays!
The point is that my onFling-based activity is now composed by two fragment and the FragmentPagerAdapter is nice but manage only one Fragment as a ViewPager current page content.
Now, what if a want a vertical stack of several fragments? ⇒ Read and adapt the FragmentPagerAdapter source code from the Support Package (compact version without comments):
publicabstractclassVerticalFragmentsPagerAdapterextendsPagerAdapter{privatestaticfinalStringTAG="VerticalFragmentsPagerAdapter";privatestaticfinalbooleanDEBUG=false;privatefinalFragmentManagermFragmentManager;privatefinalintmRows;privateFragmentTransactionmCurTransaction=null;privateArrayList<Fragment>mCurrentPrimaryItem=null;publicVerticalFragmentsPagerAdapter(FragmentManagerfm,introws){mFragmentManager=fm;mRows=rows;mCurrentPrimaryItem=newArrayList<Fragment>(mRows);}/** * Return the Fragments associated with a specified position. */publicabstractArrayList<Fragment>getItem(intposition);@OverridepublicvoidstartUpdate(ViewGroupcontainer){}@OverridepublicObjectinstantiateItem(ViewGroupcontainer,intposition){if(mCurTransaction==null){mCurTransaction=mFragmentManager.beginTransaction();}Contextctx=container.getContext();LayoutInflaterinflater=(LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);Viewlayout=inflater.inflate(R.layout.pager,null);layout.setId(6109+position);LinearLayoutll=(LinearLayout)layout.findViewById(R.id.pager_linear_layout);ll.setId(900913+position);((ViewPager)container).addView(layout);FragmentfirstFragment=mFragmentManager.findFragmentByTag(makeFragmentName(container.getId(),position,0));ArrayList<Fragment>frags=null;if(firstFragment!=null){frags=newArrayList<Fragment>(mRows);for(inti=0;i<mRows;++i){frags.add(mFragmentManager.findFragmentByTag(makeFragmentName(container.getId(),position,i)));}for(Fragmentf:frags){if(DEBUG)Log.v(TAG,"Attaching item #"+position+": f="+f);mCurTransaction.attach(f);}}else{frags=getItem(position);intcurrentRow=0;for(Fragmentf:frags){if(DEBUG)Log.v(TAG,"instanciateItem / Adding item #"+position+": f= "+f);mCurTransaction.add(ll.getId(),f,makeFragmentName(container.getId(),position,currentRow));currentRow++;}}if(frags!=mCurrentPrimaryItem){for(Fragmentf:frags){f.setMenuVisibility(false);f.setUserVisibleHint(false);}}returnlayout;}@OverridepublicvoiddestroyItem(ViewGroupcontainer,intposition,Objectobject){if(mCurTransaction==null){mCurTransaction=mFragmentManager.beginTransaction();}for(inti=0;i<mRows;++i){if(DEBUG)Log.v(TAG,"destroyItem / Detaching item #"+position+": row="+i);mCurTransaction.detach(mFragmentManager.findFragmentByTag(makeFragmentName(container.getId(),position,i)));}((ViewPager)container).removeView((View)object);}@OverridepublicvoidsetPrimaryItem(ViewGroupcontainer,intposition,Objectobject){ArrayList<Fragment>frags=newArrayList<Fragment>(mRows);for(inti=0;i<mRows;++i){frags.add(mFragmentManager.findFragmentByTag(makeFragmentName(container.getId(),position,i)));}if(!frags.equals(mCurrentPrimaryItem)){if(!mCurrentPrimaryItem.isEmpty()&&(mCurrentPrimaryItem.get(0)!=null)){for(Fragmentf:mCurrentPrimaryItem){f.setMenuVisibility(false);f.setUserVisibleHint(false);}}if(!frags.isEmpty()&&(frags.get(0)!=null)){for(Fragmentf:frags){f.setMenuVisibility(true);f.setUserVisibleHint(true);}}mCurrentPrimaryItem=(ArrayList<Fragment>)frags.clone();if(DEBUG)Log.v(TAG,"setPrimaryItem #"+position+": "+mCurrentPrimaryItem);}}@OverridepublicvoidfinishUpdate(ViewGroupcontainer){if(mCurTransaction!=null){mCurTransaction.commitAllowingStateLoss();mCurTransaction=null;mFragmentManager.executePendingTransactions();}}@OverridepublicbooleanisViewFromObject(Viewview,Objectobject){returnview==((View)object);}@OverridepublicParcelablesaveState(){returnnull;}@OverridepublicvoidrestoreState(Parcelablestate,ClassLoaderloader){}publicstaticStringmakeFragmentName(intviewId,intindex,intsubindex){return"android:switcher:"+viewId+":"+index+":"+subindex;}}
This implementation uses ArrayList as the fragments’ container. The trick here is that instead of returning a fragment from instanciateItem, I return a containing a LinearLayout.
Be warned: don’t use several instances of the same fragment for a given position! That adapter is not designed to do that…
From the above code, it’s now trivial to figure out how to extends that adapter: “Uh… the getItem should return an ArrayList of Fragments?” − “Yup!”
A trivial example is available on github. Just clone and run ant debug && ant installd… Feel free to fork, improve and return your feedback.