Android 프로그래밍 2

본문 바로가기
사이트 내 전체검색


Android 프로그래밍 2
Android 프로그래밍 2

6. ViewHolder패턴을 활용한 ListView 만들기

페이지 정보

작성자 관리자 댓글 0건 조회 1,347회 작성일 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. 이미지 추가


1.PNG




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)


data class는 하나의 데이터 타입으로 사용됩니다.

다음은 ListViewAdapter 파일입니다.

파일명 : ListViewAdapter.kt

class ListViewAdapter(private val items: MutableList<ListViewItem>): BaseAdapter() {
    override fun getCount(): Int = items.size

    override fun getItem(position: Int): ListViewItem = items[position]

    override fun getItemId(position: Int): Long = position.toLong()

    override fun getView(position: Int, view: View?, parent: ViewGroup?): View {
        var convertView = view
        if (convertView == null)
            convertView = LayoutInflater.from(parent?.context).inflate(R.layout.custom_item_list, parent, false)
        val item: ListViewItem = items[position]

        val iconImageView = convertView!!.findViewById(R.id.image_title) as ImageView
        val titleTextView = convertView!!.findViewById(R.id.text_title) as TextView
        val subTitleTextView = convertView!!.findViewById(R.id.text_sub_title) as TextView

        iconImageView.setImageDrawable(item.icon)
        titleTextView.text = item.title
        subTitleTextView.text = item.subTitle
        return convertView
    }
}
 

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. 실행


실행하고 결과를 확인한다.



2.PNG


댓글목록

등록된 댓글이 없습니다.


개인정보취급방침 서비스이용약관 모바일 버전으로 보기 상단으로

TEL. 063-469-4551 FAX. 063-469-4560 전북 군산시 대학로 558
군산대학교 컴퓨터정보공학과

Copyright © www.leelab.co.kr. All rights reserved.