yourginieus
Navigation : Navigation components, drawer 등 본문
Android/Android Kotlin 기초 실습 정리
Navigation : Navigation components, drawer 등
EOJIN 2022. 10. 26. 00:041. 프로젝트에 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 필드를 사용하여 수행 됨
- res > navigation에서 navigation.xml을 열고, layout editor에서 navigation graph가 표시되지 않으면 Design 탭 클릭
- gameFragment에서 gameOverFragment로 navigating 할 action 선택
- Attributes Pane에서 popUpTo 설정 : popUpToInclusive를 true로 설정
- 이는 네비게이션 컴포넌트에 backstack에서 나 start fragment를 포함한 fragment들을 모두 삭제하라고 지시함
- gameFragment가 백스택에서 없어지는 거니까, gameOver에서 뒤로가기를 누르면 game의 전인 title로 돌아감!
- gameFragment에서 gameWonFragment로 navigating 할 action 선택
- Attributes Pane에서 popUpTo 설정 : popUpToInclusive를 true로 설정
=> 이기든 지든 뒤로가기 버튼을 누르면 TitleFragment 로 이동함!
STEP 2 : 네비게이션 액션 추가 및 onClick 핸들러 추가
- 게임이 끝났을 때, Try Again을 누르면 GameFragment로 이동하고, 여기서 뒤로가기 버튼을 누르면 TitleFragment 를 보여주는 총 2개의 플로우가 있음
- 이 user flow를 생성하기 위해, popUpTo 속성을 사용하여 백스택 관리
- navigation.xml 파일 안에서, gameOverFragment를 gameFragment 로 이동하는 action 추가 연결.
- action의 ID에 있는 프래그먼트 이름이 xml에 있는 프래그먼트 이름과 일치하도록 유의!
- 예) action ID : action_gameOverFragment_to_gameFragment
- Attributes pane에서, 액션의 popUpTo 속성을 titleFragment 로 설정
- popUpToInclusive 는 체크 해제 : titleFragment가 백스택에서 삭제되길 원하지 않기 때문! titleFragment를 제외 한 모든 것이 백스택에서 삭제 됨
- gameWonFragment에서 gameFragment로 가는 action도 추가 연결
- 그리고 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 안에 삽입해야 함
- Drawer menu 만들기
- 프로젝트 창에서 res 폴더를 우클릭하고, New Resource file 클릭, 이름은 navdrawer_menu, 리소스 타입은 menu 로 설정한 후 OK
- navdrawer_menu.xml 파일을 열고 두 개의 menu item 추가
- 첫 번째 메뉴 아이템의 id를 rulesFragment로 설정(fragment의 ID와 같아야 함)
- title을 @string/rules 로 설정한 후 아이콘을 @drawable/rules로 설정
- 두 번째 메뉴 아이템의 id를 aboutFragment로 설정
- title을 @string/about으로 설정한 후 아이콘을 @drawable/about_android_trivia 로 설정
- 첫 번째 메뉴 아이템의 id를 rulesFragment로 설정(fragment의 ID와 같아야 함)
- activity_main.xml 을 연 후, drawer functionality를 모두 사용하기 위해 모든 뷰를 <DrawerLayout>으로 감싸 줌
- <layout>을 제외하고 rooot view가 DrawerLayout이 되는 것!
- 그리고 그 안에 <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)
}
잘 되는지 확인!
'Android > Android Kotlin 기초 실습 정리' 카테고리의 다른 글
Life Cycle 및 Logging - Timber (0) | 2022.10.26 |
---|---|
Safe Args : 프래그먼트 간 데이터 전달, intent로 외부 앱 공유 (0) | 2022.10.26 |
Fragment : fragment 생성 및 layout에 fragment 추가 (0) | 2022.10.25 |
Data binding (0) | 2022.10.25 |
ConstraintLayout, Chain, setBackgroundColor/Resource, 리스트의 아이템에 click handler 적용 (0) | 2022.10.25 |
Comments