I would like to create an Android app that uses a navigation drawer which loads different fragments, all of which include a toolbar/appbar and one that also has a TabView with ViewPager2, something like this:
So I started a new Java project with Android Studio and chose the Navigation Drawer Activity template that creates 3 different fragments. This is my code:
activity_main.xml (removed ToolBar from the template)
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
MainActivity.java (commented setupActionBarWithNavController
because ToolBar is not here anymore)
package com.testui2;
import android.os.Bundle;
import android.view.Menu;
import com.google.android.material.navigation.NavigationView;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
import com.testui2.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
//NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
fragment_home.xml (first fragment with ToolBar only)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<include layout="@layout/app_bar_main"
android:id="@+id/appbar" />
<TextView
android:id="@+id/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app_bar_main.xml (moved ToolBar here to apply to other fragments, too)
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.TestUI2.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.TestUI2.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
app_bar_main_tabs.xml (identical to the previous, but with TabLayout
for the second fragment that requires it)
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.TestUI2.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.TestUI2.PopupOverlay" />
<!-- This layout has the tabs -->
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TabLayout.Colored" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
fragment_gallery.xml (second fragment that has ToolBar and TabLayout with ViewPager like the image at the top)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.gallery.GalleryFragment">
<include layout="@layout/app_bar_main_tabs"
android:id="@+id/appbar" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFragment.java (code behind the first fragment, modified the template to have the ToolBar setup here)
package com.testui2.ui.home;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.testui2.MainActivity;
import com.testui2.databinding.FragmentHomeBinding;
public class HomeFragment extends Fragment {
private FragmentHomeBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView textView = binding.textHome;
homeViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MainActivity currentActivity = (MainActivity) requireActivity();
currentActivity.setSupportActionBar(binding.appbar.toolbar);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
GalleryFragment.java (code behind the second fragment, with tabs and viewpager2)
package com.testui2.ui.gallery;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.testui2.MainActivity;
import com.testui2.databinding.FragmentGalleryBinding;
public class GalleryFragment extends Fragment {
private FragmentGalleryBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentGalleryBinding.inflate(inflater, container, false);
View root = binding.getRoot();
// Code to handle tabs
GalleryPagerAdapter galleryPagerAdapter = new GalleryPagerAdapter(requireActivity());
ViewPager2 viewPager = binding.viewPager;
viewPager.setAdapter(galleryPagerAdapter);
TabLayout tabs = binding.appbar.tabs;
new TabLayoutMediator(tabs, viewPager,
(tab, position) -> tab.setText("TAB " + (position + 1))
).attach();
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MainActivity currentActivity = (MainActivity) requireActivity();
currentActivity.setSupportActionBar(binding.appbar.toolbar);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
// Class to handle ViewPager2
private class GalleryPagerAdapter extends FragmentStateAdapter {
public GalleryPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
public GalleryPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
@NonNull
@Override
public Fragment createFragment(int position) { return GalleryPageFragment.newInstance(position); }
@Override
public int getItemCount() {
return 3;
}
}
}
GalleryPageFragment.java (the code that handles the pages on the ViewPager2
)
package com.testui2.ui.gallery;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.testui2.databinding.FragmentGalleryPageBinding;
public class GalleryPageFragment extends Fragment {
private FragmentGalleryPageBinding binding;
private static final String ARG_PARAM1 = "param1";
private int mParam1;
public GalleryPageFragment() {
// Required empty public constructor
}
public static GalleryPageFragment newInstance(int param1) {
GalleryPageFragment fragment = new GalleryPageFragment();
Bundle args = new Bundle();
args.putInt(ARG_PARAM1, param1);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getInt(ARG_PARAM1);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentGalleryPageBinding.inflate(inflater, container, false);
View root = binding.getRoot();
binding.textGallery.setText(String.format("This is gallery page %d", mParam1 + 1));
return root;
}
}
Basically I took the template and modified it to move the ToolBar code into the fragment (using this and this) because I want the tabs to be docked directly under the ToolBar and, in the future, handle devices with big screens with the fragments displayed simultaneously. Unfortunately there are issues with this approach, that I would like to solve:
I'm unable to apply the NavigationUI to this method, because I don't know how to call
NavigationUI.setupActionBarWithNavController
from the Fragments correctly. I must call it from the Fragments because the Toolbar is there, in fact I'm missing both the AppBar title and the hamburger icon:The tab layout is displayed correctly on the second fragment (Gallery) and the PageViewer2 scrolls the tabs successfully. But if I click on the tab names, it doesn't switch the current tab. How can I do that?
Or, if you have other suggestions on how to handle a fixed ToolBar (meaning it is inside activity_main.xml
) more easily, but with one of the fragments that attaches the TabLayout to look the same than the first picture, I could of course change the code. I must have the Navigation drawer, too.
I tried in another project to stick with the default template (with the ToolBar in the activity_main.xml
) and, on the Gallery fragment, putting TabLayout and ViewPager on the same XML layout. But doing that, the tabs are not looking the same: an horizontal separator appears between the TabLayout and the ToolBar (because TabLayout is not inside the <com.google.android.material.appbar.AppBarLayout>
XML node) and there's no drop shadow below the TabLayout. Example below: