-
ViewHolder (RecyclerView & ListView)안드로이드 학습/Android 기술면접 대비 2023. 5. 25. 11:25
차례:
1. ViewHolder란
2. RecyclerView VS ListView
1. ViewHolder란?
ViewHolder는 각 뷰를 보관하는 Holder 객체로 이야기 할 수 있고 주로 RecyclerView 사용된다.
내가 안드로이드 입문을 본격적으로 시작하기도 훨씬전에는 RecyclerView가 없고 ListView만 있었던 듯 싶다. ListView에는 단점이 있었다.
getView() 메소드 내의 convertView 라는 녀석을 활용하여, 스크롤이 내려가면서 맨 위에 있던 아이템들은 화면에서 사라지고 다른 새로운 아이템을 구성해야할 때 뷰를 새로 다시 inflating하기보다, 기존에 사용하던 View를 재활용 한다.
inflate란???
더보기안드로이드에서 inflate는 xml에 표기된 레이아웃들을 메모리에 객체화시키는 행동이다.
쉽게 말해, xml코드들을 객체화해 소스코드에서 사용하기 위함이다.
Activity를 새로 만들 경우 안드로이드에서는 소스코드와, xml 인 레이아웃 파일 이렇게 두개가 생성된다.
이때, Activity에서 setContentView()가 바로 xml을 객체화시키는 Inflate 동작이다.convertView 를 도입하여 재사용성을 높였지만 !!!
여전히 뷰를 구성하기 위한 findViewById() 호출을 해야했다. ListView에서 findViewById()를 호출 할 때 매우 큰 비용이 든다. 스크롤을 내릴때 많은 cost를 요구하게 되고 결국 속도 저하를 초래하게 된다.
이러한 문제를 해결하기 위해 ViewHolder Pattern이 나타나게 되었다. 이 패턴의 원리는 각 뷰 객체를 뷰 홀더에 보관함으로써 findViewById 같은 반복적으로 호출되는 메서드를 효과적으로 줄여 속도 향상에 많은 기여를 할 수 있는 패턴이다.
아래는 RecyclerView의 adapter이다.
더보기class RecyclerViewAdapter(dataList:MutableList<Post>, mContext: Context) : RecyclerView.Adapter<RecyclerViewAdapter.MyCustomViewHolder>() { var dataList = dataList var mContext = mContext inner class MyCustomViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val title: TextView = itemView.findViewById(R.id.title) private val content: TextView = itemView.findViewById(R.id.content) fun bind(post: Post) { title.text = post.title content.text = post.content } } //만들어진 뷰홀더 없을때 뷰홀더(레이아웃) 생성하는 함수 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyCustomViewHolder { val view = LayoutInflater.from(mContext).inflate(R.layout.item_list,parent,false) return MyCustomViewHolder(view) } override fun getItemCount(): Int = dataList.size override fun onBindViewHolder(holder: MyCustomViewHolder, position: Int) { holder.bind(dataList[position]) } }
위의 MyCustomViewHolder와 3가지 메서드를 구현되어 있습니다.
- onCreateViewHolder : 뷰홀더를 생성(레이아웃 생성)
- onBindViewHolder : ViewHolder 에 data 를 넣는 작업 수행. 뷰홀더가 재활용됨.
- getItemCount : 아이템 개수를 조회
getItemCount() -> onCreateViewHolder() -> onBindViewHolder() 순으로 호출된다.
findViewById를 계속 사용하지 않고 이미 생성된 자식뷰를 재활용된다.
이것은 ListView의 adapter이다.
더보기class ListViewAdapter (val context: Context, val dataList: MutableList<Post>) : BaseAdapter() { override fun getView(position: Int, view: View?, parent: ViewGroup?): View? { var convertView = LayoutInflater.from(parent?.context).inflate(R.layout.item_list, parent, false) val post: Post = dataList[position] val title = convertView.findViewById<TextView>(R.id.title) val content = convertView.findViewById<TextView>(R.id.content) title.text = post.title content.text = post.content return convertView } override fun getCount(): Int = dataList.size override fun getItem(position: Int): Post = dataList[position] override fun getItemId(position: Int): Long = position.toLong() }
getView 메소드의 3가지 파라미터가 (position: Int, view: View?, parent: ViewGroup?)
- position: 아이템의 index를 의미한다. ListView에서 보일 아이템의 위치 정보라고 할 수 있다.
- view: 현재 index에 해당되는 뷰 객체를 의미한다. 안드로이드에서는 선택 위젯이 데이터가 많아 스크롤이 될때 뷰를 재활용 하는 메커니즘을 가지고 있어 한번 만들어진 뷰가 화면 상에 그대로 다시 보일 수 있도록 되어 있다.
- parent: 이 뷰를 포함하고 있는 부모 컨테이너 객체.
view는 재활용하는데, 정작 데이터 셋팅할 때 convertView의 자식뷰(textView 등) 정보를 매번 findViewById()를 통해 다시 가져와 재연결함 >> 비효율적
즉즉즉,,, 정리하자면 viewholder를 이용해 자식뷰를 findViewById로 연결시켜 주는 부분을 재활용해 성능을 향상시킬수 있다!!!
ListView에서도 ViewHolder 패턴 적용은 가능하다
더보기override fun getView(position: Int, view: View?, parent: ViewGroup?): View? { var convertView = view val holder = ViewHolder() if (convertView == null) { convertView = LayoutInflater.from(parent?.context) .inflate(w2022v9o12.simple.androidproject.R.layout.item_list, parent, false) if (convertView != null) { holder.title = convertView.findViewById(w2022v9o12.simple.androidproject.R.id.title) holder.content = convertView.findViewById(w2022v9o12.simple.androidproject.R.id.content) convertView.setTag(holder); } } val post: Post = dataList[position] holder.title?.text = post.title holder.content?.text = post.content return convertView } private class ViewHolder { var title: TextView? = null var content: TextView? = null } override fun getViewTypeCount(): Int = dataList.size override fun getItemViewType(position: Int): Int = position override fun getCount(): Int = dataList.size override fun getItem(position: Int): Post = dataList[position] override fun getItemId(position: Int): Long = position.toLong()
다음 세가지를 비교/정리
- ListView
- 리스트뷰에서는 BaseAdapter를 상속받은 ArrayAdapter나 CursorAdapter 등을 사용한다.
ViewHolder 패턴을 선택적으로 구현하기 때문에 구현하지 않는 경우 각각의 View를 그릴 때마다 findViewById()를 호출하기 때문에 성능 저하 문제가 발생한다.
getView() 메소드에서 뷰를 그릴 때마다 findViewById()를 매번 호출하여 성능이 저하된다.
- 리스트뷰에서는 BaseAdapter를 상속받은 ArrayAdapter나 CursorAdapter 등을 사용한다.
- ListView+ViewHolder
- 리스트뷰에서 ViewHolder 패턴을 구현한다면 성능에 관해서는 RecyclerView와 비슷하지만 기존의 ListView는 뷰 커스텀 작업에 대한 유연성이 떨어진다.
- RecyclerView
- 리싸이클러뷰는 ViewHolder 패턴의 사용을 강제하고 Adapter 클래스를 직접 구현하기 때문에 뷰 커스텀 작업에 대한 유연성이 ListView보다 더욱 쉽고 편하다.
2. RecyclerView VS ListView
RecyclerView ListView ViewHolder ViewHolder 패턴 이용 ViewHolder 패턴 이용할 필요 X Item Layout 가로/세로/지그재그 모두 지원 세로만 지원 Item Animation 아이템 애니메이션 처리 클래스 O 아이템 애니메이션 처리 클래스 X Adapter 데이터 제공을 위해 직접 구현 다양한 소스에 대한 어댑터 존재 Decoration 많은 구분선 설정 쉽게 구분 가능 Click Event 개별 터치 이벤트 관리O, 클릭 처리 기능 X 클릭 이벤트에 바인딩 하기위한 인터페이스 존재 - ViewHolder을 강제해 효율성을 높였고
- 가로/세로/지그재그 패턴이 모두 가능하고
- 애니메이션 처리 클래스도 가능한 등등
- 여러모로 RecyclerView가 더 기능도 많고 좋아보이기 때문에 그냥 RecyclerView 쓰자....
RecyclerView 추가 코드들 :
1. RecyclerViewAdapter.kt
더보기class RecyclerViewAdapter(dataList:MutableList<Post>, mContext: Context) : RecyclerView.Adapter<RecyclerViewAdapter.MyCustomViewHolder>() { var dataList = dataList var mContext = mContext inner class MyCustomViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val title: TextView = itemView.findViewById(R.id.title) private val content: TextView = itemView.findViewById(R.id.content) fun bind(post: Post) { title.text = post.title content.text = post.content } } //만들어진 뷰홀더 없을때 뷰홀더(레이아웃) 생성하는 함수 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyCustomViewHolder { val view = LayoutInflater.from(mContext).inflate(R.layout.item_list,parent,false) return MyCustomViewHolder(view) } override fun getItemCount(): Int = dataList.size override fun onBindViewHolder(holder: MyCustomViewHolder, position: Int) { holder.bind(dataList[position]) } }
2. Post.kt
더보기data class Post( val title:String, val content: String )
3. item_list.xml
더보기<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dp" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="30dp" android:text="title" android:textSize="30sp" android:textStyle="bold" android:gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="20dp" android:text="content" android:textSize="12sp" android:gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/title" app:layout_constraintTop_toTopOf="@+id/title" /> </androidx.constraintlayout.widget.ConstraintLayout>
4. RecyclerViewActivity.kt
더보기class RecyclerViewActivity : AppCompatActivity() { private lateinit var binding: ActivityRecyclerViewBinding val postData: MutableList<Post> = mutableListOf(); override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityRecyclerViewBinding.inflate(layoutInflater) setContentView(binding.root) addDataInPost() val adapter = RecyclerViewAdapter(postData, this) binding.recyclerView.adapter = adapter //리사이클러뷰에 어댑터 연결 binding.recyclerView.layoutManager = LinearLayoutManager(this) //레이아웃 매니저 연결 } fun addDataInPost() { for (i in 0..1000) { postData.add(Post(i.toString(), "content ${i.toString()}")) } } }
ListView 추가 코드들 :
1. ListViewAdapter.kt
더보기class ListViewAdapter (val context: Context, val dataList: MutableList<Post>) : BaseAdapter() { override fun getView(position: Int, view: View?, parent: ViewGroup?): View? { var convertView = LayoutInflater.from(parent?.context).inflate(R.layout.item_list, parent, false) val post: Post = dataList[position] val title = convertView.findViewById<TextView>(R.id.title) val content = convertView.findViewById<TextView>(R.id.content) title.text = post.title content.text = post.content return convertView } override fun getCount(): Int = dataList.size override fun getItem(position: Int): Post = dataList[position] override fun getItemId(position: Int): Long = position.toLong() }
2. ListViewActivity.kt
더보기class ListViewActivity : AppCompatActivity() { private lateinit var binding: ActivityListViewBinding val postData: MutableList<Post> = mutableListOf(); override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityListViewBinding.inflate(layoutInflater) setContentView(binding.root) addDataInPost() val dogAdapter = ListViewAdapter(this, postData) binding.mainListView.adapter = dogAdapter } fun addDataInPost() { for (i in 0..1000) { postData.add(Post(i.toString(), "content ${i}")) } } }
Post.kt 코드와 item_list.xml 동일함으로 생략
'안드로이드 학습 > Android 기술면접 대비' 카테고리의 다른 글
ANR이란 (0) 2023.06.12 Android Service (0) 2023.06.12 Android 런타임 ART 및 Dalvik (0) 2023.06.05 안드로이드 참고할한 링크들 (0) 2023.05.29 Android Process와 Thread (0) 2023.05.22