HOME > android > jetpack

Android Jetpack - Navigation으로 Drawer 구현

JSFollow21 Nov 2018

지난 글 Android Jetpack Navigation으로 화면 전환 구현에 이어 Navigation을 이용하여 Drawer를 구현하는 방법을 알아보겠습니다. 지난 번에는 네이게이션 그래프로 순환 구조를 정의하였고, AppBar에 Label을 출력하도록 하였습니다.

네비게이션은 Drawer를 지원하는데요. 이번에는 네비게이션을 이용하여 Drawer를 구현해보겠습니다. 간단히 샘플앱을 만들어보죠.

프로젝트 생성

Basic Activity로 생성합니다. androidx navigation drawer

KotlinUse AndroidX artifacts를 선택해주세요. (Android Studio 버전이 3.4 미만이라면 이 기능이 없습니다. 마이그레이션 기능을 이용하거나 직접 의존성을 변경해줘야 합니다) androidx navigation drawer

AndroidX로 프로젝트를 생성하지 않았다면, 메뉴에서 [Refactor] -> [Migrate to AndroidX...] 를 누르시면 AndroidX를 사용하는 프로젝트로 마이그레이션이 됩니다.

Navigation을 사용하려면 앱 gradle에서 dependency를 추가해야 합니다.

dependencies {
  ....
  implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha07'
  implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha07'
  implementation "com.google.android.material:material:1.0.0"
}

View 객체 생성

화면의 단위는 fragment입니다. MainFragmentSubFragment 두개의 화면을 생성하고 Drawer에서 두개의 화면으로 진입할 수 있도록 구현하겠습니다.

MainFragment.kt
class MainFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_main, container, false)

        return view
    }
}
SubFragment.kt
class SubFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_sub, container, false)

        return view
    }
}
fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="Main fragment"
            android:textSize="24sp"/>
</FrameLayout>
fragment_sub.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="Sub fragment"
            android:textSize="24sp"/>
</FrameLayout>

Host 설정

Drawer에서 메뉴를 선택했을 때 MainActivity의 화면이 변경되어야 하기 때문에, MainActivity의 fragment를 Host로 설정하려고 합니다. activity_main.xml에서 CoordinatorLayout를 사용하지 않고 DrawerLayout로 변경해야 합니다. 그리고 fragment를 추가하고 호스트로 설정을 해줍니다. 모든 수정이 끝난 코드는 다음과 같습니다.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<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"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <com.google.android.material.appbar.AppBarLayout
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:theme="@style/AppTheme.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/AppTheme.PopupOverlay"/>

        </com.google.android.material.appbar.AppBarLayout>

        <fragment
                android:id="@+id/nav_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>

    </LinearLayout>

    <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_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email"/>

</androidx.drawerlayout.widget.DrawerLayout>

Navigation graph 정의

그래프는 navigation 태그 아래에 MainFragmentSubFragment를 추가했습니다. 그리고 처음 보여지는 화면은 MainFragment로 하고 싶기 때문에 startDestination는 MainFragment로 설정했습니다.

/res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
    app:startDestination="@+id/main_fragment">

    <fragment
        android:id="@+id/main_fragment"
        android:name="com.codechacha.navi.drawer.MainFragment"
        android:label="Main fragment"
        tools:layout="@layout/fragment_main" >
    </fragment>

    <fragment
        android:id="@+id/sub_fragment"
        android:name="com.codechacha.navi.drawer.SubFragment"
        android:label="Sub fragment"
        tools:layout="@layout/fragment_sub"/>

</navigation>

이제 빌드는 가능하겠네요. 실행해보면 MainFragment 화면이 보입니다. 하지만 Drawer가 구현되지 않아 SubFragment로 이동할 수 없네요.

androidx navigation drawer

Drawer 구현

Drawer를 구현하려면 activity_main.xmlNavigationView를 정의해야 합니다. NavigationView를 추가한 activity_main.xml의 코드는 다음과 같습니다.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <com.google.android.material.appbar.AppBarLayout
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:theme="@style/AppTheme.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/AppTheme.PopupOverlay"/>

        </com.google.android.material.appbar.AppBarLayout>

        <fragment
                android:id="@+id/nav_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>

    </LinearLayout>

    <com.google.android.material.navigation.NavigationView
            android:id="@+id/navigation_view"
            style="@style/Widget.MaterialComponents.NavigationView"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            app:headerLayout="@layout/nav_header"
            app:menu="@menu/menu_navigation"/>

    <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_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email"/>

</androidx.drawerlayout.widget.DrawerLayout>

Drawer(NavigationView)에서 보여지는 View로, nav_headermenu_navigation도 만들어줘야 합니다.

nav_header.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="192dp"
    android:background="?attr/colorPrimaryDark"
    android:gravity="bottom"
    android:orientation="vertical"
    android:padding="16dp"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:text="Codechacha"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>

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

    <item
        android:id="@+id/main_fragment"
        android:title="Main fragment"/>

    <item
        android:id="@+id/sub_fragment"
        android:title="Sub fragment"/>

</menu>

이제 xml은 모두 만들었고 MainActivity.kt에 코드를 조금 추가해주면 됩니다.

지난 글에서 AppBarConfiguration를 사용해보았는데요. drawer와 함께 사용하려면 인자로 drawer_layout도 전달해야 합니다.

navController = Navigation.findNavController(this, R.id.nav_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph, drawer_layout)

NavController와 AppBarConfiguration를 생성하고 ActionBar에 등록을 해줍니다. 추가로 navigation_view에 NavController를 설정해줍니다.

setSupportActionBar(toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)

// Set up navigation menu
navigation_view.setupWithNavController(navController)

뒤로가기 버튼과 Back key를 눌렀을 때 이전 화면으로가기 위해 다음과 같은 코드를 추가해야 합니다.

override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

override fun onBackPressed() {
    if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
        drawer_layout.closeDrawer(GravityCompat.START)
    } else {
        super.onBackPressed()
    }
}

완성된 MainActivity.kt는 다음과 같습니다.

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navController = Navigation.findNavController(this, R.id.nav_fragment)
        appBarConfiguration = AppBarConfiguration(navController.graph, drawer_layout)

        // Set up ActionBar
        setSupportActionBar(toolbar)
        setupActionBarWithNavController(navController, appBarConfiguration)

        // Set up navigation menu
        navigation_view.setupWithNavController(navController)

        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
            drawer_layout.closeDrawer(GravityCompat.START)
        } else {
            super.onBackPressed()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

실행해보면 Drawer가 동작하고 화면전환이 되는 것을 볼 수 있습니다.

androidx navigation drawer

참고