[TIL] Android 앱 개발 숙련 : Fragment 실습 및 과제

지혜·2024년 1월 12일

Android_TIL

목록 보기
37/70

✏240112 금요일 TIL(Today I learned) 오늘 배운 것

📖Fragment 과제 + RecyclerView 추가 목표

  • 목표 : 입문 주차에 만든 UI에 Fragment를 활용하여 2개 이상의 화면을 구성한다.
    • Fragment를 관리해 주는 Widget(BottomNavigationView, Drawer, TabLayout, ViewPager 등)를 활용하여 Fragment를 화면에 표시합니다.

  • 추가 목표 : RecyclerView 아이템에 클릭 이벤트를 추가하여, 사용자가 선택한 아이템에 대한 Detail 정보를 표시합니다.
    • 아이템 클릭 시, 새로운 화면 또는 팝업으로 상세 정보를 보여주는 기능을 추가합니다.

  • 상세코드는 Clone_RecyclerFragment 깃허브 참조

[activity_main.xml]

<?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"
    android:background="@color/white"
    tools:context=".MainActivity">

  ...

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/topbar" />


   <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="69dp"
        app:itemBackground="@android:color/white"
        app:itemIconTint="#1C1C1C"
        app:itemTextColor="#1C1C1C"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:menu="@menu/menu_navigation"
        tools:layout_editor_absoluteX="-16dp">

    </com.google.android.material.bottomnavigation.BottomNavigationView>
</androidx.constraintlayout.widget.ConstraintLayout>
  • FrameLayout이 상단바(위 코드에서는 생략)와 하단네비게이션 사이에서 전환 될 수 있도록 레이아웃을 잡아준다.

[MainActivity.kt]

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    
    private val homeFragment = HomeFragment()
    private val nearFragment = NearFragment()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setFragment(homeFragment)

        binding.bottomNavigationView.setOnItemSelectedListener {
            when(it.itemId) {
                R.id.navMenu_home -> {
                    setFragment(homeFragment)
                }
                R.id.navMenu_location -> {
                    setFragment(nearFragment)
                }
            }
            true
        }
        
	...
    

    }

    private fun setFragment(frag: Fragment) {
        supportFragmentManager.commit {
            replace(R.id.frameLayout, frag)
            setReorderingAllowed(true)
            addToBackStack("")
        }
    }
}
  • 메인 액티비티에서 뷰바인딩을 통해 activity_main.xml의bottomNavigationView을 연결하고, 거기에 setOnItemSelectedListener 를 걸어서 it(=MenuItem).itemID를 참조하는 when문으로 bottomNavigation을 제어한다.
  • 프래그먼트를 연결하는 펑션을 setFragment로 밑에 따로 빼서 재사용이 용이하게 한다.

[HomeFragment.kt]

class HomeFragment : Fragment() {
	private val binding by lazy { FragmentHomeBinding.inflate(layoutInflater) }
    
    private lateinit var adapter: ProductAdapter
    private lateinit var dataList: MutableList<ProductItem>
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        dataList = mutableListOf<ProductItem>()
        dataList.add(ProductItem(R.drawable.product, "170,000원", "12월 1일", "애플워치 팝니다.", "군자동","상품설명입니다.-a24rkf!2"))
        dataList.add(ProductItem(R.drawable.product2, "90,000원", "12월 3일", "중고카페의자.", "군자동","상품설명입니다.-38ghakj@"))
        dataList.add(ProductItem(R.drawable.product3, "5,000원", "12월 7일", "귀여운 쿼카 파우치!", "군자동","상품설명입니다.-kajnkg9321dfga"))
        dataList.add(ProductItem(R.drawable.product, "300,000원", "12월 11일", "애플워치 팝니다.", "군자동","상품설명입니다.-128dkgj1y"))
        dataList.add(ProductItem(R.drawable.product2, "80,000원", "12월 12일", "중고카페의자.", "군자동","상품설명입니다.-gi4280th1j"))
        dataList.add(ProductItem(R.drawable.product3, "3,000원", "12월 16일", "귀여운 쿼카 파우치!", "군자동","상품설명입니다.-agj8r12#2"))
        dataList.add(ProductItem(R.drawable.product, "200,000원", "12월 20일", "애플워치 팝니다.", "군자동","상품설명입니다.-39gjsdkj2"))
        dataList.add(ProductItem(R.drawable.product2, "50,000원", "12월 22일", "중고카페의자.", "군자동","상품설명입니다.-02409tf13jdfagj"))
        dataList.add(ProductItem(R.drawable.product3, "5,000원", "12월 31일", "귀여운 쿼카 파우치!", "군자동","상품설명입니다.-39sdjgakl"))

        adapter = ProductAdapter(dataList)
        binding.productRecycle.adapter = adapter
        binding.productRecycle.layoutManager = LinearLayoutManager(context)

        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.btnFloating.setOnClickListener{
            val  intent = Intent(context, SelectImageActivity::class.java)
            startActivity(intent)
        }
        
        adapter.productClick = object : ProductAdapter.ProductClick {
            override fun onClick(view: View, position: Int) {

                val intent = Intent(context,MainDetailActivity::class.java)
                intent.putExtra("productData",dataList[position])
                startActivity(intent)
            }
        }

    } 
	

}
  • onCreateView 에서는 Fragment가 처음 생성될 때 Fragment에 필요한 View들을 생성하여 반환하므로, 리사이클러뷰가 반환 될 수 있게 dataList와 adapter, layoutManager를 연결하여 리사이클러 뷰를 연결해준다.

  • 여기서, 프래그먼트는 액티비티와 달리 this로 컨텍스트를 참조하면 클릭 리스너의 컨텍스트를 가리키기 때문에 참조가 되지 않는다. context를 사용하여 현재 프래그먼트의 객체를 가지고 오도록 해줘야 한다.

  • 플로팅 버튼이 홈프래그먼트에서만 보이길래, fragment_home.xml로 플로팅버튼을 옮겨주고, HomeFragment.kt에서 바인딩을 받아 onViewCreated에서 setOnClickListener으로 클릭 이벤트를 발생시킨다. SelectImageActivity는 액티비티이므로, intent를 사용하여 연결되도록 intent를 사용했다.

  • onViewCreated에서는 Fragment의 View가 완전히 생성된 후 호출되기 떄문에 UI요소에 대한 초기화나 이벤트 처리를 수행하기 좋다.

  • 클래스변수로 private lateinit var dataList: MutableList<ProductItem>를 선언해줬기 때문에, onCreateView에서 초기화된 datalist를 onViewCreated에서도 사용할 수 있다. 이를 바탕으로 @Parcelize를 사용하는 ProductItem 데이터클래스에서 역직렬화를 통해 putExtra로 MainDetailActivity에 데이터를 전송한다.

  • datalist와 adapter가 onViewCreated에서 작성되어도 문제가 없다는 사실을 알았다. 헐..ㅎㅎ 다음엔 깔끔하게 onViewCreated에서 해결해봐야겠다.. ...


[MainDetailActivity.kt]

package com.example.clone_recyclerfragment

import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.clone_recyclerfragment.databinding.ActivityMainDetailBinding

class MainDetailActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainDetailBinding

    private val productData: ProductItem? by lazy {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            intent.getParcelableExtra("productData", ProductItem::class.java)
        } else {
            intent.getParcelableExtra<ProductItem>("productData")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainDetailBinding.inflate(layoutInflater)
        setContentView(binding.root)

        productData?.let { binding.ivDetailProduct.setImageResource(it.product) }
        binding.tvDetailTitle.text = productData?.title
        binding.tvDetailLoca.text = productData?.loca
        binding.tvDetailDescription.text = productData?.description
        binding.tvDetailPrice.text = productData?.price

        binding.ivBack.setOnClickListener{
            finish()
        }

    }

}
  • getParcelableExtraHomeFragment에서 보낸 intent.putExtra("productData",dataList[position])를 같은 키 값으로 받아서 productData 변수에 저장한다. 티라미수버전을 기준으로 적용되는 코드가 다르기 때문에 if문을 통해 분리해줬다.
  • onCreate에서 binding할 각 값에 productData에서 해당하는 데이터를 연결해준다.

✏오늘의 느낀점과 내일 할 일

  • 추가 과제를 할 때, 상세페이지는 상단바 및 하단바 없이 탭으로 이동하는 영역이 아니기 때문에 그냥 액티비티를 사용하기로 했다. 이때 리사이클러뷰가 프래그먼트 안에 있기 때문에, 프래그먼트에서 액티비티로 데이터를 이동하는 영역이라고 생각했는데, 그냥 개인과제 때 처럼 리사이클러뷰에서 intent를 사용하여 데이터를 전달하는 것 처럼 전달 할 수 있었다. 그러니까 그냥 리사이클러뷰에서 액티비티로 값을 전달하는 거였던 거다. 해봤던거라 어렵지 않게 연결할 수 있었다.

  • 사실 NearFragment도 있긴 한데, 파프리카 마켓 팀 과제 때 만들어 놓은 것을 재탕 했기 때문에 그 안에서 사용하던 탭바의 처리를 어떻게 할 것인지 아직 정하지 못했다. 그 부분도 Fragment영역으로 바꿔서 레이아웃을 재활용하려고 했는데, 생각해보니까 리사이클러뷰의 layout_item.xml 처럼 고정된 레이아웃이라서 그냥 데이터만 전달하게 끔 바꿔야 하는 것 같아서.. 근데 또 리사이클러뷰는 아니고 고정된 부분이니까.. 데이터를 어떻게 연결해야 할지.. 그 와중에 전체 탭은 또 레이아웃이 달라서 여기만 따로 프래그먼트로 빼야하는 것일지.. 아직 손대기에 정리가 덜 되었다. 사실.. 그냥.. 이대로...... 냅두고 싶은 마음도 있다. 어쨌든 과제에서 요구하는 부분은 충족 되어 있으니까.. .... 그래서 이 부분은 정말로.. 시간이 남으면 해보기로 한다.

  • 이번 수준별 과제 관리에 대해서, 저번에 RecyclerView과제 때 합쳐 놓은 깃허브 레포지토리를.. 다시.. 분리 했다. 진행 중인 프로젝트에 대해서는 수정 및 변경 사항이 있을 때 하위 폴더만 따로 무언가를 하기에 너무 불편하고.. 다음 심화 과제 때에도 빌드업 형태의 과제를 주신다고 하셨어서 그냥 아예 분리를 해버렸다. 이렇게 하기 까지 또 깃배쉬 터미널을 얼마나 들락날락거렸는지.. 애초에 합치지를 말았어야했는데.. 너무 성급했다. 이번에는 RecyclerView브런치와 Fragment브런치로 관리하고, 심화 때에 또 폴더를 만들어서 새로 업로드 해야겠다. 그리고 맨 나중에 수업을 수료하고나서 깃허브를 정리할 때나 합쳐야겠다.

  • 월요일에는 새로 팀과제에 들어가니까 주말 중으로 수준별 과제 제출을 하는 것이 목표이다! 다 만들었으니까 깃허브에 올려서 정리만 하면 될 것 같다!

profile
파이팅!

0개의 댓글