What I want.
In a tab sliding menu context, I want to replace a fragment to another inside a tab, and maintaining the tab menu, and also the current tab
You can use a ViewPager
together with a TabLayout
.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Then create a class that extends FragmentStatePagerAdapter
to initialize the ViewPager
with your initial Fragment
s:
public class CustomPagerAdapter extends FragmentStatePagerAdapter {
private final List<String> tabTitles = new ArrayList<String>() {{
add("Fragment 1");
add("Fragment 4");
add("Fragment 7");
}};
private List<Fragment> tabs = new ArrayList<>();
public CustomPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
initializeTabs();
}
private void initializeTabs() {
tabs.add(HostFragment.newInstance(new Fragment1()));
tabs.add(HostFragment.newInstance(new Fragment4()));
tabs.add(HostFragment.newInstance(new Fragment7()));
}
@Override
public Fragment getItem(int position) {
return tabs.get(position);
}
@Override
public int getCount() {
return tabs.size();
}
@Override
public CharSequence getPageTitle(int position) {
return tabTitles.get(position);
}
}
The fragments containing the actual contents should be wrapped by a HostFragment
, that uses its child FragmentManager
to replace the current fragment with a new one if you want to navigate, or to pop the last fragment from the fragment stack. Call its replaceFragment
to navigate:
public class HostFragment extends BackStackFragment {
private Fragment fragment;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.host_fragment, container, false);
if (fragment != null) {
replaceFragment(fragment, false);
}
return view;
}
public void replaceFragment(Fragment fragment, boolean addToBackstack) {
if (addToBackstack) {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
} else {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
}
}
public static HostFragment newInstance(Fragment fragment) {
HostFragment hostFragment = new HostFragment();
hostFragment.fragment = fragment;
return hostFragment;
}
}
The HostFragment should extend the abstract class BackstackFragment
public abstract class BackStackFragment extends Fragment {
public static boolean handleBackPressed(FragmentManager fm)
{
if(fm.getFragments() != null){
for(Fragment frag : fm.getFragments()){
if(frag != null && frag.isVisible() && frag instanceof BackStackFragment){
if(((BackStackFragment)frag).onBackPressed()){
return true;
}
}
}
}
return false;
}
protected boolean onBackPressed()
{
FragmentManager fm = getChildFragmentManager();
if(handleBackPressed(fm)){
return true;
} else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
fm.popBackStack();
return true;
}
return false;
}
}
Wire everthing up in the MainActivity
:
public class MainActivity extends AppCompatActivity {
private CustomPagerAdapter customPagerAdapter;
private ViewPager viewPager;
private TabLayout tabLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewpager);
tabLayout = (TabLayout) findViewById(R.id.tablayout);
customPagerAdapter = new CustomPagerAdapter(getSupportFragmentManager());
// 2 is enough for us; increase if you have more tabs!
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(customPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
@Override
public void onBackPressed()
{
if(!BackStackFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
}
}
public void openNextFragment() {
HostFragment hostFragment = (HostFragment) customPagerAdapter.getItem(viewPager.getCurrentItem());
// your logic to change the fragments...
}
}
This solution is certainly not perfect but gets the job done. Read more fluff about it in my blog post on the issue.
You can change fragments inside page fragment. For example, TAB_A implements logic of picking fragment 1 or 2, and displaying picked fragment inside. Hierarchy looks like:
ViewPager -> TAB_A -> fragment 1 or 2 (displaying inside TAB_A).
Also you should use getChildFragmentManager() to manage fragments inside TAB_A.
Based on what you explained I Implement following:
1- In your Activity create your Tabs
and ViewPager
:
mPager = (ViewPager) findViewById(R.id.vp_pager);
mPager.setOffscreenPageLimit(3);
PagerAdapter mAdapter = new PagerAdapter(getSupportFragmentManager(), getContext());
mPager.setAdapter(mAdapter);
//tab.setupWithViewPager(mPager);
2- In each Tabs create ViewPager
(I assume you want tabs inside tabs
if you don't need tabs just removed them), Implementation of each fragment
would be something like :
ViewPager mPager = (ViewPager) rootView.findViewById(R.id.vp_pager);
mPager.setOffscreenPageLimit(3);
CategoryAdapter mAdapter = new CategoryAdapter(getChildFragmentManager());//make sure this ChildFragmentManager
mPager.setAdapter(mAdapter);
Also in fragment create a Button which when Click goes to another fragment:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPager.setCurrentItem(1); // go to second fragment
}
});
final result would be something like following and when If you go to Home Tabs and then goes back to Category Tab you're your Fragment
position not changed.
Two point you should remember:
mPager.setOffscreenPageLimit(3);
if you don't want to destroy your fragmentgetChildFragmentManager()
instead of FragmentManager()
If you don't need Inner ViewPager
And you Just want load one instance of Fragment at time here is another option:
1- Do first item of previous way.
2- In your fragment xml put FrameContainer and load your fragment inside it:
CategoryResultFragment f = CategoryResultFragment.newInstance();
getFragmentManager().beginTransaction()
.replace(R.id.frame_result_container, f)
.commit();
Edit: This is not best approach but I think solve your issue:
1- Create a static field in your MainActivity
like following:
static String[] type = new String[3];
2- When you call onClick in your fragment, update String
value in your MainActivity
.
public static update currentType(int pos,String type);
first value is ViewPager position, Second value is your inner Fragment type (e.g: fragment_d);
3- In your ViewPager
@Override
public Fragment getItem(int position) {
switch (position) {
case 0: // first tab
if(type[position] == "fragment_d")
return new Fragment_D();
else
return new Fragment_B();
}
}