Separate Back Navigation for a Tabbed View Pager in Android

前端 未结 3 1013
傲寒
傲寒 2021-02-09 02:48

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

相关标签:
3条回答
  • 2021-02-09 03:23

    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 Fragments:

    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.

    0 讨论(0)
  • 2021-02-09 03:40

    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.

    0 讨论(0)
  • 2021-02-09 03:42

    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:

    1. set mPager.setOffscreenPageLimit(3); if you don't want to destroy your fragment
    2. In inner fragment pass getChildFragmentManager() 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();
    
                }
            }
    
    0 讨论(0)
提交回复
热议问题