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
Based on @wize 's answer, which I found helpful and elegant, I could achieve what I wanted partially, cause I wanted the cability to go back to the first Fragment once replaced. I achieved it bit modifying a bit his code.
This would be the FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter {
private final class CalendarPageListener implements
CalendarPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
.commit();
if (mFragmentAtPos0 instanceof FirstFragment){
mFragmentAtPos0 = NextFragment.newInstance(listener);
}else{ // Instance of NextFragment
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
notifyDataSetChanged();
}
}
CalendarPageListener listener = new CalendarPageListener();;
private Fragment mFragmentAtPos0;
private FragmentManager mFragmentManager;
public MyAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public int getItemPosition(Object object) {
if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
@Override
public Fragment getItem(int position) {
if (position == 0)
return Portada.newInstance();
if (position == 1) { // Position where you want to replace fragments
if (mFragmentAtPos0 == null) {
mFragmentAtPos0 = FirstFragment.newInstance(listener);
}
return mFragmentAtPos0;
}
if (position == 2)
return Clasificacion.newInstance();
if (position == 3)
return Informacion.newInstance();
return null;
}
}
public interface CalendarPageFragmentListener {
void onSwitchToNextFragment();
}
To perfom the replacement, simply define a static field, of the type CalendarPageFragmentListener
and initialized through the newInstance
methods of the corresponding fragments and call FirstFragment.pageListener.onSwitchToNextFragment()
or NextFragment.pageListener.onSwitchToNextFragment()
respictevely.
Here's my relatively simple solution to this problem. The keys to this solution are to use FragmentStatePagerAdapter
instead of FragmentPagerAdapter
as the former will remove unused fragments for you while the later still retains their instances. The second is the use of POSITION_NONE
in getItem(). I've used a simple List to keep track of my fragments. My requirement was to replace the entire list of fragments at once with a new list, but the below could be easily modified to replace individual fragments:
public class MyFragmentAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private List<String> tabTitleList = new ArrayList<String>();
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<Fragment> fragments, List<String> titles) {
fragmentList.clear();
tabTitleList.clear();
fragmentList.addAll(fragments);
tabTitleList.addAll(titles);
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
@Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
}
I doing something to similar to wize but in my answer yo can change between the two fragments whenever you want. And with the wize answer I have some problems when changing the orientation of the screen an things like that. This is the PagerAdapter looks like:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
private Map<Integer, String> mFragmentTags;
private boolean isNextFragment=false;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (isPager) {
mFragmentAtPos0 = new FirstPageFragment();
} else {
mFragmentAtPos0 = new NextFragment();
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
}
return obj;
}
public void onChange(boolean isNextFragment) {
if (mFragmentAtPos0 == null)
mFragmentAtPos0 = getFragment(0);
if (mFragmentAtPos0 != null)
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
if (!isNextFragment) {
mFragmentAtFlashcards = new FirstPageFragment();
} else {
mFragmentAtFlashcards = new NextFragment();
}
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}
The listener I implemented in the adapter container activity to put it to the fragment when attaching it, this is the activity:
public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {
//...
@Override
public void onChange(boolean isNextFragment) {
if (pagerAdapter != null)
pagerAdapter.onChange(isNextFragment);
}
//...
}
Then in the fragment putting the listener when attach an calling it:
public class FirstPageFragment extends Fragment{
private ChangeFragmentListener changeFragmentListener;
//...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
changeFragmentListener = ((PagerContainerActivity) activity);
}
@Override
public void onDetach() {
super.onDetach();
changeFragmentListener = null;
}
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}
And finally the listener:
public interface changeFragmentListener {
void onChange(boolean isNextFragment);
}
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 holds MapView
and I thought its costly. So, I am maintaining the FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
instead of Fragment
itself.
Here is my implementation.
public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
private SwitchFragListener mSwitchFragListener;
private Switch mToggle;
private int pagerAdapterPosChanged = POSITION_UNCHANGED;
private static final int TOGGLE_ENABLE_POS = 2;
public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
super(fm);
mToggle = toggle;
mSwitchFragListener = new SwitchFragListener();
mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mSwitchFragListener.onSwitchToNextFragment();
}
});
}
@Override
public Fragment getItem(int i) {
switch (i)
{
case TOGGLE_ENABLE_POS:
if(mToggle.isChecked())
{
return TabReplaceFragment.getInstance();
}else
{
return DemoTab2Fragment.getInstance(i);
}
default:
return DemoTabFragment.getInstance(i);
}
}
@Override
public int getCount() {
return 5;
}
@Override
public CharSequence getPageTitle(int position) {
return "Tab " + (position + 1);
}
@Override
public int getItemPosition(Object object) {
// This check make sures getItem() is called only for the required Fragment
if (object instanceof TabReplaceFragment
|| object instanceof DemoTab2Fragment)
return pagerAdapterPosChanged;
return POSITION_UNCHANGED;
}
/**
* Switch fragments Interface implementation
*/
private final class SwitchFragListener implements
SwitchFragInterface {
SwitchFragListener() {}
public void onSwitchToNextFragment() {
pagerAdapterPosChanged = POSITION_NONE;
notifyDataSetChanged();
}
}
/**
* Interface to switch frags
*/
private interface SwitchFragInterface{
void onSwitchToNextFragment();
}
}
Demo link here.. https://youtu.be/l_62uhKkLyM
For demo purpose, used 2 fragments TabReplaceFragment
and DemoTab2Fragment
at position two. In all the other cases I'm using DemoTabFragment
instances.
Explanation:
I'm passing Switch
from Activity to the DemoCollectionPagerAdapter
. Based on the state of this switch we will display correct fragment. When the switch check is changed, I'm calling the SwitchFragListener
's onSwitchToNextFragment
method, where I'm changing the value of pagerAdapterPosChanged
variable to POSITION_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.
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.
@Override
public void onSaveInstanceState(Bundle outState) {
if (null != mCalFragment) {
FragmentTransaction bt = getChildFragmentManager().beginTransaction();
bt.remove(mFragment);
bt.commit();
}
super.onSaveInstanceState(outState);
}
This is my way to achieve that.
First of all add Root_fragment
inside viewPager
tab in which you want to implement button click fragment
event. Example;
@Override
public Fragment getItem(int position) {
if(position==0)
return RootTabFragment.newInstance();
else
return SecondPagerFragment.newInstance();
}
First of all, RootTabFragment
should be include FragmentLayout
for fragment change.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
Then, inside RootTabFragment
onCreateView
, implement fragmentChange
for your FirstPagerFragment
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();
After that, implement onClick
event for your button inside FirstPagerFragment
and make fragment change like that again.
getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();
Hope this will help you guy.