티스토리 뷰

챌린지반 4주차 세션 두번째 과제 

 

간단한 뉴스 리더 앱 만들기

 

구현 사항

프래그먼트
  - TitleFragment: 여러 뉴스 기사의 제목을 표시하는 리스트를 포함하고 있습니다.
  - DetailFragment: 사용자가 TitleFragment에서 기사 제목을 클릭하면 해당 기사의 내용을 표시합니다.

세부 사항
  - MainActivity에는 두 개의 Fragment를 호스팅하는 레이아웃이 포함되어야 합니다. 
  - 화면이 세로 방향일 때는 TitleFragment만 표시되며, 기사 제목을 클릭하면 DetailFragment로 교체되어야 합니다.
  - 화면이 가로 방향일 때는 TitleFragment와 DetailFragment가 동시에 표시되어야 합니다.

TitleFragment
  - RecyclerView를 사용하여 기사 제목을 표시하세요.
  - 기사 제목을 클릭하면 해당 기사의 세부 내용을 DetailFragment에서 표시해야 합니다.
  - Bundle을 통해 DetailFragment 에 기사 데이터 전달

DetailFragment
  - 전달받은 기사의 세부 내용을 TextView에 표시하세요.
  - 기사의 제목 및 내용은 임의로 설정하거나, 더미 데이터를 사용하세요.

 

가로모드 레이아웃 생성

 

res/layout 폴더 우클릭 > New > Layout Resource File 눌러서 해당 창을 띄운다.

 

특정 레이아웃의 가로모드 레이아웃을 생성하려면 파일 이름을 같게 설정해야 한다.

좌측 탭에서 Orientation을 선택하고 >> 버튼을 누른다.

 

 

Screen orientation을 Landscape로 선택한 뒤 레이아웃 파일을 생성한다.

 

 

레이아웃

 

프래그먼트를 호스팅 하는 레이아웃은 FrameLayout에 해당한다.

가로모드 UI는 두 프래그먼트를 1:1 비율로 배치시킨다.

 

 

TitleFragment는 리싸이클러뷰를 활용해 기사제목들을 표시한다.

DetailFragment는 아이템 프로퍼티 값을 전달받아 기사 내용을 출력한다. 

 

 

리싸이클러뷰의 아이템에는 기사 제목이 들어간다.

 

 

데이터 클래스

data class NewsItem(
    val title: String,
    val article: String
)

NewsItem에는 기사 제목과 내용이 프로퍼티로 들어가있다.

 

 

NewsAdpater

class NewsAdapter(private val mItems: List<NewsItem>) : RecyclerView.Adapter<NewsAdapter.Holder>() {

    interface ItemClick {
        fun onClick(position : Int)
    }

    var itemClick : ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemNewsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.setOnClickListener() {
            itemClick?.onClick(position)
        }

        holder.title.text = mItems[position].title
    }

    override fun getItemCount(): Int {
        return mItems.size
    }

    inner class Holder(binding: ItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {
        val title = binding.tvTitle
    }
}

TitleFragment에서 쓰일 어댑터 클래스를 선언한다.

 

 

프래그먼트 의존성 추가

implementation("androidx.fragment:fragment-ktx:1.8.2")

프래그먼트 세팅을 위해 build.gradle에 의존성을 추가해준다.

 

 

TitleFragment

class TitleFragment : Fragment() {
    private lateinit var binding: FragmentTitleBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentTitleBinding.inflate(inflater, container, false)

        val newsData = newsData()
        val adapter = NewsAdapter(newsData)

        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())

        val currentOrientation = resources.configuration.orientation

        adapter.itemClick = object : NewsAdapter.ItemClick {
            override fun onClick(position: Int) {
                val selectedItem = newsData[position]
                val detailFragment = DetailFragment.newInstance(selectedItem)

                if(currentOrientation == Configuration.ORIENTATION_PORTRAIT){
                    requireActivity().supportFragmentManager.commit {
                        replace(R.id.frameLayout, detailFragment)
                        setReorderingAllowed(true)
                        addToBackStack("")
                    }
                }else{
                    requireActivity().supportFragmentManager.commit {
                        replace(R.id.fl_detail, detailFragment)
                        setReorderingAllowed(true)
                        addToBackStack("")
                    }
                }
            }
        }

        return binding.root
    }


    private fun newsData(): List<NewsItem> {
        return listOf(
            NewsItem("뉴스 1", "내용 1"),
            NewsItem("뉴스 2", "내용 2")
        )
    }
}

onCretaeView 메소드에서 어댑터 설정을 진행한다.

 

아이템을 클릭하면 현재 NewsItem을 인자로 넣어 newInstance를 호출한다.

화면 방향에 따라 프래그먼트를 교체하며 백스택을 추가한다.

 

 

DetailFragment

class DetailFragment : Fragment() {
    private lateinit var binding: FragmentDetailBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentDetailBinding.inflate(inflater, container, false)

        arguments?.let {
            val title = it.getString("title")
            val article = it.getString("article")

            binding.tvTitle.text = title
            binding.tvArticle.text = article
        }

        return binding.root
    }

    companion object {
        @JvmStatic
        fun newInstance(newsItem: NewsItem) =
            DetailFragment().apply {
                arguments = Bundle().apply {
                    putString("title", newsItem.title)
                    putString("article", newsItem.article)
                }
            }
    }
}

arguments에서 기사 제목과 내용을 받아와 레이아웃 텍스트에 대입한다.

 

newInstance 메소드는 DetailFragment를 생성하고 NewsItem을 

인자로 받아 기사 제목과 내용을 Bundle객체를 통해 전달한다.

 

프래그먼트 간에 데이터를 전달할 때는 Bundle이라는 객체를 사용한다.

 

 

MainActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val currentOrientation = resources.configuration.orientation

        if(currentOrientation == Configuration.ORIENTATION_PORTRAIT){
            supportFragmentManager.commit {
                replace(R.id.frameLayout, TitleFragment())
                setReorderingAllowed(true)
            }
        }else{ 
            supportFragmentManager.commit {
                replace(R.id.fl_title, TitleFragment())
                replace(R.id.fl_detail, DetailFragment())
                setReorderingAllowed(true)
            }
        }
    }
}

화면 방향에 따라 각 프레임 레이아웃에 해당하는 초기 프래그먼트를 설정한다. 

프래그먼트를 사용함으로써 같은 화면을 재사용하는 것이 가능하다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함