Bottom Navigation using design Library in Android - Kotlin

Introduction:

Let's implement BottomNavigationView in our layout using Design Support Library. First, we have to go through the factors like, when  and how to use that.

When to use?

As per Material Design specs, the following criteria is there:

(a) Tabs (TabLayout) - Should be used when there are two navigations.

(b) Bottom Navigation - Should be used when there are 3 to 5 navigations.

(c) Navigation Drawer - Should be used when there are 6 or more navigations.

Also to remember that, Viewpager shouldn't be used with BottomNavigationView. You can read the complete design specs, here.

How to use?

BottomNavigationView have to be added  at the bottom of the screen. Also, the following are some of the important attributes:

app:itemBackground - to apply background color 
app:itemTextColor - to apply the color for the text of BottomNavigation item.
app:itemIconTint - to apply the color for the icon of BottomNavigation item.
app:menu - to display navigation item (menu file)

Design:

build.gradle

Include the following in app-level gradle's dependencies.

 implementation 'com.android.support:design:26.1.0'

bottom_navigation.xml

Since, we can't use ViewPager with BottomNavigation, we have to use Fragment with it. Here, the root layout should be CoordinatorLayout, if you would like to do some more customization like hiding bottom navigation when scrolling down and showing when scrolling up.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <FrameLayout
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:menu="@menu/navigation_menu"
        app:itemBackground="@color/colorPrimary"
        android:foreground="?attr/selectableItemBackground"
        app:itemIconTint="@android:color/white"
        app:itemTextColor="@android:color/white"
        app:elevation="4dp"
        android:layout_gravity="bottom"/>

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

navigation_menu.xml

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

    <item
        android:id="@+id/home"
        android:title="@string/home"
        android:icon="@drawable/home" />

    <item
        android:id="@+id/cart"
        android:title="@string/cart"
        android:icon="@drawable/cart" />

    <item
        android:id="@+id/notification"
        android:title="@string/notification"
        android:icon="@drawable/notification" />

    <item
        android:id="@+id/profile"
        android:title="@string/profile"
        android:icon="@drawable/profile" />

</menu>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
    android:layout_marginTop="5dp">

    <android.support.v7.widget.CardView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:elevation="3dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:scaleType="centerCrop"
                android:src="@drawable/user" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:layout_marginRight="10dp"
                android:layout_marginTop="10dp"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="5dp"
                    android:text="Name" />

                <TextView
                    android:id="@+id/phone"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingLeft="5dp"
                    android:paddingRight="5dp"
                    android:text="453345435" />

            </LinearLayout>

        </LinearLayout>
    </android.support.v7.widget.CardView>

</android.support.constraint.ConstraintLayout>

Code:

Bottombar.kt

We have to set OnNavigationItemSelectedListener to BottomNavigation, to fire particular actions while selection. Here I have only changed toolbar title while selection. You can invoke individual Fragment for each selection. 

class BottomBar : AppCompatActivity() {

    lateinit var toolBar: ActionBar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.bottom_navigation)

        supportActionBar?.let { toolBar = it }


        toolBar.title = getString(R.string.home)
        switchContent(HomeFragment())

        bottomNavigation.setOnNavigationItemSelectedListener(object : BottomNavigationView.OnNavigationItemSelectedListener {
            override fun onNavigationItemSelected(item: MenuItem): Boolean {
                when (item.itemId) {
                    R.id.home -> {
                        toolBar.title = getString(R.string.home)
                        switchContent(HomeFragment())
                        return true
                    }

                    R.id.cart -> {
                        toolBar.title = getString(R.string.cart)
                        return true
                    }

                    R.id.notification -> {
                        toolBar.title = getString(R.string.notification)
                        return true
                    }

                    R.id.profile -> {
                        toolBar.title = getString(R.string.profile)
                        switchContent(ProfileFragment())
                        return true
                    }
                }
                return false
            }

        })

    }

    private fun switchContent(fragment: Fragment) {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.frame, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }

   
}

HomeFragment.kt

class HomeFragment : Fragment() {
    var list : MutableList<Human> = ArrayList<Human>()
    lateinit var adapter: RecyclerAdapter

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater!!.inflate(R.layout.recycler, container, false);
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        /** Adding values **/
        list.add(Human("Anisha","9876543210"))
        list.add(Human("Karthik","9876543210"))
        list.add(Human("Vino","9876543210"))
        list.add(Human("Percy","95673210"))

        list.add(Human("Aruvi","9876543210"))
        list.add(Human("Faritha","9876543210"))
        list.add(Human("Annabeth","9876543210"))
        list.add(Human("Jason","95673210"))

        list.add(Human("Leo","9876543210"))
        list.add(Human("Piper","9876543210"))
        list.add(Human("Thalia","9876543210"))
        list.add(Human("Frank","95673210"))


        Log.v("list","list=="+list);

        recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayout.VERTICAL, false)
        adapter = RecyclerAdapter(activity, list);
        recyclerView.adapter = adapter

    }

     class RecyclerAdapter(val context: Context, val data: MutableList<Human>) :
            RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {


        override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
            if (holder != null)
                holder.bindItems(data.get(position))
        }

        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
            val v = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false)
            return ViewHolder(v)
        }

        override fun getItemCount(): Int {
            return data.size
        }

        class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
            fun bindItems(map: Human) {
                itemView.name.text = "Name : " + map.name
                itemView.phone.text = "Phone : " + map.phone
            }
        }
    }

}

Bonus things:

  • If you would like to make BottomNavigation to hide when scroll the RecyclerView down and show when scrolling up, you have to include the following class.
class BottomNavigationBehavior : CoordinatorLayout.Behavior<BottomNavigationView>() {

    private var height: Int = 0

    override fun onLayoutChild(parent: CoordinatorLayout?, child: BottomNavigationView?, layoutDirection: Int): Boolean {
        height = child!!.height
        return super.onLayoutChild(parent, child, layoutDirection)
    }

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout,
                            child: BottomNavigationView, directTargetChild: View, target: View,
                            axes: Int, type: Int): Boolean {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomNavigationView,
                                target: View, dxConsumed: Int, dyConsumed: Int,
                                dxUnconsumed: Int, dyUnconsumed: Int,
                                @ViewCompat.NestedScrollType type: Int) {
        if (dyConsumed > 0) {
            slideDown(child)
        } else if (dyConsumed < 0) {
            slideUp(child)
        }
    }

    private fun slideUp(child: BottomNavigationView) {
        child.clearAnimation()
        child.animate().translationY(0f)
    }

    private fun slideDown(child: BottomNavigationView) {
        child.clearAnimation()
        child.animate().translationY(height.toFloat())
    }
}

And include the following in OnCreate of BottomBar.kt

 val layoutParams = bottomNavigation.getLayoutParams() as CoordinatorLayout.LayoutParams
 layoutParams.behavior = BottomNavigationBehavior()
  • If you would like to disable the shifting animation in BottomNavigation, you can include the following class to accomplish it.
class BottomNavigationHelper {
    companion object {
        @JvmStatic
        fun disableShiftMode(view: BottomNavigationView) {
            val menuView = view.getChildAt(0) as BottomNavigationMenuView
            try {
                val shiftingMode = menuView.javaClass.getDeclaredField("mShiftingMode")
                shiftingMode.isAccessible = true
                shiftingMode.setBoolean(menuView, false)
                shiftingMode.isAccessible = false
                for (i in 0 until menuView.childCount) {
                    val item = menuView.getChildAt(i) as BottomNavigationItemView
                    item.setShiftingMode(false)
                    // set once again checked value, so view will be updated
                    item.setChecked(item.itemData.isChecked)
                }
            } catch (e: NoSuchFieldException) {
                Log.e("BottomNavigationHelper", "Unable to get shift mode field", e)
            } catch (e: IllegalAccessException) {
                Log.e("BottomNavigationHelper", "Unable to change value of shift mode", e)
            }

        }
    }
}

And include the following in OnCreate of BottomBar.kt

BottomNavigationHelper.disableShiftMode(bottomNavigation)

Run Application:

       (a) Simple                                         (b) Hide/Show                               (c) Shift disabled

Comments

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

RecyclerView with different number of columns using SpanSizeLookup

Exploring Android Slices - JetPack

Using Camera in Android - Kotlin

Databinding in RecyclerView - Android - Kotlin

Using RxJava, Retrofit in Android - Kotlin

Room with LiveData, ViewModel - Android Architecture Components - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Braintree Integration in Android - Kotlin

Android JetPack - Scheduling Tasks with WorkManager