0

I have trouble finding a good example on how to swipe between fragments with help of bottom navigatiom bar. Since FragmentStatePagerAdapter is deprecated and a new ViewPager2 is now recommended instead of the old ViewPager I want to use ViewPager2 and FragmentStateAdapter in my code instead. I have found an example of how to combine BottomNavigationBar and ViewPager here and I want to do something similar. My code have many similarities to the one in the example with the only difference that I have my code in a fragment instead of an activity. Here is a picture of how my FrontendFragment display look like. I can switch between the views using the bottomnavigationbar but I also want to be able to swipe between the views. Can someone help me or at least direct me on the right way? Here is my code:

FragmentPagerAdapter Class:

public class FragmentPagerAdapter extends FragmentStateAdapter {

    private static final int mFragmentCount = 5;

    public FragmentPagerAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position){

            case 0:
                return new HomeFragment();
            case 1:
                return new SearchFragment();
            case 2:
                return new AddFragment();
            case 3:
                return new MessageFragment();
            case 4:
                return new ProfileFragment();
        }
        return null;
    }

    @Override
    public int getItemCount() {
        return mFragmentCount;
    }
} 

FrontendFragment Class:

public class FrontendFragment extends Fragment implements BottomNavigationView.OnNavigationItemSelectedListener{

    private BottomNavigationView mBottomNavigationView;
    private ViewPager2 mViewPager2;
    private FragmentPagerAdapter mFragmentPagerAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View v =  inflater.inflate(R.layout.fragment_frontend, container, false);

        loadFragment(new HomeFragment());

        mBottomNavigationView = v.findViewById(R.id.bottomNavigationBar);
        mBottomNavigationView.setOnNavigationItemSelectedListener(this);


        return v;
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {

        Fragment selectedFragment = null;

        switch (item.getItemId()) {
            case R.id.home_icon:
                selectedFragment = new HomeFragment();
                break;
            case R.id.search_icon:
                selectedFragment = new SearchFragment();
                break;
            case R.id.add_icon:
                selectedFragment = new AddFragment();
                break;

            case R.id.message_icon:
                selectedFragment = new MessageFragment();
                break;

            case R.id.profile_icon:
                selectedFragment = new ProfileFragment();
                break;
        }
        
        return loadFragment(selectedFragment);
    }


    private boolean loadFragment(Fragment selectedFragment) {

        if(selectedFragment != null){

            MainActivity.sFm.beginTransaction().replace(R.id.relLayoutMiddle, selectedFragment).commit();
            return true;
        }
        return false;
    }
}

Thanks in advance!

Mohammed Abdu
  • 25
  • 2
  • 9

2 Answers2

1

As I'm already using BottomNav with ViewPager2 in one of my app, I can help.

Your code is partially correct which means your FragmentPagerAdapter is fine, but not your FrontEndFragment.

See, the FragmentPagerAdapter has to be set to a ViewPager2 as

//this here is the FrontEndFragment 
mViewPager2.setAdapter(new FragmentPagerAdapter(this));

Then, You don't have to do the FragmentTransaction at all, you just have to change the ViewPager2's current item position through the BottomNavigationBar as

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {

    switch (item.getItemId()) {
        case R.id.home_icon:
            mViewPager2.setCurrentItem(0);
            break;
        case R.id.search_icon:
            mViewPager2.setCurrentItem(1);
            break;
        case R.id.add_icon:
            mViewPager2.setCurrentItem(2);
            break;

        case R.id.message_icon:
            mViewPager2.setCurrentItem(3);
            break;

        case R.id.profile_icon:
            mViewPager2.setCurrentItem(4);
            break;
    }
    
    return false;
}

This is all, you don't have to deal with Fragments at all apart from the FragmentPagerAdapter. Also, don't forget to remove the loadFragment(new HomeFragment()); which is not required, nor the function loadFragment() is required.

(Optional), Furthermore, if you want to disable the Swipe Action of the ViewPager2 and want the Fragments to be selected based on the Selected BottomNav item only, then you can set setUserInputEnabled() property of ViewPager2 as false.


Next, to set the BottomNavigationBar's item as selected based on the swipe of the ViewPager2, what you've to do is,

Create a global var

MenuItem previousMenuItem;

Then, set a default item (first) to be selected of BottomNav on activity start as

mBottomNavigationView.getMenu().getItem(0).setChecked(true);

Finally, set an OnPageSelected() callback on ViewPager2 to update the selected Menu Item as:

mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels);
    }

    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
        if (previousMenuItem != null) {
            previousMenuItem.setChecked(false);
        }
        else {
            mBottomNavigationView.getMenu().getItem(0).setChecked(false);
        }
        mBottomNavigationView.getMenu().getItem(position).setChecked(true);
        previousMenuItem = mBottomNavigationView.getMenu().getItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        super.onPageScrollStateChanged(state);
    }
});

What you're doing here is that you're setting default item to previousMenuItem and then on swiping to a different page, deselecting the previousMenuItem and selecting the new one, which means updating the BottomNav based on ViewPager's current item. This is the complete code you require to acheive your objective.

Lalit Fauzdar
  • 5,953
  • 2
  • 26
  • 50
  • Thanks! I just implemented your code, howver I am getting an error at `mViewPager2.setAdapter(new FragmentPagerAdapter());` since I need to pass a fragment in `FragmentPagerAdapter`. What do I pass there? Also I do need to find viewpager2 id before setting the adapter right? just to be sure as I am new to coding. Thanks again for your help! – Mohammed Abdu Mar 17 '21 at 17:47
  • @MohammedAbdu Yes, you've to initialize the `mViewPager2` as `mViewPager2 = v.findViewById(R.id.yourViewPagerID);`, then you've to set the adapter of it. And, in the `FragmentPagerAdapter`, you've to pass the `Fragment` as `mViewPager2.setAdapter(new FragmentPagerAdapter(this));`. It will work. – Lalit Fauzdar Mar 17 '21 at 18:34
  • Thank you so much! I have been stuggling the whole day to findout how to do and now you have saved me! :) It totally works! – Mohammed Abdu Mar 17 '21 at 19:00
  • Just one last thing, as I am trying to learn everything so I do not have to struggle more. Why do I pass in `this` as fragment? what does `this` stand for in this context? Sorry for any inconvinence. – Mohammed Abdu Mar 17 '21 at 19:08
  • @MohammedAbdu `this` always depends on the scope from where you're sending it. Now, you might have noticed that `FragmentStateAdapter`'s Constructor needs a Fragment as defined in your code here `public FragmentPagerAdapter(@NonNull Fragment fragment) {`. It needs the `FrontEndFragment` and when you pass `this`, you actually pass the running instance of `FrontEndFragment` from itself as `this`. As you're in the overridden function `onCreateView()` and not in context of another class like any callback, it works. Else, you'd have to specify which this as `this@FrontEndFragment`. – Lalit Fauzdar Mar 18 '21 at 05:09
0

If you want to swipe between views, a simple solution would be to store all views in your parent view, but set all layouts for views except the initial view to android:visibility="gone". Make sure to set the initial view layout to android:visibility="visible" though. Now on you button clicks, you will have to implement onClick such that they turn on/ off view visibilities accordingly. For example, store views in order and control them via array index. But the whole thing you're trying to do is generally not a good design pattern in my opinion.

Why don't you load another Activity onClick instead of crowding your single activity? This will cause load time issues. Even if the views are non-visible, it's just an overall hassle to maintain all that in one place.

mindoverflow
  • 730
  • 4
  • 13