Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

yourginieus

Navigation : Navigation components, drawer 등 본문

Android/Android Kotlin 기초 실습 정리

Navigation : Navigation components, drawer 등

EOJIN 2022. 10. 26. 00:04

1. 프로젝트에 Navigation components 추가하기

  • Navigation component란, 앱의 화면 간에 전달되는 복잡한 탐색, 전환 애니메이션, deep linking, 컴파일 시간 확인 argument를 관리할 수 있는 라이브러리!

STEP 1 : navigation dependencies 추가

  • 네비게이션 라이브러리를 사용하려면 그래들 파일에 종속성을 추가해야 함
  • Gradle Scripts 폴더 > 프로젝트 수준의 build.gradle 파일
  • 맨 위의, 다른 ext 변수들이 있는 곳에 navigationVersion 변수 추가
    • 최신 navigation 버전을 확인하기 위해서는 Android developer documentation의 Declaring dependencies 확인!
ext {
        ...
        navigationVersion = "2.3.0"
        ...
    }
  • 이제 모듈 수준의 build.gradle 파일을 열고 종속성 추가하기
    • navigation-fragment-ktx, navigation-ui-ktx
dependencies {
  ...
  implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
  implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
  ...
}
  • Sync 하기!

STEP 2 : 프로젝트에 Navigation graph 추가

  • Android pane 에서 res 폴더 우클릭 한 후, New > Android Resource File 선택
  • 다이얼로그가 뜨면 Resource type으로 Navigation 선택
  • File Name은 navigation 으로 설정
  • Chosen qualifiers box가 비어있는지 확인한 후, OK 클릭
  • res > navigation 폴더에 navigation.xml 이라는 새로운 파일이 생성 됨!

  • res > navigation > navigation.xml 파일을 열고 Design 탭 클릭 -> Navigation Editor 열기
  • No NavHostFragments found 메세지가 보일 것

STEP 3 : NavHost Fragment 생성하기

  • navigation host fragment는 navigation graph 내의 host로서 동작 함
  • navigation host fragment는 보통 NavHostFragment 로 이름 지어짐!
  • 사용자가 네비게이션 그래프에 정의되어 있는 destinations 사이를 이동하면, navigation host Fragment는 필요에 따라 fragment를 교환 함
  • 또한 Fragment는 적절한 Fragment back stack 을 작성 및 관리 함

  • res > layout > activity_main.xml 을 열고, 기존에 존재하는 fragment의 이름을 androidx.navigation.fragment.NavHostFragment 로 변경
  • ID는 myNavHostFragment 로 변경  
  • Navigation host Fragment는 사용할 Navigation graph resource를 알아야 함
  • 프래그먼트에 app:navGraph 를 추가하고 navigation graph resource 로 지정 -> @navigation/navigation
  • 프래그먼트에 app:defaultNavHost 속성을 추가하고 "true" 로 설정
    • 이제 이 Navigation host가 default host이며 system의 Back button을 intercept 함!
<!-- The NavHostFragment within the activity_main layout -->
            <fragment
                android:id="@+id/myNavHostFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:navGraph="@navigation/navigation"
                app:defaultNavHost="true" />

2. Navigation graph에 fragment 추가하기

STEP 1 : Navigation graph에 2개의 fragment를 추가하고 action으로 연결하기

  • navigation resource 폴더에서 navigation.xml 열고
  • Navigation Editor에서 New Destination button 클릭
    • fragment와 activity의 리스트가 표시 됨
    • 원하는 프래그먼트 선택하기
      • 사용자가 처음 앱을 열었을 때 보이는 순으로 선택하면 좋음!

  • preview에 "Preview Unavailable" 메세지가 뜨면, Code 탭을 클릭하고 fragment 요소가 tools:layout="@layout/fragment_game" 를 포함하도록 하기(fragment_game은 프래그먼트 이름일 뿐)
<!-- The game fragment within the navigation XML, complete with tools:layout. -->
<fragment
   android:id="@+id/gameFragment"
   android:name="com.example.android.navigation.GameFragment"
   android:label="GameFragment"
   tools:layout="@layout/fragment_game" />
  • Design tab에서, 두 번째 추가한 fragment를 오른쪽으로 이동해 첫 번째 프래그먼트와 겹치지 않게 함

  • preview에서, 첫 번째 프래그먼트 위에 pointer를 놓으면 fragment view의 오른쪽에 원형 연결점이 나타날 것
    • 그 점을 클릭하여 다른 프래그먼트와 연결
    • 그러면 그 두 개의 프래그먼트를 접속하는 액션이 생성됨
  • Action의 속성을 확인하기 위해서는 그 화살표를 누르면 [Attributes] pane을 보면 됨
    • action의 ID가 action_titleFragment_to_gameFragment 로 설정됐음을 알 수 있음

SETP 2 : Button에 Click handler 추가

  • .kt 파일에서, onCreateView()의 return 전에, 버튼의 setOnClickListener() 로 위에서 만들어 준 액션 추가
//The complete onClickListener with Navigation
binding.playButton.setOnClickListener { view : View ->
       view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}

3. Conditional Navigation : 조건부 네비게이션 추가하기

  • 특정 context에서만 사용할 수 있는 네비게이션
    • ex) 사용자의 로그인 여부에 따라 앱의 흐름이 다른 경우에 사용

STEP 1 : 사용할 화면을 모두 네비게이션 그래프에 추가하기

  • 위에서 했던 것처럼 사용할 화면들 추가하기
  • preview 영역에서 다른 프래그먼트와 겹치지 않도록 위치 조정
  • 조건부의 경우 수평으로 나열하는 게 아닌 수직으로 나열함

STEP 2 : 게임 프래그먼트와 게임 결과 프래그먼트 연결

  • 게임 프래그먼트를 이겼을 때와 졌을 때의 프래그먼트 두 개에 모두 연결
  • Layout Editor의 preview 영역에서, 원형 연결점이 나타날 때까지 게임 프래그먼트 위에 pointer 놓기
  • 연결 지점을 클릭하여 game-over fragment와 연결 -> 게임 프래그먼트와 게임오버 프래그먼트를 연결하는 액션 생성
  • 마찬가지로 win-fragment와 연결 -> 게임 프래그먼트외 win 프래그먼트를 연결하는 액션 생성
  • preview에서 프래그먼트들을 연결하는 선 위에 포인터를 놓으면 action의 ID가 자동으로 할당 됨

STEP 3 : Add code to navigate from one Fragment to the next

  • 클릭 시 어느 프래그먼트로 이동할지에 대한 조건을 따지는 코드 작성
binding.submitButton.setOnClickListener @Suppress("UNUSED_ANONYMOUS_PARAMETER")
        { 
              ...
                // answer matches, we have the correct answer.
                if (answers[answerIndex] == currentQuestion.answers[0]) {
                    questionIndex++
                    // Advance to the next question
                    if (questionIndex < numQuestions) {
                        currentQuestion = questions[questionIndex]
                        setQuestion()
                        binding.invalidateAll()
                    } else {
                        // We've won!  Navigate to the gameWonFragment.
                    }
                } else {
                    // Game over! A wrong answer sends us to the gameOverFragment.
                }
            }
        }
  • 게임에서 이겼을 경우 win fragment로 이동하는 코드 추가 : navigation.xml 파일에 정의된 액션 이름 유의
// We've won!  Navigate to the gameWonFragment.
view.findNavController()
   .navigate(R.id.action_gameFragment_to_gameWonFragment)
  • 게임에서 졌을 경우 gameOver fragment로 이동하는 코드 추가
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().
   navigate(R.id.action_gameFragment_to_gameOverFragment)
  • 근데 이 때 결과 화면에서 뒤로가기를 누르면 질문 화면으로 이동함 -> 이상적으로는 앱의 메인 화면으로 돌아가야 하는데!

4. Back button's destination

  • 안드로이드 시스템은 사용자가 어느 화면에서 어느 화면으로 이동하는지 추적함
  • 새로운 화면으로 이동할 때마다 안드로이드는 현재(이동하기 이전)화면을 back stack 에 추가함
  • 사용자가 Back 버튼을 누르면, 앱은 Back Stack의 맨 위에 있는 destination으로 이동함
    • 기본적으로 back stack의 맨 위는 사용자가 마지막으로 본 화면
  • 지금까지는 네비게이션 컨트롤러가 back stack을 대신 처리해 왔음! 사용자가 destination으로 이동하면 안드로이드는 기존 destination을 back stack에 추가 함
  • 그래서 게임 결과에서 뒤로가기를 누르면 메인화면이 아닌 질문 화면으로 이동하게 되는 것
  • Navigation action은 back stack을 수정할 수 있음!
    • game fragment에서 이동하는 action을 변경하여 action이 back stack에서 지워지도록 설정할 수 있음

STEP 1 : Navigation action에 pop 추가

  • action의 "pop" behavior를 설정하는 것으로 백스택을 관리할 수 있음
  • action의 popUpTo 속성 : navigating 하기 전에 해당 화면을 back stack에서 pops up 시킴
  • popUpToInclusive 속성이 false 또는 not set 상태
    • popUpTo는 지정된 destination까지의 destination을 삭제하지만, 지정된 destination은 백스택에 남김
  • popUpToInclusive가 true로 설정되어 있다면
    • popUpTo 속성은 지정된 destination을 포함한 모든 destination을 백스택에서 삭제 함
  • popUpToInclusive가 true이고 popUpTo가 app의 시작 위치로 설정되어 있다면
    • 모든 destination을 백스택에서 제거함 -> 뒤로 버튼을 누르면 사용자는 앱에서 완전히 빠져나옴
  • 이 작업들은 action에서 Layout Editor의 Attributes pane의 Pop To 필드를 사용하여 수행 됨
  1. res > navigation에서 navigation.xml을 열고, layout editor에서 navigation graph가 표시되지 않으면 Design 탭 클릭
  2. gameFragment에서 gameOverFragment로 navigating 할 action 선택
    • Attributes Pane에서 popUpTo 설정 : popUpToInclusive를 true로 설정
    • 이는 네비게이션 컴포넌트에 backstack에서 나 start fragment를 포함한 fragment들을 모두 삭제하라고 지시함
    • gameFragment가 백스택에서 없어지는 거니까, gameOver에서 뒤로가기를 누르면 game의 전인 title로 돌아감!
  3. gameFragment에서 gameWonFragment로 navigating 할 action 선택
    • Attributes Pane에서 popUpTo 설정 : popUpToInclusive를 true로 설정

=> 이기든 지든 뒤로가기 버튼을 누르면 TitleFragment 로 이동함!

STEP 2 : 네비게이션 액션 추가 및 onClick 핸들러 추가

  • 게임이 끝났을 때, Try Again을 누르면 GameFragment로 이동하고, 여기서 뒤로가기 버튼을 누르면 TitleFragment 를 보여주는 총 2개의 플로우가 있음
  • 이 user flow를 생성하기 위해, popUpTo 속성을 사용하여 백스택 관리
  1. navigation.xml 파일 안에서, gameOverFragment를 gameFragment 로 이동하는 action 추가 연결.
    • action의 ID에 있는 프래그먼트 이름이 xml에 있는 프래그먼트 이름과 일치하도록 유의!
    • 예) action ID : action_gameOverFragment_to_gameFragment
  2. Attributes pane에서, 액션의 popUpTo 속성을 titleFragment 로 설정
  3. popUpToInclusive 는 체크 해제 : titleFragment가 백스택에서 삭제되길 원하지 않기 때문! titleFragment를 제외 한 모든 것이 백스택에서 삭제 됨
  4. gameWonFragment에서 gameFragment로 가는 action도 추가 연결
  5. 그리고 popUpTo 및 popUpToInclusive를 위의 경우와 같이 설정

  • 그리고 gameOverFragment와 gameWonFragment의 각 버튼에 액션 추가하기
// Add OnClick Handler for Try Again button
        binding.tryAgainButton.setOnClickListener{view: View->
        view.findNavController()
                .navigate(R.id.action_gameOverFragment_to_gameFragment)}
                
                
// Add OnClick Handler for Next Match button
        binding.nextMatchButton.setOnClickListener{view: View->
            view.findNavController()
                    .navigate(R.id.action_gameWonFragment_to_gameFragment)}

-> 두 버튼 모두 게임 화면을 돌아가서 게임을 다시 시도할 수 있음

-> 게임에서 이기거나 진 후 두 버튼 중 하나를 누르고, 그 이후에 뒤로가기 버튼을 누르면? 방금 있던 화면이 아닌 title 화면으로 이동함


5. App bar에 Up button 추가하기

  • App bar ( = action bar)
    • app branding과 identity를 위한 전용 공간
    • 앱 바를 사용하면 옵션 메뉴와 같은 navigation features에 access할 수 있음
    • 앱 바에서 옵션 메뉴에 접근하려면 3개의 점으로 이루어진 아이콘 클릭
    • 앱 바에는 각 화면에 따라 변경될 수 있는 제목 문자열이 표시됨
  • Up 버튼
    • 현재 앱에서 사용자는 시스템의 뒤로가기 버튼을 이용하여 이전 화면으로 이동함
    • 그러나 Android 앱은 앱 바의 왼쪽 상단에 Up 버튼이 표시될 수도 있음
    • 초기 화면을 제외한 모든 화면에 Up 버튼을 표시할 수 있어야 함
  • Navigation controller를 사용하여 Up 버튼을 앱에 추가하는 법
    • MainActivity.kt 안의 onCreate() 내부에 Navigation Controller object를 찾는 코드 추가
    • 또, Navitagion controller를 App bar에 연결하는 코드 추가
    • onCreate() 메소드 다음에, onSuppoerNavigateUp()을 override하여 navigation controller에서 navigateUp() 호출
//onCreate 내부
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController)

//onCreate 다음
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }

=> 앱을 실행하면 Up 버튼은 초기화면을 제외한 모든 화면의 앱 바에 나타나며, 앱의 어디에 있든 Up 버튼을 누르면 초기 화면으로 돌아옴

**navigation components에는 NavigationUI 라이브러리라 포함되어 있어서, NavigationUI 클래스를 포함하고 있음. 이 클래스에는 상단 앱 바, navigation drawer, bottom navigation을 관리하는 static method가 포함되어 있음.

따라서 Navigation controller는 app bar에 통합되어 Up 버튼의 동작을 구현하기 때문에 사용자가 직접 실행할 필요가 없음


6. Option menu 추가하기

  • Android 기기에서 사용자는 App bar에 표시되는 세 개의 dot로 이루어진 아이콘을 눌러 옵션 메뉴에 접근할 수 있음
  • Option menu에 About menu를 추가하고 이를 탭하면 앱이 AboutFragment로 이동하는 법

STEP 1 : Navigation graph에 AboutFragment 추가

  • navigation.xml에서 New Destination > fragment_about
  • 겹치지 않게 옮김 -> title의 왼쪽으로 끌기
  • ID는 aboutFragment로 설정

STEP 2 : Options-menu resource 추가

  • res 폴더를 우클릭하여 New > Android Resource File 선택
  • options_menu로 이름 지정하고, 리소스 유형은 Menu 선택 후 확인
  • options_menu.xml 열고 MenuItem 추가
  • 그 아이템의 ID를 aboutFragment로 설정, title은 @string/about 로 설정

**아이템의 ID와 네비게이션 그래프에 추가한 AboutFragment의 아이디가 일치하면 좋음

STEP 3 : onClick 핸들러 추가

  • 사용자가 about menu item을 탭할 때의 동작 구현
  • Title.Fragment.kt를 열고, onCreateView() 내부에서 return 하기 전에, setHasOptionMenu()를 호출하여 true를 전달함
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
   ...
   setHasOptionsMenu(true)
   return binding.root
}
  • onCreateView() 메소드 이후에, onCreateOptionsMenu() 를 override 함
    • options menu를 추가하고 menu resource file을 확장함
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  • onOptionsItemSelected()를 override 함 : 메뉴 아이템을 탭했을 때 어떤 조치를 취할 건지 지정
    • 이 경우 액션은 선택된 메뉴 아이템과 동일한 id를 가진 fragment로 이동함
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}


7. Navigation drawer 추가하기

  • Navigation drawer : 화면 가장자리에서 슬라이드하여 꺼내지는 패널
    • 사용자가 화면 시작 가장자리에서 끝 가장자리로 swipe하면 drawer가 나타남
    • 사용자가 앱의 start destination에 있을 때, app bar의 drawer icon(navigation drawer button, hamburger icon)을 탭하면 나타남
  • 네비게이션 드로어는 Material Components for Android 라이브러리(=Material library)의 일부분임! Material library를 사용하여 Google's Material Design guidelines의 일부인 패턴을 구현할 수 있음

STEP 1 : 프로젝트에 Material library 추가

  • app-level Gradle build 파일에 Material library에 대한 dependency 추가
dependencies {
    ...
    implementation "com.google.android.material:material:$version"
    ...
}
  • Sync

STEP 2 : destination fragment가 ID를 가지고 있도록 확인

  • 우리가 만들 네비게이션 드로어에는 두 개의 메뉴 항목이 있으며, 각각은 네비게이션 드로어에서 도달할 수 있는 fragment를 나타냄
  • 두 행선지 모두 네비게이션 그래프에 ID가 있어야 함
  • AboutFragment는 위에서 추가했으므로 RulesFragment만 지금 추가하기 - ID를 rulesFragment 로 설정

STEP 3 : Drawer menu와 drawer layout 생성

  • navigation drawer를 생성하기 위해, navigation menu를 먼저 만들어야 함
  • 또한 내 view를 layout file의 DrawerLayout 안에 삽입해야 함
  1. Drawer menu 만들기
    • 프로젝트 창에서 res 폴더를 우클릭하고, New Resource file 클릭, 이름은 navdrawer_menu, 리소스 타입은 menu 로 설정한 후 OK
  2. navdrawer_menu.xml 파일을 열고 두 개의 menu item 추가
    • 첫 번째 메뉴 아이템의 id를 rulesFragment로 설정(fragment의 ID와 같아야 함)
      • title을 @string/rules 로 설정한 후 아이콘을 @drawable/rules로 설정
    • 두 번째 메뉴 아이템의 id를 aboutFragment로 설정
      • title을 @string/about으로 설정한 후 아이콘을 @drawable/about_android_trivia 로 설정
  3. activity_main.xml 을 연 후, drawer functionality를 모두 사용하기 위해 모든 뷰를 <DrawerLayout>으로 감싸 줌
    • <layout>을 제외하고 rooot view가 DrawerLayout이 되는 것!
  4. 그리고 그 안에 <NavigationView>를 사용하여 드로어 추가
    • 방금 만든 navdrawer_menu가 app:menu 로서 들어감

<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <androidx.drawerlayout.widget.DrawerLayout
       android:id="@+id/drawerLayout"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

   <LinearLayout
       . . . 
       </LinearLayout>
   </androidx.drawerlayout.widget.DrawerLayout>
</layout>
<com.google.android.material.navigation.NavigationView
   android:id="@+id/navView"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:layout_gravity="start"
   app:headerLayout="@layout/nav_header"
   app:menu="@menu/navdrawer_menu" />

STEP 4 : Display the Navigation drawer

  • 네비게이션 드로어를 네비게이션 컨트롤러에 연결하여 사용자가 Navigation drawer의 항목을 선택할 때 앱이 적절한 fragment로 이동하도록 해야 함

  • MainActivity.kt 를 열고, onCreate() 안에 사용자가 네비게이션 드로어를 display할 수 있는 코드 추가
    • setupWithNavController() 를 onCreate() 끝부분에 추가
NavigationUI.setupWithNavController(binding.navView, navController)
  • 앱을 실행해서 Navigation Drawer가 제대로 작동하는지 확인!
  • 근데 잘 작동해도 한 가지 더 수정해야 함
    • 사용자가 드로어버튼을 탭해서 드로어를 표시할 수도 있어야 하는데, 지금은 아이콘이 표시되지 않음

STEP 5 : Display the navigation drawer from the drawer button

  • MainActivity.kt에 드로어 레이아웃을 나타내는 member variable인 drawerLayout을 lateinit으로 추가
private lateinit var drawerLayout: DrawerLayout
  • onCreate() 내부에서, binding 변수가 초기화 된 후에 drawerLayout을 초기화
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,
                R.layout.activity_main)

drawerLayout = binding.drawerLayout
  • setupActionBarWithNavController() 메소드의 세 번째 파라미터로 drawerLayout 추가
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
  • onSupportNavigateUp() 메소드를  NavigationUI.navigateUp 를 반환하도록 수정(원래는 navController.navigateUp 반환)
  • navigation controller와 drawer layout을 navigateUp() 으로 전달
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}

잘 되는지 확인!

 

 

https://developer.android.com/codelabs/kotlin-android-training-add-navigation?index=..%2F..android-kotlin-fundamentals&hl=ko#0 

 

Comments