Reputation: 729
I've been stuck on this problem for a few days and it's driving me crazy. It seems like what I'm trying to do would be a pretty common use case but I can't for the life of me figure it out.
So, the problem: I want to replace fragments within a ViewPager and maintain the normal operation of the backstack.
My ViewPager contains two fragments that display ListViews. When in the first position of the ViewPager I want to be able to drill down into the ListView and be able to back out with the back button like so:
Artists -> Albums -> Songs
Artists <- Albums <- Songs
Each of these elements is implemented as a separate fragment containing it's own ListView.
When the user swipes to the right I want to display the current playlist in a separate fragment using the ViewPager.
So a typical flow could be:
Artists -> Albums -> Swipe Right to Playlist fragment
-> Playlist -> Swipe Left to return
-> Albums
After much experimentation I've managed come up with this solution which does replace the Artists/Albums fragments, however it's not possible to back out of the fragments using the back button:
public interface ItemSelectedListener {
void onItemSelected(Item item);
}
public class ListPagerAdapter extends FragmentPagerAdapter {
private static final String TAG = RMPDApplication.APP_PREFIX + "ListPagerAdapter";
SearchView searchView;
FragmentManager fragmentManager;
AbstractListFragment firstFragment;
PlaylistFragment playlistFragment;
public ListPagerAdapter(SearchView searchView, FragmentManager fm) {
super(fm);
this.searchView = searchView;
fragmentManager = fm;
}
class FragmentChanger implements ItemSelectedListener {
@Override
public void onItemSelected(Item item) {
AbstractListFragment newFragment;
if (item instanceof Artist) {
newFragment = new ArtistAlbumsListFragment(searchView, (Artist) item, new FragmentChanger());
} else {
newFragment = new SongListFragment(searchView, (Album) item);
}
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.remove(firstFragment);
ft.commit();
firstFragment = newFragment;
notifyDataSetChanged();
}
}
@Override
public Fragment getItem(int position) {
if (position == 1) {
Log.i(TAG, "Returning playlistFragment");
if (playlistFragment == null) {
playlistFragment = new PlaylistFragment(searchView);
}
return playlistFragment;
} else {
Log.i(TAG, "Returning firstFragment");
if(firstFragment == null) {
firstFragment = new ArtistListFragment(new FragmentChanger(), searchView);
}
return firstFragment;
}
}
@Override
public int getCount() {
return 2;
}
@Override
public int getItemPosition(Object object) {
Log.i(TAG, "getItemPosition: " + object.getClass().getSimpleName());
if(object instanceof PlaylistFragment) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
}
I pass the FragmentChanger
to each of the fragments and then in onItemClick()
I call it's only method:
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Artist artist = (Artist) adapter.getItem(position);
itemSelectedListener.onItemSelected(artist);
}
This almost works, clicking an item replaces the fragment with the desired one, but I have no way to return to the previous fragment. If anyone can help me figure this out I would be very grateful.
Edit:
Ok I've got it working with J.Romero's suggestion. In the end I implemented all the forward/back functionality in a single BrowserFragment
and overrode onBackPressed()
to handle the back button. I'll add the relevant code in case it helps anyone else.
Here is my ViewPager:
public class ListPagerAdapter extends FragmentPagerAdapter {
SearchView searchView;
BrowserFragment browserFragment;
PlaylistFragment playlistFragment;
public ListPagerAdapter(SearchView searchView, FragmentManager fm) {
super(fm);
this.searchView = searchView;
}
public boolean browserFragmentCanGoBack() {
return browserFragment.canGoBack();
}
public void browserFragmentBack() {
browserFragment.goBack();
}
@Override
public Fragment getItem(int position) {
if (position == 1) {
if (playlistFragment == null) {
playlistFragment = new PlaylistFragment(searchView);
}
return playlistFragment;
} else {
if (browserFragment == null) {
browserFragment = new BrowserFragment(searchView);
}
return browserFragment;
}
}
@Override
public int getCount() {
return 2;
}
}
Then in the hosting activity I override onBackPressed()
like this:
@Override
public void onBackPressed() {
if (viewPager.getCurrentItem() == 0) {
if (pagerAdapter.browserFragmentCanGoBack()) {
pagerAdapter.browserFragmentBack();
} else {
super.onBackPressed();
}
} else {
viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
}
}
And it works!
Upvotes: 0
Views: 386
Reputation: 4868
You can do you own managing of "backstack" by overriding the onBackPressed
method in your activity and keeping track of which level the user is currently on and displaying or going back and activity as appropriate.
@Override
public void onBackPressed(){
if (mLevel > 1) {
// in your case probably change the adapter item for the page
mViewPagerAdapter.notifyDataSetChanged()
}
...
}
Upvotes: 2