Android Bottom Navigation Custom하기
Android/Custom

Android Bottom Navigation Custom하기




일반적으로 Bottom Navigation은 아래와 같은 모양새이다.

 

 

그러나 필요에 따라 아래와 같이 가운데가 움푹 파이게 하고싶은 경우가 있을 수 있다.

 

 

 

이것을 구현하기 위해 Bottom Navigation의 가운데 부분을 반원으로 깎는 방법으로 구현하는 방법이 있다.

 

1) 어떻게 구현하나요?  

 

in kotlin

import com.google.android.material.bottomnavigation.BottomNavigationView
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import android.graphics.*


class CustomBottomNavigationView : BottomNavigationView {
    private var mPath: Path = Path()
    private var mPaint: Paint = Paint()

    private val CURVE_CIRCLE_RADIUS = 190 / 2

    private val mFirstCurveStartPoint = Point()
    private val mFirstCurveEndPoint = Point()
    private val mFirstCurveControlPoint1 = Point()
    private val mFirstCurveControlPoint2 = Point()

    private var mSecondCurveStartPoint = Point()
    private val mSecondCurveEndPoint = Point()
    private val mSecondCurveControlPoint1 = Point()
    private val mSecondCurveControlPoint2 = Point()
    private var mNavigationBarWidth: Int = 0
    private var mNavigationBarHeight: Int = 0

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init()
    }

    constructor(context: Context) : super(context) {
        init()
    }

    private fun init() {
        mPaint.style = Paint.Style.FILL_AND_STROKE
        mPaint.color = ContextCompat.getColor(context, R.color.white)
        setBackgroundColor(Color.TRANSPARENT)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        
        mNavigationBarWidth = width
        mNavigationBarHeight = height
        
        mFirstCurveStartPoint.set(mNavigationBarWidth / 2 - CURVE_CIRCLE_RADIUS * 2 - CURVE_CIRCLE_RADIUS / 3, 0)
        mFirstCurveEndPoint.set(mNavigationBarWidth / 2, CURVE_CIRCLE_RADIUS + CURVE_CIRCLE_RADIUS / 4)
        mSecondCurveStartPoint = mFirstCurveEndPoint
        mSecondCurveEndPoint.set(mNavigationBarWidth / 2 + CURVE_CIRCLE_RADIUS * 2 + CURVE_CIRCLE_RADIUS / 3, 0)
        
        mFirstCurveControlPoint1.set(
            mFirstCurveStartPoint.x + CURVE_CIRCLE_RADIUS + CURVE_CIRCLE_RADIUS / 4,
            mFirstCurveStartPoint.y
        )
        
        mFirstCurveControlPoint2.set(
            mFirstCurveEndPoint.x - CURVE_CIRCLE_RADIUS * 2 + CURVE_CIRCLE_RADIUS,
            mFirstCurveEndPoint.y
        )

        mSecondCurveControlPoint1.set(
            mSecondCurveStartPoint.x + CURVE_CIRCLE_RADIUS * 2 - CURVE_CIRCLE_RADIUS,
            mSecondCurveStartPoint.y
        )
        mSecondCurveControlPoint2.set(
            mSecondCurveEndPoint.x - (CURVE_CIRCLE_RADIUS + CURVE_CIRCLE_RADIUS / 4),
            mSecondCurveEndPoint.y
        )

        mPath.reset()
        mPath.moveTo(0F, 0F)
        mPath.lineTo(mFirstCurveStartPoint.x.toFloat(), mFirstCurveStartPoint.y.toFloat())

        mPath.cubicTo(
            mFirstCurveControlPoint1.x.toFloat(), mFirstCurveControlPoint1.y.toFloat(),
            mFirstCurveControlPoint2.x.toFloat(), mFirstCurveControlPoint2.y.toFloat(),
            mFirstCurveEndPoint.x.toFloat(), mFirstCurveEndPoint.y.toFloat()
        )

        mPath.cubicTo(
            mSecondCurveControlPoint1.x.toFloat(), mSecondCurveControlPoint1.y.toFloat(),
            mSecondCurveControlPoint2.x.toFloat(), mSecondCurveControlPoint2.y.toFloat(),
            mSecondCurveEndPoint.x.toFloat(), mSecondCurveEndPoint.y.toFloat()
        )

        mPath.lineTo(mNavigationBarWidth.toFloat(), 0F)
        mPath.lineTo(mNavigationBarWidth.toFloat(), mNavigationBarHeight.toFloat())
        mPath.lineTo(0F, mNavigationBarHeight.toFloat())
        mPath.close()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawPath(mPath, mPaint)
    }
}

Hot Ez Ex) 위 코드를 복사하고 붙여넣기 하여 사용하면 된다

 

위 코드를 자신의 프로젝트 내로 가져왔다면, BottomNavigation View 대신 위 CustomView를 사용하면 된다

<FullPackageName.CustomBottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"/>

 

2) 그럼 메뉴들은 어떻게 구현하나요?

 

기본적으로 Bottom Navigation View GuideLine에 따르면 3개 이상의 메뉴가 적합함을 명시하고 있다.

https://material.io/design/components/bottom-navigation.html#usage

 

그러나 위의 상황에서는 2개의 메뉴만을 요구하며, 가운데 자리는 FAB가 차지하고 있다. 

실제로 2개의 메뉴만을 구현해보면, 메뉴들이 제대로 된 Width를 차지하지 못하며, 클릭 이펙트 위치가 마음에 들지 않는 등 마음에 들지 않는 상황이 존재할 것이다.

 

나는 그래서 3개의 메뉴를 만들고, 가운데 하나의 메뉴를 완전 비활성화 하는 방식으로 처리하였다.

 

in menu - bottom_navigation.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/action_home"
            android:icon="@drawable/ic_home"
            android:title="@string/title_home"/>

    <item
            android:id="@+id/action_empty"
            android:title=""
            android:enabled="false"
            android:checkable="false"/>

    <item
            android:id="@+id/action_menu"
            android:icon="@drawable/ic_menu"
            android:title="@string/title_menu"/>
</menu>

Hot Ez Ex) 위 menu는 bottom_navigation_id.inflateMenu(R.menu.bottom_navigation)로 BottomNavigationView와 연결한다.

 

3) Bottom Navigation View 위에 Floating Action Button은 어떻게 구현하나요?

 

이를 제대로 구현하지 않는다면 비활성화 처리한 Bottom Navigation의 2번째 메뉴 자리가 클릭 이벤트를 잡아먹어버려 2번째 메뉴 자리 위에 있는 FAB의 아래부분 (2번째 메뉴와 겹쳐있는 부분)이 클릭 이벤트를 받지 못한다.

 

단순 translationZ로 index-z 처리를 하여도 되지 않는 경우가 존재한다. 그렇지만 FAB와 BNV의 Layout을 나누어서 처리하면 말끔하게 처리된다.

 

in XML

...
<androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

      <FrameLayout
              android:id="@+id/layout_bottom"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent">

          <FullPackageName.CustomBottomNavigationView
                  android:id="@+id/bottom_navigation"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:background="#FFFFFF"
                  android:layout_gravity="bottom"
                  android:translationZ="1dp"
                  app:labelVisibilityMode="labeled"/>

      </FrameLayout>

      <LinearLayout
              android:id="@+id/layout_bottom_2"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:gravity="center"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent">

          <com.google.android.material.floatingactionbutton.FloatingActionButton
                  android:id="@+id/fab"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:clickable="true"
                  android:focusable="true"
                  android:layout_gravity="center"
                  android:elevation="3dp"
                  android:translationZ="2dp"/>

      </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

위 설명만으로 부족한 부분 (style 관련, material 버전 관련 등)이 분명히 있을 것이지만, 그렇게 어렵지 않게 처리

가능한 부분이니 잘 구현해보자

 

ETC) Floating Action Button 내에 이미지 + 글자를 추가하고 싶어요

 

추천 오픈소스를 확인하자

 

[Open Source] - 안드로이드 추천 오픈소스3) TextFloatingActionButton

 

안드로이드 추천 오픈소스3) TextFloatingActionButton

https://github.com/EdSergeev/TextFloatingActionButton EdSergeev/TextFloatingActionButton Android floating action button (fab) with text. Contribute to EdSergeev/TextFloatingActionButton development..

zladnrms.tistory.com