๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Android] Navigation - ๊ธฐ์ดˆ

interfacer_han 2024. 1. 26. 16:26

#1 ์ด์ „ ๊ธ€

 

[Android] Navigation - ํ™˜๊ฒฝ ์„ค์ •

#1 Navigation#1-1 ์•กํ‹ฐ๋น„ํ‹ฐ ๋ฐ ํ”„๋ž˜๊ทธ๋จผํŠธ ๊ตฌ์„ฑ์˜ ํŠธ๋ Œ๋“œ์š”์ฆ˜ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์˜ ํŠธ๋ Œ๋“œ๋Š” ํ•˜๋‚˜์˜ ์•กํ‹ฐ๋น„ํ‹ฐ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๋‹ค. ์ด๋Š” ๊ตฌ๊ธ€์˜ ๊ถŒ์žฅ์‚ฌํ•ญ์ด๊ธฐ๋„ ํ•˜๋‹ค. ์ด ํŠธ๋ Œ๋“œ์—์„œ ๋‹จ์ผ ์•กํ‹ฐ

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์—์„œ ์ด์–ด์ง„๋‹ค. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  Navigation์„ ์‚ฌ์šฉํ•˜๋Š” ์ƒ˜ํ”Œ ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณธ๋‹ค.

 

#2 Navigation ์‚ฌ์šฉํ•˜๊ธฐ - Destination ํ”„๋ž˜๊ทธ๋จผํŠธ

#2-1 Navigation graph์— Destination ํ”„๋ž˜๊ทธ๋จผํŠธ ๋“ฑ๋กํ•˜๊ธฐ

์šฐ๋ฆฌ๊ฐ€ activity_main.xml์„ ๋‹ค๋ฃฐ ๋•Œ Code ๋ชจ๋“œ, Split ๋ชจ๋“œ, Design ๋ชจ๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ด ํŽธ์ง‘ํ•˜๊ณค ํ–ˆ๋‹ค. nav_graph.xml๋„ ๊ทธ๋ ‡๋‹ค. nav_graph.xml์„ Design ๋ชจ๋“œ๋กœ ์—ฐ๋‹ค. ๊ทธ๋ฆฌ๊ณ   New Destination ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ๋‹ค.

 

Create new destination ํด๋ฆญ.

 

Fragment (Blank) ์„ ํƒํ›„ Next ๋ฒ„ํŠผ ํด๋ฆญ.

 

์ ์ ˆํ•œ ์ด๋ฆ„(์—ฌ๊ธฐ์„œ๋Š” HomeFragment)์„ ์ง€์–ด์ฃผ๊ณ  Finish ๋ฒ„ํŠผ ํด๋ฆญ.

 

Navigation graph์— Destination ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ๋“ฑ๋ก๋๋‹ค. ์ด์™€ ๋™์‹œ์—, res/layout ๋””๋ ‰ํ† ๋ฆฌ์— fragment_home.xml์ด ์ž๋™ ์ƒ์„ฑ๋˜์—ˆ๊ณ , MainActivity.kt๊ฐ€ ์žˆ๋Š” ๋””๋ ‰ํ† ๋ฆฌ์—๋Š” HomeFragment.kt๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋˜์—ˆ๋‹ค. 

 

<?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/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.navigationbasics.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
</navigation>

nav_graph.xml์„ Code ๋ชจ๋“œ๋กœ ๋ณด๋ฉด ์œ„์™€ ๊ฐ™๋‹ค.

 

#2-2 ์ž๋™ ์ƒ์„ฑ๋œ fragment_home.xml ๊ฐ„๋‹จํžˆ ์ˆ˜์ •ํ•˜๊ธฐ

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".HomeFragment">

        <EditText
            android:id="@+id/editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
            android:inputType="text"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/button"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="์ „์†ก"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/editText"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

์ž๋™์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” FrameLayout์ด๋‹ค. ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•œ ConstraintLayout์œผ๋กœ ๋ฐ”๊ฟจ๋‹ค. ์ถ”๊ฐ€๋กœ, EditText์™€ Button๋„ ๋„ฃ์—ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์œ„ํ•ด <layout> ํƒœ๊ทธ๋กœ ๊ฐ์‹ธ๊ณ  xmlns ์†์„ฑ์„ ์ตœ์™ธ๊ณฝ ํƒœ๊ทธ์ธ <layout>๋กœ ์˜ฎ๊ธด๋‹ค.

 

#2-3 ์ž๋™ ์ƒ์„ฑ๋œ HomeFragment.kt์— ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์ ์šฉํ•˜๊ธฐ

// package com.example.navigationbasics

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [HomeFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class HomeFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    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
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    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 HomeFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            HomeFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
        /* interfacer_han: ๋ฐ”๋กœ ์œ„์— ์žˆ๋Š” ์ฝ”๋“œ์˜ ๋™์ž‘์€ ์•„๋ž˜ ์žˆ๋Š” ์ฝ”๋“œ์™€ ๊ฐ™๋‹ค.
        @JvmStatic
        fun newInstance(param1: String, param2: String): HomeFragment {
            val fragment = HomeFragment()
            val bundle = Bundle()
            bundle.putString(ARG_PARAM1, param1)
            bundle.putString(ARG_PARAM2, param2)
            fragment.arguments = bundle
            return fragment
        }
        */
    }
}

์ฝ”๋“œ ์•„๋žซ ๋ถ€๋ถ„์— ์žˆ๋Š” ๋‚ด ์ฃผ์„์„ ์ œ์™ธํ•˜๋ฉด Navigation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜ํ•ด ์ž๋™ ์ƒ์„ฑ๋œ ๋‚  ๊ฒƒ ๊ทธ๋Œ€๋กœ์˜ Fragment ํด๋ž˜์Šค ์ฝ”๋“œ๋‹ค. ์ด ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•œ๋‹ค.

 

...

class HomeFragment : Fragment() {
    ...
    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_home, container, false)
        return binding.root
    }

    ...
}

 

#2-4 Destination ํ”„๋ž˜๊ทธ๋จผํŠธ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ (SecondFragment)

#2-1 ~ #2-3์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ SecondFragment๋ผ๋Š” ์ด๋ฆ„์˜ Destination ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. fragment_second.xml์—๋Š” <TextView> ์œ„์ ฏ ๋”ฑ ํ•˜๋‚˜๋งŒ ๋„ฃ์–ด๋‘”๋‹ค.

 

#3 Navigation ์‚ฌ์šฉํ•˜๊ธฐ - ํ”„๋ž˜๊ทธ๋จผํŠธ ์ „ํ™˜

#3-1 Action ๋งŒ๋“ค๊ธฐ

nav_graph.xml์„ Design ๋ชจ๋“œ๋กœ ์—ฐ๋‹ค. HomeFragment๋ฅผ ํด๋ฆญํ•˜๋ฉด ์˜ค๋ฅธ์ชฝ์— ํŒŒ๋ž€์ƒ‰ ์›์ด ์ƒ๊ธด๋‹ค.

 

๊ทธ ํŒŒ๋ž€์ƒ‰ ์›์„ ๋“œ๋ž˜๊ทธํ•ด์„œ SecondFragment์— ์—ฐ๊ฒฐํ•œ๋‹ค. ์ด๋Ÿฌ๋ฉด ๋‘ ํ”„๋ž˜๊ทธ๋จผํŠธ ๊ฐ„์˜ Action์ด ์ƒ์„ฑ๋œ๋‹ค. Action์ด๋ž€, ํ”„๋ž˜๊ทธ๋จผํŠธ ์ „ํ™˜์˜ ํ๋ฆ„์ด๋‹ค.

 

<?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/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.navigationbasics.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationbasics.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />

</navigation>

Code ๋ชจ๋“œ๋กœ ๋ณด๋ฉด ์œ„์™€ ๊ฐ™๋‹ค. Action์˜ ์ž๋™ ์ƒ์„ฑ๋œ id๋ฅผ ๋ˆˆ์—ฌ๊ฒจ๋ด๋‘์ž.

 

#3-2 Action ์‚ฌ์šฉํ•˜๊ธฐ (HomeFragment.kt ์ˆ˜์ •)

...
import androidx.navigation.findNavController
...

class HomeFragment : Fragment() {
    ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_home, container, false)
        binding.button.setOnClickListener {
            it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
        }
        return binding.root
    }

    ...
}

HomeFragment์˜ button์„ ๋ˆ„๋ฅด๋ฉด ๋ฐฉ๊ธˆ ๋งŒ๋“  Action์ด ์‹คํ–‰๋˜๊ฒŒ ํ•˜๋Š” ์ฝ”๋“œ๋‹ค.

 

#3-3 ์‹คํ–‰ ํ™•์ธ

ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ์ž˜ ์ „ํ™˜๋œ๋‹ค.

 

์ด ๋‹ค์Œ์œผ๋ก  HomeFragment์—์„œ SecondFragment๋กœ ์ „ํ™˜ํ•  ๋•Œ ๋ฐ์ดํ„ฐ๋„ ๊ฐ™์ด ์ „๋‹ฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณธ๋‹ค.

 

#4 ํ”„๋ž˜๊ทธ๋จผํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

#4-1 HomeFragment.kt ์ˆ˜์ •

...

class HomeFragment : Fragment() {
    ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
        binding.button.setOnClickListener {
            val userName = if(!TextUtils.isEmpty(binding.editText.text.toString())) {
                binding.editText.text.toString()
            } else {
                "์ด๋ฆ„ ์—†์Œ"
            }
            val bundle: Bundle = bundleOf("user_name" to userName)
            it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment, bundle)
        }
        ...
    }

    ...
}

navigate() ๋ฉ”์†Œ๋“œ์™€ ์˜ค๋ฒ„๋กœ๋”ฉ ๊ด€๊ณ„์ด๋ฉด์„œ Bundle์„ ์ธ์ž๋กœ ๋ฐ›๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์‚ฌ์‹ค ์ด๋ ‡๊ฒŒ ์ง์ ‘์ ์œผ๋กœ ๋ชฉ์ ์ง€(Destination) ํ”„๋ž˜๊ทธ๋จผํŠธ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ํ•œ๋‹ค. ์ •์„์ ์ธ ๋ฐฉ๋ฒ•์€ ViewModel์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์ด๋‹ค.

 

#4-2 SecondFragment.kt ์ˆ˜์ •

...

class SecondFragment : Fragment() {
    ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_second, container, false)
        var userName: String? = requireArguments().getString("user_name")
        binding.textView.text = userName.toString()
        return binding.root
    }

    ...
}

Fragment.requireArguments()๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋œ Bundle์„ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  fragment_second.xml์˜ <TextView>์— ํ‘œ์‹œํ•œ๋‹ค.

 

#4-3 ์‹คํ–‰ ํ™•์ธ

 

#5 ์š”์•ฝ

Navigation library๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ง€๋„์ฒ˜๋Ÿผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.

 

#6 ์™„์„ฑ๋œ ์•ฑ

 

android-practice/navigation/NavigationBasics at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com