简体   繁体   中英

Replacing ViewPager with Fragment - Then Navigating Back

I've got an activity which initially hosts a ViewPager, hooked up to a FragmentPagerAdapter.

When the user clicks on an item inside the ViewPager's child fragment, I'm using a FragmentTransaction to replace an empty container view with a new Fragment which I want to navigate to.

If I use addToBackStack() on the transaction, commit the transaction and then navigate back, I am not returned to the ViewPager's views (the initial layout).

If I don't use addToBackStack() on the transaction, commit the transaction and then navigate back, the application exits.

It seems apparent that the ViewPager is not added to the backstack (which is not that surprising as it isn't a fragment in itself).. But I would expect the default behaviour would be that the back press takes me back to that activities initial View (the ViewPager).

Based on what I've read, it seems that perhaps because a fragment transaction is taking place, the ViewPager or PagerAdapter loses track of which fragment should be on display.

I'm really confused with this, but I ended up creating a huge mess of code overriding the onBackPress and showing and hiding the viewpager views. I would've thought there is a simpler way to use default behaviours to perform the appropriate navigation.

tl;dr

A is a Viewpager hosting fragments. B is a new Fragment.

When I replace A with B, and then press back, I expect to navigate back to A, but that is not happening.

Any advice would be much appreciated.

Code:

MainActivity:

  @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

        headingLayout = (RelativeLayout) findViewById(R.id.headingLayout);
        headingLayout.setVisibility(View.GONE);

        // Set up the ViewPager, attaching the adapter and setting up a listener
        // for when the
        // user swipes between sections.
        mViewPager = (ViewPager) findViewById(R.id.pager);

        mViewPager.setPageMargin(8);

        /** Getting fragment manager */
        FragmentManager fm = getSupportFragmentManager();

        /** Instantiating FragmentPagerAdapter */
        MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);

        /** Setting the pagerAdapter to the pager object */
        mViewPager.setAdapter(pagerAdapter);
.
.
.
}

    public void onListItemClicked(Fragment fragment) {
        fromPlayer = false;
        InitiateTransaction(fragment, true);

    }


    public void InitiateTransaction(Fragment fragment, boolean addToBackStack) {

        invalidateOptionsMenu();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        ft.replace(R.id.fragmentContainer, fragment).addToBackStack(null)
                .commit();

    }

PagerAdapter:

package another.music.player;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import another.music.player.fragments.AlbumListFragment;
import another.music.player.fragments.ArtistListFragment;
import another.music.player.fragments.SongListFragment;

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    final int PAGE_COUNT = 3;

    /** Constructor of the class */
    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    /** This method will be invoked when a page is requested to create */
    @Override
    public Fragment getItem(int i) {
        switch (i) {
        case 0:
            ArtistListFragment artistListFragment = new ArtistListFragment();
            Bundle artistData = new Bundle();
            artistData.putInt("current_page", i + 1);
            artistListFragment.setArguments(artistData);
            return artistListFragment;

        case 1:
            AlbumListFragment albumListFragment = new AlbumListFragment();
            Bundle albumData = new Bundle();
            albumData.putInt("current_page", i + 1);
            albumData.putBoolean("showHeader", false);
            albumListFragment.setArguments(albumData);
            return albumListFragment;

        default:

            SongListFragment songListFragment = new SongListFragment();
            Bundle songData = new Bundle();
            songData.putInt("current_page", i + 1);
            songListFragment.setArguments(songData);
            return songListFragment;
        }
    }

    /** Returns the number of pages */
    @Override
    public int getCount() {
        return PAGE_COUNT;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
        case 0:
            return "Artists";

        case 1:
            return "Albums";

        default:
            return "Songs";
        }
    }
}

main xml (containing fragmentContainer & ViewPager):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/app_background_ics" >

    <RelativeLayout
        android:id="@+id/headingLayout"
        android:layout_width="match_parent"
        android:layout_height="56dp" >
    </RelativeLayout>

    <FrameLayout
        android:id="@+id/fragmentContainer"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/headingLayout" />

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

        <android.support.v4.view.PagerTabStrip
            android:id="@+id/pager_title_strip"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#33b5e5"
            android:paddingBottom="4dp"
            android:paddingTop="4dp"
            android:textColor="#fff" />
    </android.support.v4.view.ViewPager>

</RelativeLayout>

I also had this very same problem for a long time. The solution turns out to be very simple, and you don't need any hacks with the ViewPager Visibility. I is described in this other SO related question: Fragment in ViewPager not restored after popBackStack

However, to make it simple, all you need is to use getChildFragmentManager() in your ViewPager adapter, instead of getSupportFragmentManager(). So, instead of this:

    /** Getting fragment manager */
    FragmentManager fm = getSupportFragmentManager();

    /** Instantiating FragmentPagerAdapter */
    MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);

    /** Setting the pagerAdapter to the pager object */
    mViewPager.setAdapter(pagerAdapter);

You do this:

    /** Getting fragment manager */
    FragmentManager fm = getChildFragmentManager();

    /** Instantiating FragmentPagerAdapter */
    MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);

    /** Setting the pagerAdapter to the pager object */
    mViewPager.setAdapter(pagerAdapter);

UPDATE :
That's not the "Android way" and it results in bad user experience for the case of a listview. Instead, create a new activity.

For people searching for a simple solution to this problem, I'll just sum up what I did.

My architecture :

  • ViewPager in FragmentActivity (ActionBarActivity actually, for ActionBar support. But ActionBarActivity implements FragmentActivity).

  • 2 tabs :

    • FragmentContainer1 that extends Fragment.
    • FragmentContainer2 that extends Fragment.

For each FragmentContainer, we call getChildFragmentManager, in the onCreate method for example, and add the fragment we want to show in this container :

FragmentToShow fragment = new FragmentToShow();

getChildFragmentManager()
        .beginTransaction()
        .add(R.id.container, fragment)
        .commit();

We don't want our first fragment to be added to the backstack of the fragment container because we don't want to show the fragment container if we press the back button.

Then, if we want to replace FragmentToShow by another fragment in our FragmentToShow class (like with a listView) :

Fragment itemFragment = new ItemFragment();

getFragmentManager()
        .beginTransaction()
        .replace(R.id.container, itemFragment)
        .addToBackStack(null)
        .commit();

Here we retrieve the child fragment manager, and we add the itemFragment to the back stack.

So now we want, on pressing the back button, to go back to the listView (the FragmentToShow instance). Our activity (FragmentActivity) is the only one aware of the back button, so we have to override the method onBackPressed() in this activity :

@Override
public void onBackPressed() {

    // We retrieve the fragment manager of the activity
    FragmentManager frgmtManager = getSupportFragmentManager();

    // We retrieve the fragment container showed right now
    // The viewpager assigns tags to fragment automatically like this
    // mPager is our ViewPager instance
    Fragment fragment = frgmtManager.findFragmentByTag("android:switcher:" + mPager.getId() + ":" + mPager.getCurrentItem());

    // And thanks to the fragment container, we retrieve its child fragment manager
    // holding our fragment in the back stack
    FragmentManager childFragmentManager = fragment.getChildFragmentManager();

    // And here we go, if the back stack is empty, we let the back button doing its job
    // Otherwise, we show the last entry in the back stack (our FragmentToShow) 
    if(childFragmentManager.getBackStackEntryCount() == 0){
        super.onBackPressed();
    } else {
        childFragmentManager.popBackStack();
    }
}

Since we call getSupportFragmentManager in our activity, we just can call getFragmentManager in our child fragments. This will return a support FragmentManager instance.

And that's it! I'm not an expert, so if you have suggestions or remarks, feel free.

The only way I've found to achieve this is to do the following:

When navigating away from the viewPager, send the viewPager out of view using Visiblity.GONE. Add any fragment transactions to the backstack.

When returning to the viewPager screen (via a back press), override the onBackPressed. You can check to see how many fragments are in the backstack. If the viewPager was the first view before fragment transactions took place, then you can check to see if the fragment backstack entry count is 0.

fragmentManager.getBackStackEntryCount() == 0, there are no fragments in the backstack.

If that statement is true, then just bring the viewPager back into view using Visibility.VISIBLE.

If you are using a ViewPager containing Fragments that can start Activities , here is how you would properly navigate back to the position in the ViewPager , upon hitting back or navigating up from said Activity (Assumes you have Upward navigation declared for your Activities ).

First off you need to pass the current position of the ViewPager as an Extra in the Intent to start the new Activity .

Then you will pass back that position to the parent Activity doing this:

Intent upIntent = NavUtils.getParentActivityIntent(this);
upIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
upIntent.putExtra(FeedPagerActivity.EXTRA_POSITION, position);
NavUtils.navigateUpTo(this, upIntent);

Put that code within

onOptionsItemSelected(MenuItem item)

I think I've had a very similar problem.

It seems that FragmentManager s behave in somewhat hierarchical way. The way I've solved this problem, was to use the "main" FragmentManager from the activity, that hosts the container and the ViewPager, and not the one, that can be retrieved from fragments inside the ViewPager.

To do this, I've used:

this.getActivity().getSupportFragmentManager()

where this is an instance of Fragment.

Now if I navigate from an item in the ViewPager to some other fragment with "replace" transaction, upon returning I can see the ViewPager in the state that I've left.

More complete code sample looks like this:

FragmentManager fragmentManager =  MyCurrentFragment.this.getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();

transaction.replace(R.id.container, newFragment);
transaction.addToBackStack(null);

transaction.commit();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM