1. ViewHolder의 필요성

 

앞선 글에서 설명했듯 많은 데이터를 

항목화하여 표현하는 AdapterView 에서는

동작할 때마다 각 항목

(xml형식의 레이아웃)을

View객체로 가져와야 한다

 

그리고 이렇게 생성한 View에 

포함된 뷰를 찾아

( Java 에서는 findViewById)

접근하는 일도 아주 빈번하게 일어난다.

 

문제는 이 작업은 

많은 리소스를 필요로 한다는 점이다. 

 

*참고

 

 

이를 해결하기 위해 사용되는 것이 ViewHolder이다.

한번 inflate 된 View를 객체에 담아 두어 

재사용하는 방식이다.

 

즉, View를 생성하고 활용하는 과정에서

리소스를 효율적으로 사용하기 위한

패턴이라고 볼 수 있다.

 

 

 

2. ListView에 ViewHolder 적용하기

 


이러한 ViewHolder 패턴은

꼭 RecyclerView에서만 사용되는 건 아니다.

오히려 ListView에

ViewHolder를 적용해보면
어떤 이점이 있는지 이해하기 편할 것 같다.

1) 기본 구성 (ViewHolder 없이 구현)

(1) activity_main.xml

먼저 기본적인 Main Activity의 화면 구성은 
다음과 같이 단순하게 해 놓았다

 

<?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="0dp"
      android:layout_height="0dp"
      android:layout_marginStart="1dp"
      android:layout_marginLeft="1dp"
      android:layout_marginTop="1dp"
      android:layout_marginEnd="1dp"
      android:layout_marginRight="1dp"
      android:layout_marginBottom="1dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

(2) item.xml

ListView에 요소로 들어갈 

item 레이아웃을 생성한다.

 

 

 

<?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="50dp"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/textName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="MacBook Pro" />

    <TextView
        android:id="@+id/textColor"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="sliver" />

    <TextView
        android:id="@+id/textOs"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="OS X" />
</LinearLayout>

 

 

(3) NoteBook.kt

Data class를 선언한다.

하나의 항목에 들어갈 데이터를

저장하는 객체라고 보면 된다.

 

data class NoteBook(val name:String, val color:String, val os:String)

 

 

 

 

 

 

 

 

 

(4) CustomBaseAdapter.kt

일반적으로 구현하는 어댑터이다.

getView를 자세히 보면 

Item.xml을 inflate 하고 inflate 한 View에서 

textView를 찾아서 데이터를 바인딩해준다.

 

그리고 마지막으로 inflate한 View전체에 

click이벤트를 지정한다.

 

class CustomBaseAdapter(private val context:Context, private val dataList: List<NoteBook>) : BaseAdapter() {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        // Inflating
        val view = LayoutInflater.from(parent?.context).inflate(R.layout.item,parent,false)

        val getData = dataList[position]

        // findViewById
        view.textName.text = getData.name
        view.textOs.text = getData.os
        view.textColor.text = getData.color

        // 이벤트 생성
        view.setOnClickListener(View.OnClickListener {
            Toast.makeText(context,getData.toString(),Toast.LENGTH_SHORT).show()
        })
        return view
    }

    override fun getItem(position: Int): Any {
        return dataList[position]
    }

    override fun getItemId(position: Int): Long {
        return 0
    }

    override fun getCount(): Int {
        return dataList.size
    }
}

 

 

** 이전 글에서 Adapter에 대한 내용과 일치하는지 확인하기

더보기

Adapter가 알고 있어야 하는 것


(1) Data가 무엇인지 알고 있어야 한다.
(2) 어떤 형태로 item을 구성할지 layout을 알고 있어야 한다.
(3) Data와 layout을 매핑할 수 있어야 한다.
(4) item도 화면에 표현하는 내용이기 때문에 Context를 알고 있어야 한다.

 

 

 

[Android] RecyclerView 이해하기 (1) - Adapter는 무엇인가

RecyclerView를 이해하기에 앞서 AdapterView를 알고 있어야 한다. 왜냐하면 나는 RecyclerView를 AdapterView의 확장판쯤으로 이해했기 때문이다. 1. AdapterView AdapterView는 많은 데이터를 하나의 뷰로 보여..

kyome.tistory.com

 

 

 

(5) MainActivity.kt

Main Activity에서는 지금까지 작성한 내용을 조합한다고 보면 된다.

Data를 list로 선언해서 Adapter에 초기화 값으로 선언한 후 

Adapter 를 ListView에 연결하면 정상적으로 동작한다.

 

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Data
        val noteBookList = listOf<NoteBook>(
            NoteBook("MacBook Pro","Sliver","OS X"),
            NoteBook("MacBook Pro","Gray","OS X"),
            NoteBook("Galaxy Book","Sliver","Windows")
        )

        //CustomAdapter를 View에 추가
        var arrayAdapter = CustomBaseAdapter(this,noteBookList)
        listView.adapter =arrayAdapter
}

 

2) 문제점

getView메서드 내에

Log.d를 찍어보면 알겠지만

화면에 Item이 보여야 할 때마다 

계속 반복적으로 호출된다.

 

앞에서 반복적으로 이야기했듯

문제는 여기서 생긴다.

 

getView 안을 살펴보면 아래와 같은 구문들이

아무 조건 없이 반복적으로 실행되기 때문이다.

 

...

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
    
    /* 리소스 사용!! */
    // Inflating
    val view = LayoutInflater.from(parent?.context).inflate(R.layout.item,parent,false)
    
    ...
    
    /* 리소스 사용!! */
    // findViewById
    view.textName.text = getData.name
    view.textOs.text = getData.os
    view.textColor.text = getData.color
    
    ...
    
    return view
}

 

 

3) 해결방법 : ViewHolder

해결방법은 간단하다.

View를 한번 만들었으면

어딘가에 담아 두었다가

필요할 때 재사용하는 것이다.

 

(1) VIewHodler class 생성

그 '어딘가'가 ViewHolder 클래스이다.

Item.xml에서 사용한 구성요소에 맞추어

변수를 선언하고 setter를 생성해두면 된다.

 

class ViewHolder() {
    // item을 구성하고 있는 View에 맞게 변수 생성
    
    var textName:TextView? = null
    var textOs:TextView? = null
    var textColor:TextView? = null

    // setter 생성
    fun setNoteBook(noteBook: NoteBook){
        textName?.text = noteBook.name
        textOs?.text = noteBook.os
        textColor?.text = noteBook.color
    }

}

 

 

(2) ViewHolder 사용하기

 

이렇게 생성된 ViewHolder는 

getView 메서드 내에서 사용된다.

 

이전에 View로 inflate 한 xml을

다시 inflate 하지 않기 위해
getView의 두 번째 매개변수를 활용한다.

 

두 번째 매개변수 convertView는 

이전에 inflate 한 View를 그대로 전달한다.

 

 

 

 

 

 

 

즉, convertView가 null이라면 최초 생성이라는

말이기 때문에 Inflate를 수행한다.

 

위에서 만들어 놓은 ViewHolder class를 

객체로 생성하고

이 객체에 Inflate 된 View를 매핑한다.

 

ViewHolder객체를 잘 생성했다면 

Inflate 된 View에 tag의 값으로

저장하고 반환한다.

 

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
    // return 
    val view:View
    val getData = dataList[position]

    val vh:ViewHolder

    if (convertView == null){
        // Inflating
        view = LayoutInflater.from(parent?.context).inflate(R.layout.item,parent,false)

        // 매핑해놓음
        vh = ViewHolder()
        vh.textName = view.textName
        vh.textOs = view.textOs
        vh.textColor =view.textColor

        // view에 viewholder 저장
        view.tag = vh

        // 이벤트 생성
        view?.setOnClickListener{
            Toast.makeText(context,getData.toString(),Toast.LENGTH_SHORT).show()
        }

    }else {
        view = convertView
        vh = convertView.tag as ViewHolder
    }
    //값 매칭
    vh.setNoteBook(getData)

    return view
}

이렇게 반환된 View는 화면에서 보이고

이후에 다시 getView가 실행될 때 convertView로 재사용된다.

convertView로 재사용되었다면

convertView안에 tag를 꺼내어 

ViewHolder로 활용한다.

 

ViewHolder에 Inflate 된 View가 다 매핑이 되었다면

ViewHolder의 setter를 통해

데이터를 세팅해주면 손쉽게 

값이 바인딩된다.

 

 

 

4. RecyclerView와 다른 점

RecyclerView는 ListView와 달리

앞에 설명한 내용을 

반드시 구현하도록 되어있다.

 

덕분에 자연스럽게 각 요소가 재활용될 수 있으며

추가적으로 배치 방법과 방향등을

LayoutManager를 통해 조작할 수 있어 

더 편하게 구현이 가능하다.

 

 

 

RecyclerView를 이해하기에 앞서

AdapterView를 알고 있어야 한다.

왜냐하면 나는 RecyclerView를

AdapterView의 확장판쯤으로 이해했기 때문이다.

 

 

1. AdapterView

AdapterView는 많은 데이터를

하나의 뷰로 보여주기 위해 만들어진 뷰이다.

즉, 여러 항목을 보여주는 뷰이다.

 

많은 데이터를 View로 보여주기 위해

Adapter라는 것을 사용하기 때문에

지어진 이름이지 않을까 싶다.

 

 

1) 구성요소

 

 

AdapterView에 대한 정의를 내린것에서

구성요소를 추출할 수 있다.

 

"항목을 보여준다"

 

항목을 보여준다는 표현에서 알 수 있듯

 

데이터로 구성되어 있는 하나의 요소가 존재하고

이러한 요소를 각자의 방식대로 보여주는 것이다.

 

즉, (1) 데이터를 (2)하나의 요소로 구성하고

여러 개의 요소들을 (3) View에 나열한다는 것이다.

 

 

 

 

 

 

 

위의 정의에서 볼 수 있듯 크게 3가지 구성요소로 정리할 수 있다.

 

(1) Data

뷰의 내용이 되는 정보

데이터 저장소

 

(2) Adapter

Data를 요소(Item)로 구성하여

View에서 활용 가능하도록 만드는 객체

 

(3) View

나름의 표현방식으로 Item을 보여주는 View

 

 

2) 여전히 정의되지 않은 Adapter

 

 

위의 내용처럼 정리를 해보니

Data와 View의 역할을 뚜렷한데

Adpater는 그래서 뭘 하겠다는 건지

여전히 정리가 잘 안 돼서

다시 세부적으로 정리했다.

 

Adapter는 정보를 가공하여

하나의 View(item)를 만든다.

 

Adpater가 만든 View는

(3) View의 하나의 항목이 된다.

 

 

- Adapter가 알고 있어야 하는 것

(1) Data가 무엇인지 알고 있어야 한다.

(2) 어떤 형태로 item을 구성할지 layout을 알고 있어야 한다.

(3) Data와 layout을 매핑할 수 있어야 한다.

(4) item도 화면에 표현하는 내용이기 때문에 Context를 알고 있어야 한다.

 

( 이 내용은 제가 이해하기 위해 추측을 더하여 작성한 부분이 기입니다.

실제와 다를 수 있습니다. 더 공부가 필요한 부분입니다. )

 

 

3) 예시

대표적인 AdpaterView 인 Spinner를 통해

정리한 내용이 맞는지 확인해보았다.

 

val spinner: Spinner = findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
// Spinner 생성

// Adapterter 생성 
// (1) Context 넣음
// (2) 리소스에서 R.array.planets_array 배열을 데이터로 넣음
// (3) item layout 넣음

ArrayAdapter.createFromResource(
        this,
        R.array.planets_array,
        android.R.layout.simple_spinner_item
).also { adapter ->

    // Specify the layout to use when the list of choices appears
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
    
    // Spinner에 adapter 연결
    spinner.adapter = adapter
}

출처:  공식문서 [developer.android.com/guide/topics/ui/controls/spinner?hl=ko]

 

 

 

 

 

 

ArrayAdapter의 생성 파라미터를

통해 확인할 수 있듯

Adapter는 컨텍스트가 무엇인지,

데이터가 무엇인지,

item은 어떤 레이아웃을 사용할지를 알고 있다.

ArrayAdapter는 배열 형식의 데이터를

안드로이드 기본 layout에 매칭 할 수 있다.

 

 

 

 

4) 기존 AdapterView의 한계는?

Spinner나 ListView (Legacy)와

같은 AdapterView에서는

매번 Item을 만들 때마다 Inflating을 해야 한다.

xml을 객체로 만드는 과정인데 수많은 요소들을
반복해서 View를 만드는 것은 리소스 낭비이며

부하를 일으킨다고 한다.

 

 

 

 

5) RecyclerView는 한계를 해결했는가?

화면에 보이는 View는 어차피 한정되어있다

예를 들어 500개의 항목을

보여주는 View가 있다고 하더라도

한 화면에서 볼 수 있는 항목이 10개라고 하면

Item을 10개만 생성해놓고

Item안의 데이터만 변경하는 방식으로

재사용하면 되는 것이다.

 

이러한 컨셉으로 도입된 것이 ViewHolder이고

이를 강제화한 것이 RecyclerView이다.

 

 

 

 

이어지는 글 : [Android] RecyclerView 이해하기 (2) - ViewHolder는 무엇인가

 

[Android] RecyclerView 이해하기 (2) - ViewHolder는 무엇인가

1. ViewHolder의 필요성 앞선 글에서 설명했듯 많은 데이터를 항목화하여 표현하는 AdapterView 에서는 동작할 때마다 각 항목 (xml형식의 레이아웃)을 View객체로 가져와야 한다 그리고 이렇게 생성한

kyome.tistory.com

 

 

 

 

도움이 되었다면

로그인이 필요 없는 공감 버튼 꾹 눌러주세요! 

 

 

 

 

+ Recent posts

"여기"를 클릭하면 광고 제거.