두 개의 프래그먼트로 간단한 뉴스 리더 앱을 만들어보자.
class NewsAdapter(private val newsList:MutableList<NewsItem>,private val itemClick: (NewsItem) -> Unit) : RecyclerView.Adapter<NewsAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : Holder {
val binding = ItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val newsItem = newsList[position]
holder.bind(newsItem)
holder.itemView.setOnClickListener {
itemClick(newsItem)
}
}
override fun getItemCount(): Int {
return newsList.size
}
inner class Holder(private val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(newsItem: NewsItem) {
binding.titleText.text = newsItem.title
binding.smallText.text = newsItem.article
binding.root.setOnClickListener {
}
}
}
}
NewsTitle 프래그먼트에서 recyclerView를 사용할 생각이기 때문에 Adapter를 만들어줬다.
class NewsTitle : Fragment() {
private lateinit var adapter: NewsAdapter
private lateinit var binding: FragmentNewsTitleBinding
private val newsData = NewsData()
// 프래그먼트 뷰가 초기화되며, 정상적으로 초기화가 되었다면 뷰 객체를 반환한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentNewsTitleBinding.inflate(inflater, container, false)
return binding.root
}
// onCreateView()에서 뷰 객체가 반환된 직후에 호출되며, 뷰가 완전히 생성되었음을 보장한다.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val newsDummy = newsData.getNewsData()
// bundle로 데이터 전달
// bundle : 문자열로 된 키와 여러가지의 타입의 값을 매핑하여 다양한 데이터 타입 전달 가능.
adapter = NewsAdapter(newsDummy) { clickedNewsItem ->
val detailFragment = NewsDetail()
val bundle = Bundle()
bundle.putString("title", clickedNewsItem.title)
bundle.putString("article", clickedNewsItem.article)
detailFragment.arguments = bundle
// arguments : arguments에 Bundle()을 넘겨줌으로써 데이터를 넘겨줄 수 있다.
// arguments는 Bundle()을 파라미터로 받고
// Bundle()은 put~~() 메소드를 호출하여 데이터를 담아서 arguments에 넘김
val transaction = parentFragmentManager.beginTransaction()
transaction.replace(R.id.frame, detailFragment)
transaction.addToBackStack(null)
transaction.commit()
// transaction : 프래그먼트를 추가/교체/삭제하는 작업을 제공
// FragmentTransaction의 인스턴스는 FragmentManager의 beginTransaction() 메소드를 통해 얻을 수 있다.
}
// recyclerView와 어댑터 연결
binding.titleRecycler.adapter = adapter
binding.titleRecycler.layoutManager = LinearLayoutManager(context)
// recyclerView에 divider 만들어줌
val dividerItemDecoration = DividerItemDecoration(binding.titleRecycler.context, LinearLayoutManager(context).orientation)
binding.titleRecycler.addItemDecoration(dividerItemDecoration)
// divider의 간격 조절
val spaceDecoration = VerticalSpaceItemDecoration(30)
binding.titleRecycler.addItemDecoration(spaceDecoration)
}
inner class VerticalSpaceItemDecoration(private val verticalSpaceHeight: Int) :
RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
outRect.bottom = verticalSpaceHeight
outRect.top = verticalSpaceHeight
}
}
}
class NewsDetail : Fragment() {
private lateinit var binding: FragmentNewsDetailBinding
// 프래그먼트 뷰가 초기화되며, 정상적으로 초기화가 되었다면 뷰 객체를 반환한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentNewsDetailBinding.inflate(inflater, container, false)
val textView1: TextView = binding.textView1
val textView2: TextView = binding.textView2
// 'arguments'는 번들 객체를 나타내며, 객체에 전달된 데이터가 포함되어 있다.
// bundle로 전달된 데이터를 받아옴.
// null이 아닐 경우에만 코드가 실행된다.
arguments?.let {
val titleDetail = it.getString("title")
val articleDetail = it.getString("article")
textView1.text = titleDetail
textView2.text = articleDetail
}
return binding.root
}
}
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// 'supportFragmentManager'를 사용하여 프래그먼트의 트랜잭션을 시작.
// 트랜잭션은 fragment를 추가, 교체 or 제거하는 작업을 관리.
if (savedInstanceState == null) {
val titleFragment = NewsTitle()
supportFragmentManager.beginTransaction()
.replace(R.id.frame, titleFragment)
.commit()
// replace(R.id.frame) : 화면에 표시되는 프래그먼트를
// titleFragment 안에 있는 id값이 frame인 레이아웃으로 교체.
// commit() : 트랜잭션을 확정하고 적용.
}
}
}
data class NewsItem(
val title: String,
val article: String) {
}
초라한 뉴스 리더 앱 완성!
아 그리고 코드엔 이상이 없는데 앱이 강제종료 된다던가 켜지지 않는다면 코드와 로그캣을 잘확인해보자. adapter 연결 코드를 엉뚱한 곳에 써놔서 한참을 헤맸다^.^
잘 확인해볼게요 ! ^>^