관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] 구글 공식 프로젝트 Sunflower 스터디 (3) Jetpack Navigation 구조 본문

안드로이드/코틀린 & 아키텍처 & Recent

[안드로이드] 구글 공식 프로젝트 Sunflower 스터디 (3) Jetpack Navigation 구조

막무가내막내 2021. 4. 19. 13:50
728x90

 

[Jetpack Navigation 구조]

 

 

 

[참고]

developer.android.com/guide/navigation?hl=ko

 

탐색  |  Android 개발자  |  Android Developers

Android Jetpack의 탐색 구성요소를 사용하여 앱에서 탐색 구현

developer.android.com

github.com/android/sunflower

 

android/sunflower

A gardening app illustrating Android development best practices with Android Jetpack. - android/sunflower

github.com

 

 

앞선 (1) 패키지 구조 포스팅에서 말했듯이 이 프로젝트는 SPA(Single-Page-Application) 싱글 액티비티 디자인 구조로 되어있으므로 하나의 액티비티와 다수의 프래그먼트가 존재한다.

 

이 프로젝트에서는 Jetpack Navigation에서 제공해주는 바텀네비게이션, 툴바를 사용하지는 않았고 메인 액티비티에 Toolbar + ViewPager2 + TabLayout 구조를 가진 Fragment 가 세팅되고 프래그먼트가 전환되는 식이다. 추가로 프래그먼트에서 아이템을 누르면 상세화면으로 가는 네비게이션을 갖고있다.

 

Jetpack Navigation은 싱글 액티비티 디자인, 프래그먼트를 적극적으로 활용할때 강한 이점이 있다.

프로젝트에서는 위 사진의 기능들을 대부분 사용한다. 한번 살펴보도록 하겠다.

 

먼저 디자인이다.

 

[GardenActivity XML]

단 하나인 메인 액티비티를 담당하는 부분이다. XML을 보면 navGraph와 defaultNavHost가 설정되어 있다. 이이 대해서 포스팅에 기록한 경험이 있다.

간단히 설명하면 

  • app:navGraph 속성은 NavHostFragment를 탐색 그래프와 연결합니다. 탐색 그래프는 사용자가 이동할 수 있는 이 NavHostFragment의 모든 대상을 지정합니다.
  • app:defaultNavHost="true" 속성을 사용하면 NavHostFragment가 시스템 뒤로 버튼을 가로챕니다. 하나의 NavHost만 기본값으로 지정할 수 있습니다. 동일한 레이아웃에 여러 호스트가 있다면(예: 창이 2개인 레이아웃) 한 호스트만 기본 NavHost로 지정해야 합니다

youngest-programming.tistory.com/274

 

[안드로이드] Android Jetpack Navigation 정리 및 BottomNavigationView 에 적용 + ActionBar 적용 (Kotlin)

[2021-03-30 업데이트] 안드로이드 코틀린 Jetpack 라이브러리들에 대해 공부중이고 Jetpack Naviagtion 중 Bottom navigation 을 프로젝트에 간단하게 적용해볼려 하고있습니다. 보면서 도움이 되는 사이트를

youngest-programming.tistory.com

 

nav_garph.xml 에서 app:startDestination 보면 FragmentContainerView 에 ViewPager2 가 세팅된다. 

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_garden" />

</layout>

[nav_graph XML]

<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"
    app:startDestination="@id/view_pager_fragment">

    <fragment
        android:id="@+id/view_pager_fragment"
        android:name="com.google.samples.apps.sunflower.HomeViewPagerFragment"
        tools:layout="@layout/fragment_view_pager">

        <action
                android:id="@+id/action_view_pager_fragment_to_plant_detail_fragment"
                app:destination="@id/plant_detail_fragment"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right" />
    </fragment>

    <fragment
        android:id="@+id/plant_detail_fragment"
        android:name="com.google.samples.apps.sunflower.PlantDetailFragment"
        android:label="@string/plant_details_title"
        tools:layout="@layout/fragment_plant_detail">

        <action
            android:id="@+id/action_plant_detail_fragment_to_gallery_fragment"
            app:destination="@id/gallery_fragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
        <argument
            android:name="plantId"
            app:argType="string" />
    </fragment>

    <fragment
        android:id="@+id/gallery_fragment"
        android:name="com.google.samples.apps.sunflower.GalleryFragment"
        android:label="@string/plant_details_title"
        tools:layout="@layout/fragment_gallery">
        <argument
            android:name="plantName"
            app:argType="string" />
    </fragment>

</navigation>

 

 

 

 

 

Arguments에 대해 보겠다.

 

anim 파일
Animations, Argument 등이 설정되어 있다.

 

Jetpack Navigation의 디자인은 위와 같이 3개의 화면으로 이루어져있다. 그리고 각 화면이동간에 커스텀 anim이 구현되어 있다. 또한 넘길 arguments들도 설정되어 있다.

이에 대해서도 이전에 정리한 경험이 있다.

youngest-programming.tistory.com/332

 

[안드로이드] Jetpack Navigation Fragment to Fragment direction (젯팩 네비게이션 프래그먼트 화면 전환) + 값

[2021-04-13 업데이트] 이전에 Jetpack Navigation에 이어서 태그를 사용하여 프래그먼트끼리 화면전환하는 것을 프로젝트에 적용해봤습니다. 먼저 원리 및 <태그> 등 자세한 설명은 제가 쓰지 않고 밑

youngest-programming.tistory.com

 

HomeViewPagerFragment(홈 식물 리스트 프래그먼트 화면) -> PantDetailFragment(식물 상세화면) 예시하나만 살펴보겠다.

nav_graph.xml 을 보면 PlantDetailFragment 는 plantId라는 String 타입 매개변수를 전달받는다.

    <fragment
        android:id="@+id/plant_detail_fragment"
        android:name="com.google.samples.apps.sunflower.PlantDetailFragment"
        android:label="@string/plant_details_title"
        tools:layout="@layout/fragment_plant_detail">

        <action
            android:id="@+id/action_plant_detail_fragment_to_gallery_fragment"
            app:destination="@id/gallery_fragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
        <argument
            android:name="plantId"
            app:argType="string" />
    </fragment>

 

그리고 HomeViewPager에서는 <action> 으로 PantDetailFragment 로 Direction이 설정되어 있다. 이렇게 설정해놓으면 빌드시 자동으로 NavController에서 네비게이션하는 함수가 만들어진다.

<fragment
        android:id="@+id/view_pager_fragment"
        android:name="com.google.samples.apps.sunflower.HomeViewPagerFragment"
        tools:layout="@layout/fragment_view_pager">

        <action
                android:id="@+id/action_view_pager_fragment_to_plant_detail_fragment"
                app:destination="@id/plant_detail_fragment"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right" />
    </fragment>

 

[HomeViewPagerFragment - PlantListFragment - PlantAdapter]

 

ViewPager 안의 PlantListFragment 리사이클러뷰의 PlantAdater 에 아이템 클릭시 식물 상세화면으로 가는 이벤트가 구현되어 있다. 

Jetpack Navigation Graph에 의해 자동으로 만들어진 Directions 객체와 action 함수를 통해 프래그먼트간 전환 및 값 전달을 하게 되는 것을 볼 수 있다.

class PlantAdapter : ListAdapter<Plant, RecyclerView.ViewHolder>(PlantDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return PlantViewHolder(
            ListItemPlantBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val plant = getItem(position)
        (holder as PlantViewHolder).bind(plant)
    }

    class PlantViewHolder(
        private val binding: ListItemPlantBinding
    ) : RecyclerView.ViewHolder(binding.root) {
        init {
            binding.setClickListener {
                binding.plant?.let { plant ->
                    navigateToPlant(plant, it)
                }
            }
        }

		// 이 부분을 보면 된다. Jetpack Navigation Direction 
        private fun navigateToPlant(
            plant: Plant,
            view: View
        ) {
            val direction =
                HomeViewPagerFragmentDirections.actionViewPagerFragmentToPlantDetailFragment(
                    plant.plantId
                )
            view.findNavController().navigate(direction)
        }

        fun bind(item: Plant) {
            binding.apply {
                plant = item
                executePendingBindings()
            }
        }
    }
}

private class PlantDiffCallback : DiffUtil.ItemCallback<Plant>() {

    override fun areItemsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem.plantId == newItem.plantId
    }

    override fun areContentsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem == newItem
    }
}

 

 

 

[PlantDetailFragment]

by navArgs 로 값을 전달받는 것을 볼 수 있다.

@AndroidEntryPoint
class PlantDetailFragment : Fragment() {

    private val args: PlantDetailFragmentArgs by navArgs()

    @Inject
    lateinit var plantDetailViewModelFactory: PlantDetailViewModelFactory

    private val plantDetailViewModel: PlantDetailViewModel by viewModels {
        PlantDetailViewModel.provideFactory(plantDetailViewModelFactory, args.plantId)
    }

 

 

 

Anim에 대해 보겠다.

 

Animations, Argument 등이 설정되어 있다.

커스텀 애니메이션이 구현되어있는 것을 볼 수 있다.

이거에 대해서는 이전에 포스팅 한 경험이 있으니 참고하면 좋을 것 같다. 

youngest-programming.tistory.com/483

 

[안드로이드] Android Jetpack Navigation Animation Transition (젯팩 네비게이션 애니메이션 트랜지션)

[2021-04-14 업데이트] 이전에 위와 같이 안드로이드 Jetpack Navigation Component 에 대해서 몇개의 포스팅을 한 적이 있습니다. 이번에는 젯팩 네비게이션 컴포넌트에서 제공하는 Animation Transition (애니..

youngest-programming.tistory.com

 

 

 

 

 

Jetpack Navigation 디자인만으로는 완전한 디자인을 보기 힘드므로 원본 앱 사진을 보면 크게 ViewPager 구조에 내 정원과 식물 리스트 두개의 탭이 있고 식물의 상세화면이 존재한다.

 

 

 

 

댓글과 공감은 큰 힘이 됩니다. 감사합니다. !!

728x90
Comments