[Android] Navigation Component 시작하기

이제일·2022년 7월 14일
0

Android

목록 보기
3/15
post-thumbnail

Navigation은 하나의 Activity 안에서 여러 Fragment간의 전환에 중점을 두고 설계가 되었습니다.
이러한 Fragment의 전환에 있어 보일러플레이트를 제거하고, 데이터 전달과 스택 관리에있어 이점이 있습니다.

장점

공식 문서에서는 다음과 같이 설명하였습니다.

  • 프래그먼트 트랜잭션 처리.
  • 기본적으로 '위로'와 '뒤로' 작업을 올바르게 처리.
  • 애니메이션과 전환에 표준화된 리소스 제공.
  • 딥 링크 구현 및 처리.
  • 최소한의 추가 작업으로 탐색 UI 패턴(예: 탐색 창, 하단 탐색) 포함.
  • Safe Args - 대상 사이에서 데이터를 탐색하고 전달할 때 유형 안정성을 제공하는 그래프 플러그인입니다.
  • ViewModel 지원 - 탐색 그래프에 대한 ViewModel을 확인해 그래프 대상 사이에 UI 관련 데이터를 공유합니다.

또한 아래와 같이 Navigation Editor에서 미리보기를 지원합니다.


Start Navigation

리소스 생성

dependencies 설정
아래와 같이 dependencies를 설정합니다.

build.gradle(module)

dependencies{
	...
    implementation 'androidx.navigation:navigation-fragment:2.5.0'
    ...
}

프래그먼트 생성
아래 사진과 같이 New -> Fragment 를 통해서 생성할 수 있습니다.

저는 아래와 같이 프래그먼트와 해당 레이아웃 파일을 만들었습니다.
OneFragment.kt

package com.example.jetpacksample

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.jetpacksample.databinding.FragmentOneBinding

class OneFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding = FragmentOneBinding.inflate(layoutInflater)

        return binding.root
    }


}

fragment_one.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
    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"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".OneFragment">

    <TextView
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="it is one Activity\n" />


    <Button
        android:id="@+id/btnOneByOne"
        android:text="go to One Activity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btnTwoByOne"
        android:text="go to Two Activity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btnThreeByOne"
        android:text="go to Three Activity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</androidx.appcompat.widget.LinearLayoutCompat>

네비게이션 그래프 생성
아래와 같이 res폴더에서 new -> resource file를 통해 탐색 그래프를 생성합니다

에디터의 그림과 같은 표시를 클릭하면 프래그먼트를 생성하고 화살표로 연결지어 액션을 생성합니다.
스택 관리와 애니메이션을 설정할 수 있습니다.

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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/menu_one">

    <fragment
        android:id="@+id/menu_one"
        android:name="com.example.jetpacksample.OneFragment"
        android:label="fragment_one"
        tools:layout="@layout/fragment_one" >
        <action
            android:id="@+id/action_oneFragment_to_twoFragment"
            app:destination="@id/menu_two" />
        <action
            android:id="@+id/action_oneFragment_to_threeFragment"
            app:destination="@id/menu_three" />
    </fragment>
    <fragment
        android:id="@+id/menu_two"
        android:name="com.example.jetpacksample.TwoFragment"
        android:label="fragment_two"
        tools:layout="@layout/fragment_two" >
        <action
            android:id="@+id/action_twoFragment_to_oneFragment"
            app:destination="@id/menu_one" />
        <action
            android:id="@+id/action_twoFragment_to_threeFragment"
            app:destination="@id/menu_three" />
    </fragment>
    <fragment
        android:id="@+id/menu_three"
        android:name="com.example.jetpacksample.ThreeFragment"
        android:label="fragment_three"
        tools:layout="@layout/fragment_three" >
        <action
            android:id="@+id/action_threeFragment_to_oneFragment"
            app:destination="@id/menu_one" />
        <action
            android:id="@+id/action_threeFragment_to_twoFragment"
            app:destination="@id/menu_two" />
    </fragment>
</navigation>

여기서 각 fragment의 id 값은 menu에서 설정한 id값과 동일해야 작동한다.

프래그먼트 화면 설정
생성한 탐색 그래프를 띄워줄 프래그먼트 화면을 메인 레이아웃에 설정합니다.

activity_main.xml

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph"
        app:defaultNavHost="true"/>

defaultNavHost : NavHostFragment가 시스템 뒤로 버튼을 가로챕니다. 하나의 NavHost만 기본값으로 지정할 수 있습니다. 동일한 레이아웃에 여러 호스트가 있다면(예: 창이 2개인 레이아웃) 한 호스트만 기본 NavHost로 지정해야 합니다.
이후에 나올 바텀네비게이션과 서랍UI의 경우 뒤로가기시 NavHost로 이동할지 여부입니다
navGraph : 생성한 탐색그래프 파일
name : navHostFragment임을 명시

화면 전환 구현

앞서 탐색 그래프에서 생성했던 액션을 인자로 프래그먼트를 전환합니다.

다음과 같이 작성했던 OneFragment에 클릭리스너를 추가해서
navController에 navigate를 이용해서 이동할 수 있습니다.

binding.btnTwoByOne.setOnClickListener{
	findNavController().navigate(R.id.action_oneFragment_to_twoFragment)
}

Bottom Navigation

네비게이션으로 만든 프래그먼트 및 그래프를 하단에 메뉴로 표기할 수 있습니다.

dependencies 설정
네비게이션을 이용한 UI를 만드려면 다음과 같은 의존성을 주입합니다.

build.gradle(module)

dependencies{
	...
    implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
    ...
}

리소스 생성

먼저 하단에 들어갈 아이콘과 이름을 설정할 메뉴 파일을 생성합니다.
res -> new -> Menu Type 설정으로 진행할 수 있습니다.

res/menu/nav_menu.xml

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

    <item
        android:id="@+id/menu_one"
        android:icon="@drawable/ic_baseline_music_note_24"
        android:title="one" />

    <item
        android:id="@+id/menu_two"
        android:icon="@drawable/ic_baseline_place_24"
        android:title="two" />

    <item
        android:id="@+id/menu_three"
        android:icon="@drawable/ic_baseline_newspaper_24"
        android:title="three" />

</menu>

그리고 메인 레이아웃에 바텀 네비게이션을 추가하고 메뉴를 설정합니다

activity_main.xml

 <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:menu="@menu/nav_menu" />

바텀 네비게이션 설정

아래와 같이 host fragment를 지정해서 네비게이션 탐색 그래프가 활동할 위치를 지정해줍니다.

activity_main.xml

<fragment
        android:id="@+id/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navBottom"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

바텀 네비게이션 컨트롤러 설정
아래와 같이 컨트롤러를 설정해줌으로써 작동합니다.
바텀 네비게이션은 이동할 때 스택이 쌓이지 않으며 xml에서 defaultNavHost속성을 true 했을 때 host fragment로 돌아갑니다.

MainActivity.kt

val navController = findNavController(R.id.fragmentContainerView)
binding.navBottom.setupWithNavController(navController)

host fragment를 <Fragment ... /> 가 아닌 androidx.fragment.app.FragmentContainerView등 다른 레이아웃을 이용했을 경우

navController를 (supportFragmentManager.findFragmentById(R.id.호스트Fragment) as NavHostFragment).navController 로 지정해주어야 navController를찾을수 있습니다.


Drawer Navigation

DrawerLayout은 drawer(서랍)이라는 이름에서 알 수 있듯이

평소에는 화면의 한쪽에 숨겨져 있다가 사용자가 액션을 취하면 화면에 나타날 수 있도록 하는 레이아웃입니다.

리소스 준비

레이아웃을 위해서 먼저 res/values/themes.xml에서 액션바를 지우고 커스텀 툴바를 넣는다.
보통 서랍 UI를 여는 버튼은 액션바에 존재하기에 해당 버튼의 배치 및 아래 사진과 같이 서랍이 액션바 밑으로 가게되어 시인성이 떨어진다.

기본 액션바 지우기
res/values/themes.xml

    <style name="Theme.JetpackSample" parent="Theme.MaterialComponents.DayNight.NoActionBar">

새로운 툴바 생성
activity_main.xml

	...

	<androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:background="@color/teal_700"/>
	
    ...

Header layout 설정

위에서 표시한 부분인 drawer의 header 부분을 보여줄 레이아웃을 생성한다. 해당 레이아웃은 이후 서랍UI에 속성으로 적용된다

layout/nav_drawer_header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:background="@color/teal_200"
    android:gravity="bottom"
    android:orientation="vertical"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="16dp"
        app:srcCompat="@mipmap/ic_launcher_round" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="Android Studio"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="4dp"
        android:text="android.studio@android.com" />
</LinearLayout>

서랍 UI 설정

Drawer Navigation을 레이아웃에 설정하려면 최상위 레이아웃을 DrawerLayout으로 설정하고 Drawer navigation이 기존 루트 레이아웃까지 덮을 수 있도록 맨 아래에 추가한다.

activity_main.xml

<androidx.drawerlayout.widget.DrawerLayout
	...>
        
        <!--기존 루트 레이아웃-->
        <androidx.constraintlayout.widget.ConstraintLayout
        	...>
            	...
 		</androidx.constraintlayout.widget.ConstraintLayout>
        
         <com.google.android.material.navigation.NavigationView
            android:id="@+id/navDrawer"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:fitsSystemWindows="true"
            app:headerLayout="@layout/nav_drawer_header"
            app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

headerLayout 속성에 이전에 만들었던 header layout파일을 설정한다
menu 속성은 header 밑에 나올 컨텐츠 목록으로 기존에 쓰던 메뉴파일과 동일하게 설정
layout_gravity속성으로 왼쪽 또는 오른쪽에서 나오도록 지정

이제 서랍UI를 나오게 할 메뉴버튼을 툴바에 적용하자
메인 액티비티에서 다음의 코드로 버튼을 생성한다.

setSupportActionBar(binding.toolbar) // 액션바 등록
supportActionBar?.setDisplayHomeAsUpEnabled(true) // 왼쪽 상단 버튼 만들기
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_menu_24) //왼쪽 상단 버튼 아이콘 지정

이제 navController에 drawerNavigation을 설정해서 동기화 해보도록 한다

binding.navDrawer.setupWithNavController(navController)

이제 메뉴 버튼을 누를때마다 서랍UI가 열릴 수 있도록 툴바에 리스너를 onOptionsItemSelected 메서드를 오버라이드해서 등록한다

override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            android.R.id.home -> binding.mainActivityLayout.openDrawer(Gravity.START)
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onBackPressed() {
        if(binding.mainActivityLayout.isDrawerOpen(binding.navDrawer))
            binding.mainActivityLayout.closeDrawer(Gravity.START)
        else
            super.onBackPressed()
    }

샘플 코드 및 참고 사이트

샘플 코드
https://github.com/WorldOneTop/AndroidJetpackSample/tree/Navigation

참고 사이트
https://developer.android.com/guide/navigation
https://medium.com/nbt-tech/android-navigation-%EC%93%B8%EA%B9%8C%EB%A7%90%EA%B9%8C-964388a562e0
https://material.io/components/navigation-drawer

작동 영상

Navigation 영상

profile
세상 제일 이제일

0개의 댓글