6. ViewHolder패턴을 활용한 ListView 만들기
페이지 정보
작성자 관리자 댓글 0건 조회 2,708회 작성일 21-05-20 17:20본문
6. ViewHolder패턴을 활용한 ListView 만들기
ListView에서 발견된 문제점은, 스크롤을 움직이는 등 View가 보이거나 사라지면 그 때마다 findViewById를 통해 convertView에 들어갈 요소를 찾는다는 점이었다.
스크롤 할 때마다 View를 찾으면 리소스를 많이 사용하게 되고, 속도가 느려진다.
ViewHolder를 이용하면 이 View의 재활용(recycle)이 가능하다.
ViewHolder란 현재 화면에서 보이는 아이템 개수만큼만 생성되고 스크롤이 발생하면 ViewHolder를 재사용한 후 데이터만 바꿔주기 때문에 앱의 효율이 향상됩니다.
1. 프로젝트 생성
프로젝트명 : ViewHolderTest
2. 아이템 뷰 만들기 : custom_item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/image_title"
android:layout_weight="1" />
<LinearLayout android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="4">
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text_title"
android:textSize="24dp"
android:textColor="#000000"
android:gravity="center_vertical"
android:layout_weight="2" />
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text_sub_title"
android:textSize="16dp"
android:textColor="#666666"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
커스텀 아이템 뷰는 이미지와 타이틀, 서브 타이틀로 구성했습니다.
이미지를 사용하기 위해 적절한 이미지를 res/drawable에 추가합니다.
3. 이미지 추가
4. 리스트 뷰 만들기
기본으로 생성되어 있는 activity_main.xml에서 만들겠습니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ListView의 id를 listView로 지정했습니다.
5. 어댑터 만들기
모든 소스코드가 MainActivity에 구현된다면 가독성이 떨어지고 유지보수가 어려워집니다.
새로운 코틀린 파일을 생성해서 어댑터를 구현하겠습니다.
ListViewAdapter, ListViewItem 클래스를 생성했습니다.
ListViewItem 클래스는 date class로 만들어줍니다.
파일명 : ListViewItem.kt
import android.graphics.drawable.Drawable
data class ListViewItem(val icon: Drawable, val title: String, val subTitle: String)
BaseAdapter 클래스를 상속해서 필요한 메서드를 override 합니다.
오버라이딩된 getView 메서드를 통해서 실제 리스트 뷰에 그려질 아이템을 셋팅합니다.
LayoutInflater를 사용해서 xml 파일을 클래스로 사용할 수 있도록 inflate 합니다.
그렇게 변환된 뷰에 리소스를 셋팅합니다.
6. 어댑터 연결하기
MainActivity로 넘어와서 아이템 리스트와 어댑터를 생성해서 리스트 뷰에 뿌려주겠습니다.
파일명 : MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listview = findViewById<View>(R.id.listview) as ListView
val items = mutableListOf<ListViewItem>()
items.add(ListViewItem(ContextCompat.getDrawable(this, R.drawable.instargram)!!, "인스타그램", "인스타그램 입니다"))
items.add(ListViewItem(ContextCompat.getDrawable(this, R.drawable.fasebook)!!, "페이스북", "페이스북 입니다"))
items.add(ListViewItem(ContextCompat.getDrawable(this, R.drawable.youtube)!!, "유튜브", "유튜브 입니다"))
val adapter = ListViewAdapter(items)
listview.adapter = adapter
listview.setOnItemClickListener { parent: AdapterView<*>, view: View, position: Int, id: Long ->
val item = parent.getItemAtPosition(position) as ListViewItem
Toast.makeText(this, item.title, Toast.LENGTH_SHORT).show()
}
}
}
ListViewItem만 담을 수 있는 더미 데이터(items)를 만들고 어댑터의 생성자에 인자로 전달합니다.
마지막으로 listView.adapter = adapter 어댑터 셋팅한다.
7. 실행
커스텀 리스트뷰의 실행을 확인한다.
convertView 변수가 비어있을 때만 새롭게 inflate 되므로 View 객체를 재활용하게 되어있습니다.
하지만 뷰의 속성들의 id를 찾는 것은 계속해서 반복됩니다. 즉, findViewById() 메서드가 아이템마다 호출된다는 것입니다.
뷰의 깊이가 심하게 깊거나 자식이 많은 경우가 아니라면 findViewById()가 호출되는 시간은 크게 염려할 수준은 아닙니다.
조금이라도 성능 개선을 위해서 ViewHolder 패턴을 적용해보겠습니다.
8. CustomViewHolder 클래스 생성
파일명 : CustomViewHolder.kt
class CustomViewHolder {
var drawableIcon: ImageView? = null
var textTitle: TextView? = null
var textSubTitle: TextView? = null
}
9. 아답터 수정
getView 메서드를 아래와 같이 수정합니다.
override fun getView(position: Int, view: View?, parent: ViewGroup?): View {
var convertView = view
var viewHolder: CustomViewHolder
if (convertView == null) {
convertView = LayoutInflater.from(parent?.context).inflate(R.layout.custom_item_list, parent, false)
viewHolder = CustomViewHolder()
viewHolder.drawableIcon = convertView.findViewById(R.id.image_title) as ImageView
viewHolder.textTitle = convertView.findViewById(R.id.text_title) as TextView
viewHolder.textSubTitle = convertView.findViewById(R.id.text_sub_title) as TextView
}else {
viewHolder = convertView.tag as CustomViewHolder
}
val item: ListViewItem = items[position]
viewHolder.drawableIcon!!.setImageDrawable(item.icon)
viewHolder.textTitle!!.text = item.title
viewHolder.textSubTitle!!.text = item.subTitle
return convertView!!
}
view가 비어있을 때 viewHolder 객체를 생성해서 widget을 설정해주고 비어있지 않다면 저장된 viewHolder 객체를 가져와서 재사용이 가능해집니다.
10. 실행
실행하고 결과를 확인한다.
댓글목록
등록된 댓글이 없습니다.