Android Custom Side Menu 2-Navigation Drawer Alternative -> using Fragment

Android Custom Side Menu 2-Navigation Drawer Alternative -> using Fragment

Imagine we need to create a side menu in android. One of the options we have is to use Navigation Drawer. If you don't know about the navigation drawer it is better to check my first article Navigation Drawer to have a better and clear understanding, although it is not necessary.

What we will do in this article? Creating navigation drawer alternative using a fragment. Yes, you read it right.

Prequisites

  • basic kotlin knowledge
  • recyclerview/adapter
  • interface
  • fragment basics
  • lambda functions
  • ViewBinding

I have explained the navigation drawer and lambda basics in the first article. Read it here.

Steps to Implement Side Menu in Android Using Fragment

  1. Create Android Studio Project

    To create an android project, open Android Studio, click on New Project, select an empty activity, click Next, then give a name to your project and choose kotlin as the language, then click on Finish. Now, the new project is created let's move to the next step.

  2. Recyclerview layout

    Now let's create recyclerview layout file for our adapter. Click to res folder to expand it, right-click to layout-> new -> Layout resource File and name it as menu_list_item and choose the root element as Constraint Layout then click ok.

    menu_list_item includes the following code

    <?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"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
    
     <TextView
         android:id="@+id/textView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="10dp"
         android:padding="8dp"
         android:text="TextView"
         android:textAlignment="center"
         android:textAllCaps="true"
         android:textColor="@color/black"
         android:textSize="20sp"
         android:textStyle="bold"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  3. Creating Recyclerview Adapter

    Right-click to package name and create a new kotlin/file class and name it MenuAdapter

    MenuAdapter.kt just regular recyclerview adapter with on click listener attached to textview. One difference is I am using the adapter function to submit data instead of sending the data with the constructor.

    MenuAdapter.kt

package com.cobanogluhasan.androidcustomsidemenu

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.cobanogluhasan.androidcustomsidemenu.databinding.MenuListItemBinding

class MenuAdapter(private val listener: OnOptionClick) :
    RecyclerView.Adapter<MenuAdapter.ViewHolder>() {

    private val TAG = "MenuAdapter"
    private var optionList = emptyList<String>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        //inflate the layout file with viewbinding
        return ViewHolder(
            MenuListItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        //bind the data to the view by calling bind function and pass the option string
        holder.bind(optionList[position])
    }

    override fun getItemCount(): Int = optionList.size

    inner class ViewHolder(private val binding: MenuListItemBinding) :
        RecyclerView.ViewHolder(binding.root), View.OnClickListener {
        fun bind(option: String) {
            // bind the data -> to textview in that case
            binding.textView.text = option
        }

        init {
            binding.textView.setOnClickListener(this)
        }

        override fun onClick(v: View?) {
            val position = adapterPosition
            val option = optionList[position]
            listener.onOptionClick(position, option)
        }
    }

    //send the data list to adapter from the fragment/activity by calling adapter.submitData()
    fun submitData(list: List<String>) {
        optionList = list
    }

    interface OnOptionClick {
        fun onOptionClick(position: Int, option: String)
    }
}

4- Creating A Fragment

We will be hosting our recyclerview inside the fragment and implement the interface we created to understand and manage which option is clicked. Right-click to package name and create a new Fragment. I named it as MenuFragment.

Let's create string resources for our options to avoid using not use hard-coded strings. I added 5 items for our side menu in the strings.xml file. We are going to use this list in our fragment to populate the recyclerview. Also, add string for our toggle button.

strings.xml

<resources>
    <string name="app_name">Android Custom Side Menu</string>
    <!-- TODO: Remove or change this placeholder text -->
    <string name="hello_blank_fragment">Hello blank fragment</string>
    <string name="option_1">Option 1</string>
    <string name="option_2">Option 2</string>
    <string name="option_3">Option 3</string>
    <string name="option_4">Option 4</string>
    <string name="option_5">Option 5</string>
    <string name="toggle">Toggle</string>
</resources>

Strings are ready and our fragment is created. Time to add a recyclerview into our (fragment XML). Simply add a recyclerview and constraints as match _parent. Do not forget to give it an id.

my fragment_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MenuFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/optionMenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Let's code our fragment class. Implement the OnOptionClick interface ve created and initialize the recyclerview. Simply add this code.

MenuFragment.kt

package com.cobanogluhasan.androidcustomsidemenu

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.cobanogluhasan.androidcustomsidemenu.databinding.FragmentMenuBinding

class MenuFragment : Fragment(), MenuAdapter.OnOptionClick {

    private lateinit var binding: FragmentMenuBinding
    private lateinit var menuAdapter: MenuAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentMenuBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //initialize recyclerview
        initRecycler()
    }

    private fun initRecycler() {
        Log.i("staryed", "initRecycler: ")
        //get the strings and add to list
        val list = mutableListOf<String>()
        list.add(getString(R.string.option_1))
        list.add(getString(R.string.option_2))
        list.add(getString(R.string.option_3))
        list.add(getString(R.string.option_4))
        list.add(getString(R.string.option_5))

        binding.optionMenu.apply {
            //create new adapter instance and apply it to our recyclerview.
            menuAdapter = MenuAdapter(this@MenuFragment)
            val manager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            layoutManager = manager
            adapter = menuAdapter
        }
        //send our menu to the adapter.
        menuAdapter.submitData(list)
    }

    override fun onOptionClick(position: Int, option: String) {
        Toast.makeText(requireContext(), option, Toast.LENGTH_LONG).show()
    }
}

Now if we run the app we will see nothing happens since we do not show the fragment.

5- Show the Fragment

Time to show the fragment and see if it's working. Add FragmentContainerView for our menuFragment and button for handling show/hide logic as toggle button. Navigate to activity_main.xml and add 2 views.

activity_main.xml

<?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=".MainActivity">

    <Button
        android:id="@+id/toggleButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="100dp"
        android:background="@color/black"
        android:padding="6dp"
        android:text="@string/toggle"
        android:textAllCaps="false"
        android:textColor="@color/white"
        android:textSize="20sp"
        app:layout_constraintStart_toEndOf="@id/fragmentContainerView"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="com.cobanogluhasan.androidcustomsidemenu.MenuFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:background="#ddd"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintWidth_percent="0.3" />
    <!--
    0.3 percentage of width means the gonna take 30% of the screen
    give it any background color you want, I picked up gray.
    -->
</androidx.constraintlayout.widget.ConstraintLayout>

Our fragment in place and click listener implemented let's go and run the app.

Screenshot from 2021-08-30 00-09-15.png

If everything is alright that is our app will look for now. Add the following line to hide the fragmentContainerView

android:visibility="invisible"

You can see the Toast message when an option is clicked. But there is no toggle functionality yet. This is what we are going to do in the next step.

6- Add Animations

Right-click to res folder-> new ->android resource directory, select anim and click okay to create anim folder for our animations. We will have 2 animations.

1- slide_in_left_to_right.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="400"
        android:fromXDelta="-100%"
        android:fromYDelta="0%"
        android:toXDelta="0%"
        android:toYDelta="0%" />
</set>

2- slide_out_right_to_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="400"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="-100%"
        android:toYDelta="0%" />
</set>

Everything is ready to move to the final step.

7-Toggle functionality

We're going to implement the toggle functionality on the main activity. Basically, show the menu if it is not showing and hide if it is showing(using animations).

Give an id to the constraint layout since we want the menu to be hidden whenever user click anywhere on the screen and also when an option is clicked. That is the latest version of the activity_main.xml

<?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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/toggleButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="100dp"
        android:background="@color/black"
        android:padding="6dp"
        android:text="@string/toggle"
        android:textAllCaps="false"
        android:textColor="@color/white"
        android:textSize="20sp"
        app:layout_constraintStart_toEndOf="@id/fragmentContainerView"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="com.cobanogluhasan.androidcustomsidemenu.MenuFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:background="#ddd"
        android:visibility="invisible"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintWidth_percent="0.3" />
    <!--
    0.3 percentage of width means the width of the view gonna take 30% of the screen,
    give it any background color you want, I picked up gray.
    -->
</androidx.constraintlayout.widget.ConstraintLayout>

To implement the toggle functionality navigate to MainActivity and add the following.

package com.cobanogluhasan.androidcustomsidemenu

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.animation.AnimationUtils
import com.cobanogluhasan.androidcustomsidemenu.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private var isMenuShowing = false
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.toggleButton.setOnClickListener { toggleBtn() }
        //hide the menu on anywhere click
        binding.container.setOnClickListener { hideMenu() }
    }

    private fun toggleBtn() {
        if (!isMenuShowing) {
            //show the menu if is not showing
            binding.fragmentContainerView.visibility = View.VISIBLE
            //prepare and load animation
            val animation = AnimationUtils.loadAnimation(this, R.anim.slide_in_left_to_right)
            binding.fragmentContainerView.startAnimation(animation)
            // isMenuShowing = assign reverse of the current state.
            // if it's true it'll be false. if it's false, it will be true
            isMenuShowing = !isMenuShowing
        } else hideMenu()
    }

    //hideMenu should not be private otherwise we can not acces from the child fragment.
    fun hideMenu() {
        if (isMenuShowing) {
            //hide menu if it's showing
            binding.fragmentContainerView.visibility = View.INVISIBLE
            val animation = AnimationUtils.loadAnimation(this, R.anim.slide_out_right_to_left)
            binding.fragmentContainerView.startAnimation(animation)
            isMenuShowing = false
        }
    }
}

Our side menu is complete and working.

ezgif-3-5011609bce4e.gif

Source Code.

Feel free to clone, download or to play with the source code :)

Conclusion

In this tutorial, you learned how to create a navigation drawer alternative using fragment and recyclerview in Android from scratch. If you'd like to add the menu to the right you only need to change the constraint of the view(fragment) and add proper animations. I hope you enjoyed this. Thanks for reading, Like, comment and share and watch out for more articles from me. Happy Coding!!