Android Bottom Navigation stays at the bottom of the screen providing navigation between top-level views in the app. This is introduced in design support library with backward compatibility. Bottom Navigation should be used when the app has three to five top-level navigations.
This article explains the basics of Bottom Navigation, combining it with Fragments. We also going to learn how to load the first fragment with grid data (using RecyclerView) by fetching JSON through HTTP call.
1. Bottom Navigation
The Bottom Navigation can be easily added using BottomNavigationView component. You have to use gravitation or relative attributes to make it appear at the bottom of the screen.
app:menu — The menu resource file to display the navigation items along with icon and text. app:itemBackground — Applies background color to bottom navigation. app:itemTextColor — The text color of bottom navigation item. app:itemIconTint — The icon color of bottom navigation item.
When to use Bottom Navigation? As per the design specs, the below navigations should be used depending on the criteria.
>Navigation Drawer – Use when top-level navigation has more than six destinations.
>Tabs – Use when there are two navigational destinations.
>Bottom Navigation – Use when there are three to five top-level destinations.
Before going further, have a quick look at the design specifications of Bottom Navigation.
Now let’s try it by creating a new project in Android Studio.
2. Creating New Project
1. Create a new project in Android Studio from File ⇒ New Project and select Basic Activity from templates.
2. Download this res folder and add the drawables to your project’s res. This folder contains necessary drawables required for bottom navigation items.
3. Make sure you have design support library in your build.gradle.
6. Open the layout file of main activity i.e activity_main.xml and add BottomNavigationView widget. Here we are also adding a FrameLayout to load the Fragments when the navigation item is selected.
7. Now open MainActivity.java and modify it as below.
> Here, OnNavigationItemSelectedListener will be called when the bottom navigation item is selected. For now we are just changing the toolbar title upon selecting the navigation item.
If you run the app, you can see the bottom navigation displayed as shown below.
3. Adding Fragments
As we have the Bottom Navigation ready, let’s see how to switch views when the navigation menu item is selected. This can be done easily by using the Fragments.
Note: ViewPager shouldn’t be used when using Bottom Navigation as per design specs (Avoid using lateral motion to transition between views)
I am creating four fragments named StoreFragment, GiftsFragment, CartFragment and ProfileFragment.
8. Create new Fragment by going to File ⇒ New ⇒ Fragment ⇒ Fragment (Blank) and name it as StoreFragment.java. Likewise create other three fragments too.
9. Open MainActivity.java and modify bottom navigation listener as below to load the fragments in FrameLayout.
>loadFragment() – loads the Fragment into FrameLayout. The same method is called in OnNavigationItemSelectedListener callback by passing appropriate fragment instance. > The logic needed for specific module goes into appropriate Fragment keeping the MainActivity clean.
MainActivity.java
package info.androidhive.bottomnavigation;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import info.androidhive.bottomnavigation.fragment.CartFragment;
import info.androidhive.bottomnavigation.fragment.GiftsFragment;
import info.androidhive.bottomnavigation.fragment.ProfileFragment;
import info.androidhive.bottomnavigation.fragment.StoreFragment;
import info.androidhive.bottomnavigation.helper.BottomNavigationBehavior;
public class MainActivity extends AppCompatActivity {
private ActionBar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = getSupportActionBar();
// load the store fragment by default
toolbar.setTitle("Shop");
loadFragment(new StoreFragment());
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
Fragment fragment;
switch (item.getItemId()) {
case R.id.navigation_shop:
toolbar.setTitle("Shop");
fragment = new StoreFragment();
loadFragment(fragment);
return true;
case R.id.navigation_gifts:
toolbar.setTitle("My Gifts");
fragment = new GiftsFragment();
loadFragment(fragment);
return true;
case R.id.navigation_cart:
toolbar.setTitle("Cart");
fragment = new CartFragment();
loadFragment(fragment);
return true;
case R.id.navigation_profile:
toolbar.setTitle("Profile");
fragment = new ProfileFragment();
loadFragment(fragment);
return true;
}
return false;
}
};
private void loadFragment(Fragment fragment) {
// load fragment
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
Now if you run the project you can see the fragments loaded when navigation is selected.
4. Implementing ShopFragment – Displaying Items in Grid
Now we’ll see how to implement the first fragment i.e ShopFragment which displays the shop items in a Grid fashion. For demonstration, I have created a sample json which contains few movies for sale. To implement this, all we have to do is, fetch json and display the data in RecyclerView in a grid format. To make the task simpler, follow my other article which explains the same.
14. Create an xml layout named store_item_row.xml under res ⇒ layout. This layout file will be used in RecyclerView adapter class to render single item.
15. Create a class named Movie.java. This POJO class will be useful while parsing the json.
Movie.java
package info.androidhive.bottomnavigation;
public class Movie {
String title;
String image;
String price;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
16. Now open StoreFragment.java and add below code. For simplicity the RecyclerView adapter class StoreAdapter included in the same fragment.
>fetchStoreItems() Method fetches the movies json using Volley and serializes it using Gson.
>StoreAdapter class renders the movies in RecyclerView.
StoreFragment.java
package info.androidhive.bottomnavigation.fragment;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.List;
import info.androidhive.bottomnavigation.Movie;
import info.androidhive.bottomnavigation.app.MyApplication;
import info.androidhive.bottomnavigation.R;
public class StoreFragment extends Fragment {
private static final String TAG = StoreFragment.class.getSimpleName();
private static final String URL = "https://api.androidhive.info/json/movies_2017.json";
private RecyclerView recyclerView;
private List<Movie> movieList;
private StoreAdapter mAdapter;
public StoreFragment() {
// Required empty public constructor
}
public static StoreFragment newInstance(String param1, String param2) {
StoreFragment fragment = new StoreFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_store, container, false);
recyclerView = view.findViewById(R.id.recycler_view);
movieList = new ArrayList<>();
mAdapter = new StoreAdapter(getActivity(), movieList);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(getActivity(), 3);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(8), true));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(mAdapter);
recyclerView.setNestedScrollingEnabled(false);
fetchStoreItems();
return view;
}
private void fetchStoreItems() {
JsonArrayRequest request = new JsonArrayRequest(URL,
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
if (response == null) {
Toast.makeText(getActivity(), "Couldn't fetch the store items! Pleas try again.", Toast.LENGTH_LONG).show();
return;
}
List<Movie> items = new Gson().fromJson(response.toString(), new TypeToken<List<Movie>>() {
}.getType());
movieList.clear();
movieList.addAll(items);
// refreshing recycler view
mAdapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// error in getting json
Log.e(TAG, "Error: " + error.getMessage());
Toast.makeText(getActivity(), "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
});
MyApplication.getInstance().addToRequestQueue(request);
}
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing; // item top
}
}
}
}
/**
* Converting dp to pixel
*/
private int dpToPx(int dp) {
Resources r = getResources();
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}
class StoreAdapter extends RecyclerView.Adapter<StoreAdapter.MyViewHolder> {
private Context context;
private List<Movie> movieList;
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView name, price;
public ImageView thumbnail;
public MyViewHolder(View view) {
super(view);
name = view.findViewById(R.id.title);
price = view.findViewById(R.id.price);
thumbnail = view.findViewById(R.id.thumbnail);
}
}
public StoreAdapter(Context context, List<Movie> movieList) {
this.context = context;
this.movieList = movieList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.store_item_row, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
final Movie movie = movieList.get(position);
holder.name.setText(movie.getTitle());
holder.price.setText(movie.getPrice());
Glide.with(context)
.load(movie.getImage())
.into(holder.thumbnail);
}
@Override
public int getItemCount() {
return movieList.size();
}
}
}
Now if you run the app, you can see the ShopFragment displaying the movies in grid manner. Likewise you can implement other fragments too.
5. Hiding Bottom Navigation on Scroll
As per design specs, the Bottom Navigation has to be hidden when the content is scrolled giving more room to content on the screen. To achieve this, we need to attach the BottomNavigationBehavior to Bottom Navigation.
17. Create a class named BottomNavigationBehavior.java with the below code.