I'm trying to use Fragment with a ViewPager
using the FragmentPagerAdapter
.
What I'm looking for to achieve is to replace a fragment, positioned on the first page of the ViewPager
, with another one.
The pager is composed of two pages. The first one is the FirstPagerFragment
, the second one is the SecondPagerFragment
. Clicking on a button of the first page. I'd like to replace the FirstPagerFragment
with the NextFragment.
There is my code below.
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
...and here the xml files
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
Now the problem... which ID should I use in
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
If I use R.id.first_fragment_root_id
, the replacement works, but Hierarchy Viewer shows a strange behavior, as below.
At the beginning the situation is
after the replacement the situation is
As you can see there is something wrong, I expect to find the same state shown as in the first picture after I replace the fragment.
I found simple solution, which works fine even if you want add new fragments in the middle or replace current fragment. In my solution you should override
getItemId()
which should return unique id for each fragment. Not position as by default.There is it:
Notice: In this example
FirstFragment
andSecondFragment
extends abstract class PageFragment, which has methodgetPage()
.I have implemented a solution for:
The tricks to achieve this are the following:
The adapter code is the following:
The very first time you add all tabs, we need to call the method createHistory(), to create the initial history
Every time you want to replace a fragment to a specific tab you call: replace(final int position, final Class fragmentClass, final Bundle args)
On back pressed you need to call the back() method:
The solution works with sherlock action bar and with swipe gesture.
There is another solution that does not need modifying source code of
ViewPager
andFragmentStatePagerAdapter
, and it works with theFragmentPagerAdapter
base class used by the author.I'd like to start by answering the author's question about which ID he should use; it is ID of the container, i.e. ID of the view pager itself. However, as you probably noticed yourself, using that ID in your code causes nothing to happen. I will explain why:
First of all, to make
ViewPager
repopulate the pages, you need to callnotifyDataSetChanged()
that resides in the base class of your adapter.Second,
ViewPager
uses thegetItemPosition()
abstract method to check which pages should be destroyed and which should be kept. The default implementation of this function always returnsPOSITION_UNCHANGED
, which causesViewPager
to keep all current pages, and consequently not attaching your new page. Thus, to make fragment replacement work,getItemPosition()
needs to be overridden in your adapter and must returnPOSITION_NONE
when called with an old, to be hidden, fragment as argument.This also means that your adapter always needs to be aware of which fragment that should be displayed in position 0,
FirstPageFragment
orNextFragment
. One way of doing this is supplying a listener when creatingFirstPageFragment
, which will be called when it is time to switch fragments. I think this is a good thing though, to let your fragment adapter handle all fragment switches and calls toViewPager
andFragmentManager
.Third,
FragmentPagerAdapter
caches the used fragments by a name which is derived from the position, so if there was a fragment at position 0, it will not be replaced even though the class is new. There are two solutions, but the simplest is to use theremove()
function ofFragmentTransaction
, which will remove its tag as well.That was a lot of text, here is code that should work in your case:
Hope this helps anyone!
I followed the answers by @wize and @mdelolmo and I got the solution. Thanks Tons. But, I tuned these solutions a little bit to improve the memory consumption.
Problems I observed:
They save the instance of
Fragment
which is replaced. In my case, it is a Fragment which holdsMapView
and I thought its costly. So, I am maintaining theFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
instead ofFragment
itself.Here is my implementation.
Demo link here.. https://youtu.be/l_62uhKkLyM
For demo purpose, used 2 fragments
TabReplaceFragment
andDemoTab2Fragment
at position two. In all the other cases I'm usingDemoTabFragment
instances.Explanation:
I'm passing
Switch
from Activity to theDemoCollectionPagerAdapter
. Based on the state of this switch we will display correct fragment. When the switch check is changed, I'm calling theSwitchFragListener
'sonSwitchToNextFragment
method, where I'm changing the value ofpagerAdapterPosChanged
variable toPOSITION_NONE
. Check out more about POSITION_NONE. This will invalidate the getItem and I have logics to instantiate the right fragment over there. Sorry, if the explanation is a bit messy.Once again big thanks to @wize and @mdelolmo for the original idea.
Hope this is helpful. :)
Let me know if this implementation has any flaws. That will be greatly helpful for my project.
To replace a fragment inside a
ViewPager
you can move source codes ofViewPager
,PagerAdapter
andFragmentStatePagerAdapter
classes into your project and add following code.into
ViewPager
:into FragmentStatePagerAdapter:
handleGetItemInvalidated()
ensures that after next call ofgetItem()
it return newFragmentgetFragmentPosition()
returns position of the fragment in your adapter.Now, to replace fragments call
If you interested in an example project ask me for the sources.
after research i found solution with short code. first of all create a public instance on fragment and just remove your fragment on onSaveInstanceState if fragment not recreating on orientation change.