Bottom navigation is the latest design trend in Android apps. Using this wisely and efficiently, one can create beautiful interfaces and wonderful user experiences.

When to use bottom navigation?

Bottom Navigation is best used when you have three to five top-level navigation items of similar importance.

Android Support Library provides a Bottom Navigation View. We will be using the same for this tutorial. If you’re looking for something fancier and more customizable, check this awesome library.

The layout and views

Each bottom bar item is supposed to hold a Fragment. Which means it will work very similar to a TabLayout with a ViewPager, minus the swiping.

So the first object we will create is a ViewPager with no swiping.

Create a Java class NoSwipePager.

public class NoSwipePager extends ViewPager {
 private boolean enabled;

 public NoSwipePager(Context context, AttributeSet attrs) {
   super(context, attrs);
   this.enabled = true;
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
   if (this.enabled) {
     return super.onTouchEvent(event);
   }
   return false;
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
   if (this.enabled) {
     return super.onInterceptTouchEvent(event);
   }
   return false;
 }

 public void setPagingEnabled(boolean enabled) {
   this.enabled = enabled;
 }
}

Now, let’s create our layout. Modify your activity’s XML layout, to the following:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"/>

    </android.support.design.widget.AppBarLayout>
    <NoSwipePager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:background="#EFEFEF"/>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        app:labelVisibilityMode="labeled"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:menu="@menu/navigation" />

</android.support.design.widget.CoordinatorLayout>

The important elements are the bottom navigation view and the ViewPager for the fragments. Apart from these, you can modify the layout according to your requirements. You can even embed the entire thing inside a DrawerLayout. Comment below if you need an example for this.

Add the navigation items

The navigation items are added in the form of a menu. Create a file navigation.xml inside res/menu and add your items there.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/tab1"
        android:icon="@drawable/icon1"
        android:title="Home" />

    <item
        android:id="@+id/tab2"
        android:icon="@drawable/icon2"
        android:title="Profile" />

    <item
        android:id="@+id/tab3"
        android:icon="@drawable/icon3"
        android:title="Notifications" />

</menu>

The adapter

Now that we have our items and our views, we need to write an adapter for handling our fragments.

Here we can use theFragmentStatePagerAdapter provided by Android, but we want to retain the fragment state. There’s a ‘smart’ implementation of this adapter that manages the fragment lifecycles. So grab the code from this gist and add it to your project in a file named SmartFragmentStatePagerAdapter.java.

/*
   Extension of FragmentStatePagerAdapter which intelligently caches
   all active fragments and manages the fragment lifecycles.
   Usage involves extending from SmartFragmentStatePagerAdapter as you would any other PagerAdapter.
*/
public abstract class SmartFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
    // Sparse array to keep track of registered fragments in memory
    private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();

    public SmartFragmentStatePagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    // Register the fragment when the item is instantiated
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.append(position, fragment);
        return fragment;
    }

    // Unregister when the item is inactive
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    // Returns the fragment for the position (if instantiated)
    public Fragment getRegisteredFragment(int position) {
        return registeredFragments.get(position);
    }
}

It’s time to create the adapter extending this class.

public class BottomBarAdapter extends SmartFragmentStatePagerAdapter {
    private final List<Fragment> fragments = new ArrayList<>();

    public BottomBarAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }
    // Our custom method that populates this Adapter with Fragments
    public void addFragments(Fragment fragment) {
        fragments.add(fragment);
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
}

Adding the fragments

Here’s a dummy fragment for demonstration. Create three different fragments for the three navigation items and in the next step we shall add them to our adapter.

public class FragmentOne extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_one, container, false);
        //more code here
        return view;
    }
}

Finally, let’s set up the activity’s java code.

public class MainActivity extends AppCompatActivity {

    private NoSwipePager viewPager;
    private BottomBarAdapter pagerAdapter;
    BottomNavigationView navigation;

    FragmentOne frag1 = new FragmentOne();
    FragmentTwo frag2 = new FragmentTwo();
    FragmentThree frag3 = new FragmentThree();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_main);

        navigation = findViewById(R.id.navigation);
        viewPager = findViewById(R.id.viewPager);
        
        //optimisation
        viewPager.setOffscreenPageLimit(3);
        viewPager.setPagingEnabled(false);
        
        pagerAdapter = new BottomBarAdapter(getSupportFragmentManager());
        
        pagerAdapter.addFragments(frag1);
        pagerAdapter.addFragments(frag2);
        pagerAdapter.addFragments(frag3);

        viewPager.setAdapter(pagerAdapter);

        //Handling the tab clicks
        navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {

                switch (menuItem.getItemId()) {
                    case R.id.tab1:
                        viewPager.setCurrentItem(0);
                        return true;
                    case R.id.tab2:
                        viewPager.setCurrentItem(1);
                        return true;
                    case R.id.tab3:
                        viewPager.setCurrentItem(2);
                        return true;
                    
                }
                return false;
            }
        });
    }
}