일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- appbar
- Kotlin
- android
- 테스트
- textview
- 앱바
- Compose
- binding
- Dialog
- intent
- viewmodel
- activity
- livedata
- DART
- drift
- CustomScrollView
- scroll
- 앱
- data
- Coroutines
- TEST
- 안드로이드
- Flutter
- LifeCycle
- Button
- 계측
- ScrollView
- textfield
- tabbar
- Navigation
- Today
- Total
Study Record
[안드로이드] RecyclerView 살펴보기 본문
😶 RecyclerView 개요
스마트폰에서 자주 사용하는 앱은 거의 목록이 하나 이상 있다. 목록은 단순한 단어나 구문 목록부터 텍스트와 이미지가 포함된 카드와 같은 더 복잡한 항목을 포함한다. 이렇게 어떤 콘텐츠든 상관없이 데이터 목록을 표시하는 것이 Android 에서 가장 일반적인 UI 작업이다.
Android 는 목록이 있는 앱을 빌드할 수 있도록 RecyclerView 를 제공한다. RecyclerView 는 화면에서 목록 항목이 스크롤되면 다음 표시할 목록 항목에 이전 뷰를 재사용하기 때문에 처리 시간을 단축하고 목록이 더 원활하게 스크롤되도록 도와준다.
목록 예시)
😶 RecyclerView 동작 원리
RecyclerView 에서는 공통된 유형의 데이터 항목을 목록으로 표현한다.
item 은 표시할 목록의 단일 데이터 항목을 나타내니다. 예를 들어, 책의 이름, 책의 이미지, 책 소개를 보여주는 목록이라면 한 목록 당 책의 이름, 이미지, 소개 등의 정보를 가지고 있는 데이터 하나를 item 이라고 할 수 있다.
ViewHolder 는 데이터의 리스트인 item 을 보여줄 View 혹은 재사용할 View 들의 pool 이다. RecyclerView 는 항목이 스크롤되면 다음 표시할 항목에 이전 뷰를 재사용한다고 개요에서 설명한 바가 있다. 여기서 View 는 item 정보를 가져와 각 항목을 어떻게 보여줄 것인지에 대한 디자인이 담겨있다.
Adapter 는 데이터를 가져와 RecyclerView 에서 보여줄 준비를 한다.
RecyclerView 는 화면에 항목(View) 를 보여준다.
😶 RecyclerView 사용하기
1. 데이터 정의(item 정의)
공통된 유형의 데이터를 항목으로 가지고 있는 목록을 표시하는 RecyclerView 를 사용할 때는 공통된 유형의 데이터 타입을 따로 클래스를 만드는 것이 좋다. 개발자는 데이터를 모델링하거나 표현하는 클래스의 패키지 이름으로 "model" 을 사용하는 경우가 많다.
package com.example.affirmations.model
data class Snack(val name: String)
2. RecyclerView (xml 파일에 배치)
xml 파일에 RecyclerView 를 배치한다.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
android:scrollbars 속성으로 가로 스크롤(horizontal), 세로 스크롤(vertical) 로 나뉠 수 있다.
android:layoutManager 속성으로 목록의 모양을 정의할 수 있는데 LinearLayout 은 선형 모양이다.
3. View 만들기
item 데이터을 항목 디자인에 적용할 View 를 만들어야 한다. 원하는 이름으로 res/layout 디렉터리의 xml 파일을 만든다. 이 View 는 RecyclerView 에서 직접 관리하지 않고 ViewHolder 에서 관리한다.
> res/layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
4. Adapter 와 ViewHolder 만들기
전체적인 코드는 다음과 같다. Adapter 클래스 안에 ViewHolder 를 중첩하여 정의하는 것은 개발자가 코드를 보기 쉽게 하기 위해서이다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById<TextView>(R.id.list_item_title)
fun bind(snack: Snack) {
textView.text = snack.name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_snack, parent, false)
return SnackViewHolder(adapterLayout)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
holder.bind(data[position])
}
}
먼저, Adapter 는 ViewHodler 와 item 을 참고하여 RecyclerView 에 항목을 표시할 준비를 하고 ViewHolder 는 사용/재사용할 View 를 관리하는 역할을 한다.
따라서, Adapter 의 정의 부분을 보면 각 item 의 정보를 담고 있는 변수를 data 로 받고 RecyclerView.Adapter 를 상속받을 때 사용할 ViewHolder 를 표시해 준다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById<TextView>(R.id.list_item_title)
fun bind(snack: Snack) {
textView.text = snack.name
}
}
}
RecyclerView.Adapter 를 상속받은 클래스는 반드시 재정의해야 하는 함수 getItemCount() 와 onCreateViewHolder(), onBindViewHolder() 가 있다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById<TextView>(R.id.list_item_title)
fun bind(snack: Snack) {
textView.text = snack.name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_snack, parent, false)
return SnackViewHolder(adapterLayout)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
holder.bind(data[position])
}
}
하나씩 함수의 역할과 의미를 알아보면,
😶 getItemCount()
getItemCount() 는 목록에 포함된 항목의 개수를 리턴 받는다. 따라서, item 의 개수인 data.size 를 리턴하고 있다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
override fun getItemCount(): Int = data.size
}
😶 onCreateViewHolder()
onCreateViewHolder() 는 RecyclerView 의 새 ViewHolder 를 만들기 위해 레이아웃 관리자가 호출한다. 재사용할 수 있는 기존 ViewHolder 가 있는 경우 호출되지 않는다. 여기서 parent 는 새 목록 항목 보기가 자식으로 연결된 보기 그룹으로 부모는 RecyclerView 이다. ViewHolder 를 리턴 받아야 하므로 미리 만들어둔 SnackViewHolder 를 리턴한다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_snack, parent, false)
return SnackViewHolder(adapterLayout)
}
...
}
ViewHolder 는 item 데이터를 가지고 화면에 꾸밀 View 를 관리하기 때문에 RecyclerView.ViewHolder 는 화면에 꾸밀 View 를 인자로 받는다. 이 View 를 만들 때 LayoutInflater.from(context).inflate() 함수를 이용한다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById<TextView>(R.id.list_item_title)
fun bind(snack: Snack) {
textView.text = snack.name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_snack, parent, false)
return SnackViewHolder(adapterLayout)
}
}
inflate() 함수의 첫 번째 매개변수(resource)는 로드할 XML 레이아웃 리소스 ID 를 의미하고,
두 번째 매개변수(root)는 생성된 계층의 부모(attachToRoot = true) or 반환된 계층 루트에 대한 LayuotParams 값 집합을 제공하는 개체(attachToRoot = false)를 의미한다.
세 번째 매개변수(attachToRoot)가 true 일 경우, 계층 구조를 root(두 번째 매개변수)에 연결하고 false 인 경우 root(두 번째 인자)에 대해 올바른 하위 클래스를 만드는 데에만 사용한다.
세 번째 매개변수(attachToRoot)가 false 이므로, 두 번째 매개변수(root) 는 LayoutParams 값 집합만을 제공한다.
😶 onBindViewHodler()
onBindViewHodler() 는 목록에서 항목들을 보여줄 때 보여줄 항목의 내용을 바꿀 때(ex. 스크롤하여 보여줘야 할 항목을 변경해야할 경우 레이아웃 관리자에 의해 호출된다. onBindViewHolder() 는 두 개의 파라미터를 갖는다. 첫 번째는 onCreateViewHolder() 에서 생성한 ViewHodler 이고 두 번째 파라미터는 현재 보이는 목록의 position 이다.
class SnackAdapter(private val data: List<Snack>) :
RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById<TextView>(R.id.list_item_title)
fun bind(snack: Snack) {
textView.text = snack.name
}
}
override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
holder.bind(data[position])
}
}
5. Activity 에서 Adapter 정의하기
RecyclerView 에 adapter 를 정의한다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.recyclerView.adapter = SnackAdapter(listOf(Snack("happy"), Snack("hop"), Snack("dfsfdf")))
binding.recyclerView.setHasFixedSize(true)
}
}
setHasFixedSize 속성은 RecyclerView 의 레이아웃 크기가 고정되어 있을 경우 성능을 개선하기 위해 true 로 설정할 수 있다. RecyclerView 의 크기가 변경되지 않는다는 것을 아는 경우 true 로 설정해야 한다.
😶 RecyclerView 동작 보충 설명
RecyclerView 는 화면에서 목록 항목이 스크롤되면 다음 표시할 목록 항목에 이전 뷰를 재사용하기 때문에 처리 시간을 단축하고 목록이 더 원활하게 스크롤되도록 도와준다.
이것이 가능한 이유는 항목(View)을 관리하는 ViewHolder 가 item(항목에 대한 데이터)의 개수보다 적게 생성(onCreateVieWHodler()) 되고 해당 item 에 따라 View 를 표시해야 할 때(onBindViewHolder()) 이전 ViewHolder 를 재사용하기 때문이다.
😶 RecyclerView 여러가지 모양
RecyclerView 의 app:layoutManager 속성을 이용하면 여러가지 목록 모양을 배치할 수 있다. LinearLayoutManager , GridLayoutManager , StaggerdGridLayoutManager 가 있다.
LinearLayoutManager
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/horizontal_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_gravity="center"/>
선형으로 목록을 배치한다.
GridLayoutManager
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"/>
app:spanCount 속성으로 한 줄이아닌 여러줄로 항목을 배치할 수 있다.
StaggerdGridLayoutManager
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/vertical_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2"/>
GridLayoutManager 와 비슷한데 배칠될 항목의 크기가 제각각일 경우 지그재그 형식으로 보여진다.
출처
'안드로이드' 카테고리의 다른 글
[안드로이드] 간단한 단위 테스트 실행해보기 (0) | 2023.07.18 |
---|---|
[안드로이드] 어노테이션 (0) | 2023.07.18 |
[안드로이드] 간단한 계측 테스트 실행해보기 (0) | 2023.07.15 |
[안드로이드] 아이콘, 적응형 앱 아이콘 (0) | 2023.07.12 |
[안드로이드] RadioButton, RadioGroup 살펴보기 (0) | 2023.07.11 |