Kotlin

[Kotlin] Fragment Navigation 화면 전환 방법

cob 2022. 11. 2. 10:08

 

Fragment Navigation 이란?
Android Jetpack의 Navigation Component를 사용하여 이지 이동을 쉽게 구현하게 해주는 방법이다.

 

* 공식 문서 *
https://developer.android.com/guide/navigation/navigation-getting-started

 

 


1. Navigation 구성요소

  • Navigation Graph : 모든 Navigation 관련 정보가 하나의 중심 위치에 모여 있는 XML 리소스입니다. 단순히 말해 사용자가 앱에서 갈 수 있는 모든 플로우를 보여주고 앱 내의 Fragment를 한눈에 확인할 수 있습니다.
  • NavHost : Navigation Graph에서 대상을 표시하는 빈 컨테이너입니다. 대상 구성요소에는 프래그먼트 대상을 표시하는 기본 NavHost 구현인 NavHostFragment가 포함됩니다.
  • NavController : NavHost에서 App Navigation을 관리하는 객체입니다. NavController는 사용자가 앱 내에서 이동할 때 NavHost에서 대상 콘텐츠의 환을 조종하는 역할을 합니다.
Navigation Graph에서 특정 경로를 따라 이동할지, 특정 대상으로 직접 이동할지 NavController에게 전달하고 NavController가 NavHost에 적절한 대상을 표시해주는 방식입니다.

 

 

 


2. 장점 

  • 프래그먼트의 트렌젝션 처리.
  • 올바르게 Up과 Back동작을 처리.
  • 화면 전환에 대한 표준화된 애니메이션 리소스.
  • 딥 링크 구현 및 처리를 쉽게 할 수 있다.
  • Bottom Navigation과 같은 요소들을 최소한의 추가 작업으로 구현.
  • Safe Args - 대상 사이에서 데이터를 탐색하고 전달할 때 안정성을 제공하는 Graph 플러그인.
  • ViewModel - Navigation Graph에 대한 ViewModel을 확인해 그래프 대상 사이에 UI 관련 데이터를 공유.

 

 


3. 사용 방법

3-1) 라이브러리 설치

( build.gradle(Module: 프로젝트명.app) )

dependencies {
    def nav_version = "2.3.5"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
  • Sync Now를 눌러 적용

 

3-2) navigtion 폴더 생성

navigation 폴더 생성

  • 경로 : res폴더 우클릭 → New → AndroidResource Direcroty 클릭 navigation 폴더 생성

 

 

3-3) navigation 파일 생성

navigation 파일 생성

  • 경로 : navigtion폴더 우클릭 → New → Navigtion resource file 클릭 navigation 파일 생성
  • 파일을 생성하게 되면 GUI 적으로 한눈에 알아볼 수 있게 이미지로 나타낸다.

 

 

3-4) Fragment 생성

fragment 생성

  • fragment 폴더 생성 후 내부에  fragment 파일 생성
    - fragment 파일 생성 : fregment폴더 우클릭 → New → Fragment → Fragment(Blank) 클릭
  • fragment 이름 설정 시 Layout Name 자동 변경
  • layout 자동 생성

 

3-5) Fragment Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  • tools:context= ".MainActivity"  : 지정해야 navigation 파일에서 등록할 수 있다( .MainActivity  액티비티 연결)

 

3-6) Fragment 관계 정리(화면 이동)

fragment 관계 설정

  • 이전 3-3)에서 만들었던 navigation 파일에서 관계 설정을 한다.
    navigtion   nav_graph.xml 클릭   Design 클릭
  • 집 모양을 눌러 메인 화면 설정 가능

 

Fragment 관계

  • 화살표를 드래그하여 관계 설정을 적용한다.

 

 

3-7)  Activity Layout에 Fragment  Navigation 적용

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
  ........>

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

 
</androidx.constraintlayout.widget.ConstraintLayout>
  • 경로 : res → layout → 적용 액티비티 ( activity_main.xml )
  • app:navGraph="@navigation/nav_graph : 이전 3-3)에서 만들어 두었던 navigtion 파일 적용
  • activity layout은 단순히 fragment를 담아두는 틀 역할을 한다.
  • fragment 태그 안의 속성 값은 고정 값으로 복붙!

 

3-8) Activity에 Navigation Controller 생성

class MainActivity : AppCompatActivity() {
	//네비게이션 컨트롤러
    lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
				// main Activity에서 선언된 fragment id를 불러와 연결한다.
        navController = nav_host_fragment.findNavController()
    }
}

 

 

3-9) Fragment 화면 전환 방법 (1)

( MainFragment.kt )

class MainFragment : Fragment() {
    // onViewCreated에서 사용할거기 때문에 lateinit 선언
    lateinit var navController: NavController

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_main, container, false)
    }
    // onViewCreated : 뷰를 그리고 나서 실행
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        navController = Navigation.findNavController(view)
	// id클릭시 네비게이션 이동
        btn_storage.setOnClickListener {
            navController.navigate(R.id.action_mainFragment_to_boxFragment)
        }
    }
}

navigate 목록

  • navigate : 인자 입력 시 main 까지만 쓰면 위에서 관계 설정한 fragment들이 나타난다.

 

 

3-10)  Fragment 화면 전환 (2) 인터페이스 활용

여러 페이지 전환 시 인터페이스 방식 사용
class SelectionFragment : Fragment(),View.OnClickListener {
//lateinit -> 나중에 정의
lateinit var navController: NavController //네비게이션 컨트롤러
...
	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
		//뷰를 그리고 나서 실행
        super.onViewCreated(view, savedInstanceState)
        navController = Navigation.findNavController(view)

        btn_back.setOnClickListener(this)
        option_1.setOnClickListener(this)
        option_2.setOnClickListener(this)
        option_3.setOnClickListener(this)
        option_4.setOnClickListener(this)
        
        // 인터페이스 없이 바로 사용도 가능한 이벤트
        //btn_home.setOnClickListener{ 
        //		navController.navigate(R.id.action_resultFragment_to_mainFragment)
        //  }   
  }

	override fun onClick(v: View?) {
        when(v?.id){
            R.id.option_1 -> {navigationWithIndex(1)}
            R.id.option_2 -> {navigationWithIndex(2)}
            R.id.option_3 -> {navigationWithIndex(3)}
            R.id.option_4 -> {navigationWithIndex(4)}
            R.id.btn_back -> {
                navController.popBackStack()
            }
        }
    }
	fun navigationWithIndex(index : Int){
	//fargment에 데이터를 bundle로 보낸다. 
        val bundle = bundleOf("index" to index) //key to value 

	//action_selectionFragment_to_resultFragment 로 이동
        navController.navigate(R.id.action_selectionFragment_to_resultFragment, bundle)
    }
}
  • View.OnClickListener 인터페이스 상속받아 모든 버튼에 대한 이벤트 줄 수 있다.
  • action_selectionFragment_to_resultFragment : selectionFragment에서 클릭하게 되면 resultFragment로 이동한다는 의미
  • bundle : key, value 값으로 데이터를 넘긴다.

 

3-11) bundle 데이터 받기 예제

//데이터 받기
class ResultFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    var option = -1
    lateinit var navController: NavController


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        option = arguments?.getInt("index")?:-1
        return inflater.inflate(R.layout.fragment_result, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        navController = Navigation.findNavController(view)

        setResult(option)
        btn_home.setOnClickListener{
            navController.navigate(R.id.action_resultFragment_to_mainFragment)
        }
    }
    fun setResult(option:Int){
        when(option){
            1 -> {
                tv_main.text = "You are a QUITTER!"
                tv_sub.text ="You can let the person easily."
            }

            2 -> {
                tv_main.text="You should focus on yourself"
                tv_sub.text="You become really clingy to your ex."

            }

            3-> {
                tv_main.text="You should take it easy"
                tv_sub.text="You can do crazy things no matter what it takes."
            }

            4-> {
                tv_main.text="You are pretty mature."
                tv_sub.text="You can easily accept the break-up."
            }
        }
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment ResultFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            ResultFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}
  • option = arguments?.getInt("index")?:-1 
    - 이전 Fragment 화면에서 넘겨준 데이터 key 값으로 가져오기
  • ?:
    - 엘비스 연산자로 만약 null 이면 -1을 반환
반응형