이번 포스팅에서는 지난 포스팅의 리스트 뷰에 이어 리사이클러 뷰에 대해 다뤄보려 합니다. 두 뷰의 차이점을 한마디로 설명하면, "리사이클러 뷰가 리스트 뷰보다 성능은 좋지만, 사용 방법이 더 복잡하다"라고 할 수 있을 것 같습니다. 두 방법 모두 반드시 알아야 할만큼 중요한 내용입니다. 그래서 이번에는 뉴스를 분야별로 사용자에게 보여주는 뉴스 어플리케이션을 리사이클러 뷰로 구현해보도록 하겠습니다.
① news라는 이름으로 프로젝트를 생성한다.
② app 우클릭 > New > Android Resource File을 클릭한다.
③ 이름은 main_nav라 하고, Resource type은 Navigation으로 지정한다.
④ OK를 누르면 경고창이 나오는데 OK를 눌러주어야 한다. OK를 눌러야 build.gradle.kts (Module) 파일에 navigation 관련 의존성이 추가된다.
⑤ activity_main.xml의 TextView 태그를 삭제한 후 Design 모드로 열어 Containers > NavHostFragment를 드래그앤 드롭으로 흰 바탕 위에 올려 놓는다. 방금 만든 main_nav를 선택하고 OK를 누르면 된다.
⑥ 다시 코드로 돌아오면 에러가 나고 있을 것이다. 이는 Constraint Layout을 사용하고 있기 때문이다. width와 height를 match_parent로 변경하자.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp" />
① MainActivity가 있는 default 패키지에 우클릭 > New > Fragment > Fragment (Blank)를 클릭한다.
② Fragment Name은 EconomyFragment로 지정한다.
③ 같은 방법으로 PoliticsFragment, SocietyFragment를 만들자.
④ main_nav.xml 파일에 들어와서 휴대폰+ 아이콘을 클릭하면 방금 생성한 Fragment들이 보인다. 3개의 Fragment를 모두 클릭하여 화면에 띄워주자.
⑤ Fragment에 커서를 가져다대면 동그라미가 보이는데 이 동그라미에서 뻗어나오는 화살표로 다른 Fragment와 연결시킬 수 있다. 1번 Fragment에서 2, 3번으로 연결하고, 2번 Fragment에서 1, 3번으로 연결하고, 3번 Fragment에서 1, 2번으로 연결하면 된다.
⑥ frgament_economy.xml, frgament_politics.xml, frgament_society.xml 파일의 TextView 태그를 아래와 같이 수정한다. 경제는 상황에 맞게 정치, 사회로 변경한다.
<TextView
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="경제" />
⑦ fragment.xml 파일에서 사용하는 FrameLayout을 ConstraintLayout으로 변경한다. ConstraintLayout을 사용하는 이유는 네비게이션 바를 최하단에 배치하기 위함이다.
⑧ 방금 작성한 Text View를 LinearLayout으로 감싼다.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="경제" />
</LinearLayout>
⑨ LinearLayout 컨테이너에 커서를 가져다대면 아래와 같이 TextView에 동그라미 표가 나오는데 화살표를 Bottom에 가져다대면 된다.
⑩ 이와 같은 방식으로 정치, 사회 TextView도 넣어준다. 다른 Fragment xml 파일에도 적용해준다.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/economy"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="경제"
android:gravity="center"
android:layout_weight="1"
android:textSize="30sp" />
<TextView
android:id="@+id/politics"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="정치"
android:gravity="center"
android:layout_weight="1"
android:textSize="30sp" />
<TextView
android:id="@+id/society"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="사회"
android:gravity="center"
android:layout_weight="1"
android:textSize="30sp" />
</LinearLayout>
① economyFragment로 이동한 후, 아래의 내용만 남기고 모두 지운다.
package com.chrome.newslist
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class eeconomyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_economy, container, false)
}
}
② politics와 society Fragment로 이동할 수 있도록 onClickListener를 등록한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_economy, container, false)
val politics = view.findViewById<TextView>(R.id.politics)
politics.setOnClickListener {
}
val society = view.findViewById<TextView>(R.id.society)
society.setOnClickListener {
}
return view
}
③ onClickListener에 Fragment 전환 기능을 추가한다. economyFragment의 경우 economyFragment로의 전환은 넣지 않는다.
politics.setOnClickListener {
it.findNavController().navigate(R.id.action_economyFragment_to_politicsFragment)
}
④ politicsFragment와 societyFragment에도 동일하게 작성한다.
⑤ Fragment 전환을 제대로 확인해보기 위해 화면마다 다른 TextView가 나타나도록 Fragment xml파일에 아래의 내용을 추가하자. LinearLayout 컨테이너 위에 작성하면 된다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="경제"
android:layout_marginTop="10dp"
android:textSize="40sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
⑥ 코드를 실행해보면 네비게이션 바의 버튼을 따라 Fragment가 전환되고 있음을 확인할 수 있다.
Dependency 'androidx.navigation:navigation-common:2.7.0' requires libraries and applications that
depend on it to compile against version 34 or later of the
Android APIs.
:app is currently compiled against android-33.
android {
namespace = "com.chrome.news"
compileSdk = 34
① fragment_economy.xml 파일에 아래와 같이 RecyclerView를 적용한다. TextView 태그와 LinearLayout 컨테이너 사이에 작성하면 된다.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/newsRV"
android:layout_marginTop="70dp"
android:layout_marginBottom="80dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
② 다른 Fragment의 xml에도 동일하게 적용한다.
③ 디폴트 패키지 하위로 RVAdapter Kotlin 클래스를 만든다.
④ 아래와 같이 입력한 후, class RVAdapter에 커서를 둔 상태에서 우클릭 > Implement Members를 클릭한다.
class RVAdapter(val itmes: MutableList<String>) : RecyclerView.Adapter<RVAdapter.ViewHolder>(){
⑤ ViewHolder라는 inner class를 작성한다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVAdapter.ViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: RVAdapter.ViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
}
⑥ res > layout 우클릭 > New > Layout Resource File을 클릭한 후 rv_item이라는 이름의 xml파일을 생성한다.
⑦ rv_item.xml 파일의 레이아웃을 LinearLayout으로 변경하고 아래의 내용을 추가한다.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp">
<TextView
android:id="@+id/rvTextId"
android:text="TextArea"
android:layout_margin="10dp"
android:textStyle="bold"
android:textSize="25sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
⑧ RVAdapter 클래스로 이동한 후, 아래의 내용을 작성한다.
class RVAdapter(val items: MutableList<String>) : RecyclerView.Adapter<RVAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: RVAdapter.ViewHolder, position: Int) {
holder.bindItems(items[position])
}
override fun getItemCount(): Int {
return items.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(item : String) {
val rv_text = itemView.findViewById<TextView>(R.id.rvTextId)
rv_text.text = item
}
}
}
⑨ EconomyFragment 파일을 아래와 같이 수정한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_economy, container, false)
val items = mutableListOf<String>()
items.add("‘초전도체의 날’…SNS글 하나에 서남·덕성·서원 등 6개종목 상한가[투자360]")
items.add("대신증권, 사옥매각 협상자로 이지스자산운용 선정")
items.add("오스템임플란트, 16년 만에 상장폐지…“아듀, 코스닥”")
items.add("한국씨티은행, 상반기 순익 1천777억원…작년의 2.2배")
items.add("MSCI 신규편입 종목 일제히 하락…\"일찍 사고 재료 소멸에 매도\"(종합)")
items.add("불티나는 '불닭볽음면'…삼양식품, 상한가[핫스탁](종합)")
items.add("[부동산 라운지] 광주·평택·세종…'전국구 청약' 단지 속속 공급")
items.add("LH, 철근 누락 전관업체와 3년간 2335억원 수의 계약")
items.add("삼표시멘트 2Q 영업익 319억원…전년比 147%↑")
items.add("CFD·부동산 PF에 발목 잡힌 중소형 증권사, 2Q 실적 '부진'")
val rv = view.findViewById<RecyclerView>(R.id.newsRV)
val rvAdapter = RVAdapter(items)
rv.adapter = rvAdapter
rv.layoutManager = LinearLayoutManager(context)
val politics = view.findViewById<TextView>(R.id.politics)
politics.setOnClickListener {
it.findNavController().navigate(R.id.action_economyFragment_to_politicsFragment)
}
val society = view.findViewById<TextView>(R.id.society)
society.setOnClickListener {
it.findNavController().navigate(R.id.action_economyFragment_to_societyFragment)
}
return view
}
⑩ PoliticsFragment 파일도 아래와 같이 수정한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_politics, container, false)
val items = mutableListOf<String>()
items.add("국방부, 해병대 전 수사단장 혐의 '집단항명 수괴→항명' 변경(종합)")
items.add("광주 찾은 송영길 “쫄지 말자…검찰 독재정권 맞설 것”")
items.add("김태우 \"강서구 복귀\" 논란 與, 역풍 우려 공천 선긋기")
items.add("‘사면’ 김태우 “강서구 돌아가겠다”…달아오르는 10월 보선")
items.add("민주, 김태우 사면에 \"사법부에 대한 대통령의 정면 도전\"")
items.add("전·현직 대통령, 잼버리 두고 '극과 극' 평가")
items.add("해병대 전우회 \"채수근 상병 수사, 일체 외부 간섭 없어야\"")
items.add("尹 \"잼버리 무난히 마무리 종교계·기업·국민에 감사\"")
items.add("극으로 치닫는 잼버리 '네 탓' 책임 공방…\"文이냐, 尹이냐\"(종합)")
items.add("문재인, '잼버리 파행' 윤정부 비판 이유는")
items.add("[단독] \"안되는 줄 알지만…\" 행안부 요청에 구급차까지")
val rv = view.findViewById<RecyclerView>(R.id.newsRV)
val rvAdapter = RVAdapter(items)
rv.adapter = rvAdapter
rv.layoutManager = LinearLayoutManager(context)
val economy = view.findViewById<TextView>(R.id.economy)
economy.setOnClickListener {
it.findNavController().navigate(R.id.action_politicsFragment_to_economyFragment)
}
val society = view.findViewById<TextView>(R.id.society)
society.setOnClickListener {
it.findNavController().navigate(R.id.action_politicsFragment_to_societyFragment)
}
return view
}
⑪ SocietyFragment 파일도 아래와 같이 수정한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_society, container, false)
val items = mutableListOf<String>()
items.add("윤 대통령 8·15 특사, 중대경제범죄 기업인·김태우 등 논란의 사면")
items.add("이원욱 \"김태우 사면, 특별사면 역사에 오점\"")
items.add("여가부 \"잼버리 책임의식 부족했다는 지적에 동의할 수 없다\"")
items.add("'위기상황에서 더 빛났다' 잼버리 대원 지원 마친 용인시")
items.add("광복절 특사 키워드는 '경제'… 기업인에 후했지만 정치인에 엄격")
items.add("“대신 결제하겠다”…잼버리 대원에 야식 선물한 마포구 부부")
items.add("28년 전 뉴월드호텔살인사건 수배범 서울서 숨진 채 발견")
items.add("말다툼 중 우산에 손 넣길래 간신히 빼앗아 살펴보니 ‘흉기’(영상)")
items.add("이번엔 '가짜 세입자'…청년 전세대출 악용한 일당")
items.add("\"광복절이 뭔데요?\"…Z세대 4명중 1명 \"광복절 몰라\"")
items.add("경찰 \"서이초 교사 사망, 학부모 범죄 혐의 발견 못 해\"")
val rv = view.findViewById<RecyclerView>(R.id.newsRV)
val rvAdapter = RVAdapter(items)
rv.adapter = rvAdapter
rv.layoutManager = LinearLayoutManager(context)
val politics = view.findViewById<TextView>(R.id.politics)
politics.setOnClickListener {
it.findNavController().navigate(R.id.action_societyFragment_to_politicsFragment)
}
val economy = view.findViewById<TextView>(R.id.economy)
economy.setOnClickListener {
it.findNavController().navigate(R.id.action_societyFragment_to_economyFragment)
}
return view
}
⑫ 코드를 실행시키면 네비게이션 바의 버튼에 따라 분야 별 뉴스 목록이 나오는 것을 확인할 수 있다.
RecyclerView도 한 번 보고 이해하기는 쉽지 않기 때문에 구성 순서를 다시 한번 살펴보도록 하겠다. 구성 순서는 ListView와 크게 다르지 않다.
① recylerview_item.xml 파일을 layout 디렉토리 하위에 추가
② LinearLayout으로 변경한 뒤, TextView 또는 ImageView 태그 추가
③ Adapter 클래스 생성 후 아래의 내용을 입력, Generics는 상황에 맞게 변경
class RVAdapter(val items: MutableList<String>) : RecyclerView.Adapter<RVAdapter.ViewHolder>(){
}
④ 메서드 오버라이딩
⑤ RecyclerView를 적용할 Activity의 xml 파일에 RecyclerView 태그 추가
⑥ RecyclerView를 적용할 Activity 파일에 mutableListOf로 리스트 생성 후 add로 items 추가
⑦ mutableListOf로 생성한 List를 Adapter의 입력인자로 넣고, 방금 만든 RecyclerView의 adapter로 넘겨준다. 아래의 코드 정도는 외워두는게 좋다.
val recyclerView = findViewById<RecyclerView>(R.id.rv)
recyclerView.adapter = RVAdapter(items)
recyclerView.layoutManager = LinearLayoutManager(this)