자라나라
[Kotlin] RecyclerView 만들기(生기초) 본문
1. build.gradle 수정
App 수준의 build.gradle 파일 속 android{} 안에 아래의 구성을 추가해준다. 데이터바인딩 이용 시 필수!
buildFeatures {
dataBinding true
}
2. RecyclerView 만들기
activity_main.xml에 RecyclerView를 만들어준 후 rv_profile 이라는 id를 줬다.

3. 아이템 클래스 만들기
프로필 리스트를 만들 것이기 때문에 Profiles 라는 이름의 kotlin class 파일 만들어 준다.
class Profiles(val photo: Int, val name: String, val age: Int, val job: String)
간단히 프로필사진, 이름, 나이, 직업을 받겠다.
photo는 getDrawable(Int)로 이미지를 불러올 것이기 때문에 Int값을 주었다
4. 아이템 레이아웃 그리기
위의 Profile을 그려줄 list_item 이라는 xml 파일을 만들어준다.
위에 RecyclerView에 item1 item2 ... 이 자리에 넣을 아이템을 그리는 것이다.
취향따라 그려준다. 나는 아래와 같이 그렸고

각각 iv_profile, tv_name, tv_age, tv_job 으로 id를 부여했다. (이미지와 텍스트는 임시임)
5. 어댑터 만들기
이제 아이템 view를 그려주기 위한 Adapter와 ViewHolder를 구현해야 한다.
이 두 클래스는 함께 작동하여 데이터 표시 방식을 정의한다.
ViewHolder는 목록에 있는 개별 항목의 레이아웃을 포함하는 View의 래퍼이며 Adapter 내부에 구현한다.
자세한 설명은 코드를 작성하며 해보겄슴다
ProfileAdapter라는 kotlin class 파일을 만들었다.
profileList를 받아서 RecyclerView.Adapter를 반환할 것이다.
class ProfileAdapter(val profileList: ArrayList<Profiles>) :
RecyclerView.Adapter<ProfileAdapter.CustomViewHolder>() {
}
이렇게 입력하면 아래와 같이 에러가 날 것이다.

빨간 글씨의 CustomViewHolder는 아직 정의되지 않아서 나타나는 에러이다.
(꼭 이름이 CustomViewHolder가 아니어도 된다. MyViewHolder, ProfileViewHolder 등 알아서 만들면 된다. 이건 간단한 예제라 이렇게 지었지만 다른 프로젝트에서는 용도에 맞게, 직관적으로 짓도록)
5 - 1. 뷰홀더 생성
그렇다면 우선 CustomViewHolder를 내부에 만들어주자
class ProfileAdapter(val profileList: ArrayList<Profiles>) :
RecyclerView.Adapter<ProfileAdapter.CustomViewHolder>() {
class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val photo = itemView.findViewById<ImageView>(R.id.iv_profile) // 프사
val name = itemView.findViewById<TextView>(R.id.tv_name) // 이름
val age = itemView.findViewById<TextView>(R.id.tv_age) // 나이
val job = itemView.findViewById<TextView>(R.id.tv_job) // 직업
}
}
이렇게 위 list_item에서 그려놓은 각 뷰의 객체를 보관하는 역할을 하는 것이 ViewHolder이다.
그렇다면 ViewHolder를 쓰는 이유는 무엇일까?
TextView나 ImageView 등 단일 뷰에 대한 findViewById() 는 비용이 많이 들지 않는다.
자기 자신의 ID만 확인하기 때문이다.
그런데 여러 개의 자식 뷰를 포함하는 레이아웃에서는, 매번 자신과 자신의 자식뷰를 모두 확인해서 가져오게 된다. 이 과정에서 고비용이 발생하는 것이다.
그리하여 findViewById()의 호출 횟수를 줄이기 위해 탄생한 것이 ViewHolder 패턴이다.
이를 사용하면 한 번 생성하고 저장된 뷰는 다시 findViewById()를 통해 불러올 필요가 없어진다.
이제 어댑터의 문제를 해결해보자

5 - 2. 어댑터 필수 메서드 구현
RecyclerView.Adapter를 상속해서 어댑터를 만들 땐
onCreateViewHolder()
onBindViewHolder()
getItemCount()
이 세 가지 추상메서드를 반드시 구현해 주어야 한다.
Alt + Enter 키로 자동 implement 할 수 있다.
(이중 getItemCount()를 제외한 두 메서드는 ViewHolder에 관한 것이라 위에서 CustomViewHolder를 제대로 완성 하지 않았으면 메서드 자동 implement가 안 될 것이다.)

하나하나 기능을 살펴보자
- onCreateViewHolder()
ViewHolder를 새로 만들어야 할 때 호출되는 메서드로, xml 레이아웃 뷰 객체를 생성하고 이를 뷰 홀더 객체에 담아 리턴해준다. ViewHolder와 그에 연결된 View를 생성, 초기화하지만 뷰의 콘텐츠를 채우진 않는다.
여기까지는 아직 특정 데이터에 바인딩된 상태가 아니기 때문이다. - getItemCount()
RecyclerView로 만들어지는 item의 총 개수를 반환한다. - onBindViewHolder()
ViewHolder를 데이터와 연결 즉 바인딩할 때 호출 되는 메서드. 뷰의 레이아웃을 채운다.
먼저 onCreateViewHolder()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return CustomViewHolder(view)
}
앞서 말한대로 레이아웃을 뷰홀더에 담아 리턴해주고 있다.
LayoutInflater란 xml에 정의된 Resource를 View 객체로 반환해 준다.
parent.context라 함은 이 어댑터와 연결 될 코틀린 파일(여기선 MainActivity)의 모든 정보를 의미한다고 보면 된다.
맨 뒤의 .inflate의 소스를 보면 아래와 같다

resource는 view를 만들고 싶은 레이아웃의 id를 말한다.
root는 attachToRoot가
true일 때 : 생성되는 View가 추가될 부모 뷰,
false일 때 : LayoutParams값을 설정해주기 위한 상위뷰,
null일 때 : android : layout_xxxx 값들 무시
attachToRoot는
true일 때: 생성되는 뷰를 root의 자식으로 만듦
false일 때: 생성되는 뷰의 LayoutParams를 생성하는 데에만 root를 씀
다음으로 getItemCount()
override fun getItemCount(): Int {
return profileList.size
}
우리가 어댑터의 파라미터로 받을 profileList의 길이를 리턴한다.
마지막 onBindViewHolder()
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.photo.setImageResource(profileList.get(position).photo)
holder.name.text = profileList.get(position).name
holder.age.text = profileList.get(position).age.toString()
holder.job.text = profileList.get(position).job
}
이미지리소스만 바인딩해주는 방법이 다르다
position 파라미터를 이용해 데이터의 순서에 맞게 아이템 레이아웃을 바인딩 해줄 것이다.
6. 메인액티비티에서 데이터 만들어 연결
우선 바인딩을 위해 아래와 같이 코드를 넣어준다.
ActivityMainBinding 은 activity_main의 데이터를 바인딩 하는 것을 말한다.
언더바 없애고 첫글자 대문자로 바꿔서 자동으로 네이밍 해준다.
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
}
6 - 1 실질적인 데이터 리스트 생성
임의로 Profiles 클래스 arrayList를 만들어 주었다.
이미지는 여자 남자 두개로 돌려썼다
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val profileList = arrayListOf<Profiles>(
Profiles(R.drawable.man, "윤해찬", 23, "요리사"),
Profiles(R.drawable.woman, "최현아", 23, "해녀"),
Profiles(R.drawable.man, "이진호", 25, "작곡가"),
Profiles(R.drawable.woman, "임혜진", 32, "의사"),
Profiles(R.drawable.woman, "진세아", 20, "도예가"),
Profiles(R.drawable.man, "배연우", 33, "펀드매니저"),
Profiles(R.drawable.woman, "한주연", 26, "쇼핑몰 ceo"),
Profiles(R.drawable.man, "지학현", 30, "변호사"),
)
}
}
6 - 2 레이아웃 설정 및 어댑터와 연결
리스트 바로 밑에 아래의 코드를 추가해 준다.
binding.rvProfile.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rvProfile.setHasFixedSize(true)
binding.rvProfile.adapter = ProfileAdapter(profileList)
layoutManager는 리스트를 어떤 형태로 보여줄지 타입을 지정해주는 것이다.
https://lakue.tistory.com/56 << layoutManager 종류에 대해선 이 분께서 정리를 잘 해놓으셨다..ㅎ
setHasFIxedSize(true)를 설정하지 않으면 아이템 항목이 추가 될 때마다 RecyclerView의 크기가 변경되고, 그렇게 되면 앱은 매번 크기를 다시 측정하고 그리는 것을 반복한다. 이런 고비용의 작업을 막기위해 꼭 추가해주자
마지막은 recyclerview의 어댑터를 이제까지 만든 ProfileAdapter로 설정해주는 것 profileList를 넣어서 말이다
실행해보면 잘 작동된다. (다크모드라 어둡게 나옴)

'Kotlin' 카테고리의 다른 글
[Kotlin] data class & enum class (0) | 2022.09.21 |
---|