android-studioandroid-toolbaroverflow-menukotlin-android

Custom Overflow Menu Of Toolbar Android


I am trying to implement a custom overflow menu in a toolbar, the image is attached for reference. I went through many tutorials and articles to implement the desired result but was unable to do it. I managed to implement it a little bit but was unable to customize it more. I read a few articles which suggested using ListPopupWindow or PopupMenu can anyone guide me or share any tutorials or articles?

I need the following to be done:

  1. Layout width to wrap_content
  2. Show the selected item background, icon and text color
  3. Layout to be shown just below the toolbar not on the toolbar

What I want to achieve

what I want to achieve

What I actually achieved

what I actually achieved


Solution

  • I managed to get the desired result using ListPopupWindow and code is below.

    NOTE

    Using DataBinding, Navigation Component and Material Toolbar

    Step 1

    Create a custom layout for a single item which will appear in ListPopupWindow.

    item_overflow.xml
    <?xml version="1.0" encoding="utf-8"?>
    <layout 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">
    
        <data>
    
            <variable
                name="item"
                type="com.merisehat.clinicapp.data.models.app.OverflowMenu" />
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/_5sdp">
    
            <ImageView
                android:id="@+id/iv_menuIcon"
                android:layout_width="@dimen/_12sdp"
                android:layout_height="@dimen/_12sdp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:setImage="@{item.icon}"
                tools:src="@drawable/ic_profile" />
    
            <TextView
                android:id="@+id/tv_menuName"
                style="@style/Theme.TextView.CircularStdLight.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="@dimen/_5sdp"
                android:text="@{item.name}"
                app:layout_constraintBottom_toBottomOf="@id/iv_menuIcon"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/iv_menuIcon"
                app:layout_constraintTop_toTopOf="@id/iv_menuIcon"
                tools:text="Menu Item" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    Step 2

    Create a custom adapter for a single item which will appear in ListPopupWindow.

    OverflowMenuAdapter.kt
    import android.content.Context
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.ArrayAdapter
    import com.merisehat.clinicapp.data.models.app.OverflowMenu
    import com.merisehat.clinicapp.databinding.ItemOverflowBinding
    
    class OverflowMenuAdapter(private val context: Context, private val menuList: List<OverflowMenu>):
        ArrayAdapter<OverflowMenu>(context, 0, menuList) {
    
        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            val binding: ItemOverflowBinding
            val view: View
    
            if (convertView == null) {
                binding = ItemOverflowBinding.inflate(LayoutInflater.from(context), parent, false)
                view = binding.root
                view.tag = binding
            } else {
                binding = convertView.tag as ItemOverflowBinding
                view = convertView
            }
    
            binding.item = menuList[position]
            binding.executePendingBindings()
    
            return view
        }
    }
    

    Step 3

    Create two drawables files. One for the ListPopupWindow which will customise its background like its radius, color, padding and etc. Second for the background of an item of ListPopupWindow when it is selected/clicked like its radius, color and etc.

    background_overflow.xml
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <padding android:top="@dimen/_5sdp" android:right="@dimen/_5sdp" android:bottom="@dimen/_5sdp" android:left="@dimen/_5sdp"/>
        <solid android:color="@color/white" />
        <corners android:radius="@dimen/overall_corners" />
    </shape>
    
    background_selected_overflow.xml
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="@color/green" />
        <corners android:radius="@dimen/overall_corners" />
    </shape>
    

    Step 4

    Create ListPopupWindow instance and show when overflow icon is clicked on Toolbar

    MainActivity.kt
    private fun showOverflowMenu(anchorView: View) {
        val listPopupWindow = ListPopupWindow(this)
        listPopupWindow.apply {
            setBackgroundDrawable(ContextCompat.getDrawable(this@MainActivity, R.drawable.background_overflow))
            setListSelector(ContextCompat.getDrawable(this@MainActivity, R.drawable.background_selected_overflow))
            setAdapter(OverflowMenuAdapter(this@MainActivity, getOverflowMenuList()))
            this.anchorView = anchorView
            width = 220
            height = ViewGroup.LayoutParams.WRAP_CONTENT
            horizontalOffset = -150
            verticalOffset = -10
            setOnItemClickListener { _, view, position, _ ->
                (view?.findViewById<TextView>(R.id.tv_menuName))?.setTextColor(
                    ContextCompat.getColor(this@MainActivity, R.color.white)
                )
                (view?.findViewById<ImageView>(R.id.iv_menuIcon))?.imageTintList = ColorStateList.valueOf(
                    ContextCompat.getColor(this@MainActivity, R.color.white)
                )
                when (position) {
                    0 -> navController.navigate(R.id.action_to_profileFragment)
                    1 -> navController.navigate(R.id.action_to_historyFragment)
                    2 -> navController.navigate(R.id.action_to_logoutDialog)
                }
                lifecycleScope.launch {
                    delay(200)
                    dismiss()
                }
            }
            show()
        }
    }
    
    private fun getOverflowMenuList(): List<OverflowMenu> =
        listOf(
            OverflowMenu(R.drawable.ic_profile, getString(R.string.title_profile)),
            OverflowMenu(R.drawable.ic_history, getString(R.string.title_history)),
            OverflowMenu(R.drawable.ic_logout, getString(R.string.title_logout))
        )
    
    
    private val homeMenuProviderCallback = object : MenuProvider {
        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
            menuInflater.inflate(R.menu.toolbar_app_home, menu)
        }
    
        override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
            when (menuItem.itemId) {
                R.id.searchFragment -> navController.navigate(R.id.action_to_searchFragment)
                R.id.action_overflow -> showOverflowMenu(findViewById(menuItem.itemId))
                else -> return false
            }
            return true
        }
    }
    

    I hope it helps someone out.