반응형

 

2. 메인 화면 - 리사이클러뷰를 통해 대량의 데이터를 보여주는 화면 MainActivity

대부분의 앱에서 대량의 데이터를 보여주는 용도로 많이 구현하는 RecyclerView 를 코틀린언어를 사용하여 만들어 봄으로서 앞으로의 안드로이드 앱 개발은  코틀린을 이용한 앱개발로 자연스럽게 전환하겠습니다.

 

 

 리사이클러뷰를 사용하여 대량의 데이터를 보여주는 앱들의 UI [ 크몽, 여기어때, 오늘의 집 ]

리사이클러뷰를 사용하는 앱들의 UI

 

 

이번 글은 리사이클러뷰에 대한 학습이라기 보다 코틀린으로 구현하는 리사이클러뷰에 대한 내용이기에 위의 앱들과 같은 모양의 UI 구현은 너무 오래걸릴 듯 화여 최대한 간결하게 Image 1개, Text 2개를 가진 아이템뷰만 간단하게 만들어보겠습니다.

 

리사이클러뷰를 이용한 대량의 데이터를 보여주는 화면을 만들려면 여러 단계와 파일들을 만들어야 합니다. 그래서 초보자들의 경우에는 관려예제를 보고 따라 만드는 것 조차 쉽게 수행하지 못하는 경우가 많습니다. 그래서 필자의 경우에는 리사이클러뷰를 구현할 때 나름의 순서를 기준으로 하여 구현하는 편입니다. 정답은 아니지만 제가 선호하는 순서를 소개하고 그대로 만들어 보겠습니다. 혹시 리사이클러뷰 구현에 어려움을 겪고 있으시다면 이 방법을 써보시길 권해드립니다.

리사클러뷰 구현 단계
1) 액티비티 레이아웃 xml 파일에 RecyclerView 추가 및 액티비티에서 참조변수 선언
2) 대량의 데이터 준비( List , Data class )
3) 아이템뷰 1개의 레이아웃 xml 파일 작성
4) 대량의 데이터(List) 개수만큼 아이템뷰(아이템뷰 1개의 레이아웃 모양) 객체를 생성하는 아답터 클래스 설계
5) 아답터 객체 생성 및 리사이클러뷰에 아답터로 설정하여 완성

 

 

1) RecyclerView 추가 및 참조변수 만들기

 

레이아웃 파일은 간단하게 리사이클러뷰 1개만 배치하도록 하겠습니다.

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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

기본으로 만들어져 있는 MainActivity 클래스 파일을 보겠습니다. 역시 컴포즈 개발방법의 등장과 함께 도입된 화면 전체를 사용하는 Edge To Edge 와 Navigation Bar 사이즈를 얻어와서 패딩을 주는 기능이 작성되어 있습니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()    //화면 전체를 사용하는 기능
        setContentView(R.layout.activity_main)
        
        //Navigation Bar 의 사이즈만큼 액티비티 패딩 설정.
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }
}

 

MainActivity 역시 이전 IntroActivity 와 마찬가지로 리사이클러뷰의 사용에만 집중하기 위해 불필요한 EdgeToEdge 는 제거하고 시작하겠습니다. 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)        
        setContentView(R.layout.activity_main) //화면에 보여줄 뷰로 레이아웃 xml 파일 설정
        
    }//onCreate method...
}//MainActivity class.....

 

이제 레이아웃 xml 파일에서 추가한 리사이클러뷰를 제어하기 위한 참조변수를 만들겠습니다. 이전에는 var 키워드를 사용해보았습니다. 

하지만 뷰 참조변수들은 한번 뷰객체를 참조하면 다른 뷰객체로 바꾸어 참조하는 경우가 거의 없기에 var보다 val을 사용해도 됩니다. 저는 이런 이유로 값변경이 불가능한 val 변수로 만드는 것을 선호합니다.
val 변수도 역시 초기화를 하지 않으면 에러입니다. 하지만 RecyclerView의 객체 참조값을 지금 초기화 할 수는 없습니다. 이전에서 소개했듯이 onCreate() 에서 findViewById()를 해야 하기에 나중에 해야 합니다. 그렇다고 nullable 변수로 만들면 null 값을 다른 참조값을 바꿀 수 없기에 사용할 수 없습니다. 그래서 늦은초기화 문법을 사용하겠습니다.

단, lateinit 은 var에만 사용할 수 있기에 by lazy 지연 초기화 문법으로 초기화를 진행하겠습니다. lazy 중괄호 { } 안의 작성 내용을 참조변수가 처음 사용될 때까지 좀 게으르게(lazy) 지연하여 초기화를 수행하기에 문제없이 RecyclerView 객체의 생성 이후 참조값을 얻어올 수 있습니다.

class MainActivity : AppCompatActivity() {

    // 뷰 참조변수들은 한번 뷰객체를 참조하면 다른 뷰객체로 바꾸어 참조하는 경우가 거의 없기에 var보다 val을 선호함
    // 이때, 초기화 안하면 에러 - 하지만 recycler의 객체 참조값을 지금 초기화 할 수 없음 [ onCreate() 에서 findViewById()를 해야 하기에..]
    // 그래서 늦은초기화 사용[ lateinit 은 var에만 사용할 수 있기에 by lazy 지연초기화 사용]
    val recycler: RecyclerView by lazy { findViewById(R.id.recycler) }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }//onCreate method...
}//MainActivity class.....

 

보다시피 한 줄에 참조변수 선언과 findViewById()를 작성할 수 있어서 lateinit var 늦은 초기화 보다 코드의 가독성이 좋아서 저는 더 선호하는 편입니다. 개발자 마다 뷰의 초기화 참조변수를 만드는 방법은 약간씩 차이가 있으니 어떤 것이 정답이라는 생각보다는 본인이 편하고 좋다고 생각하시는 방법으로 뷰 참조변수를 만드시길 바랍니다.


 

 

앞서 언급했듯이 가급적 간결하게 아이템뷰를 구성하고자 합니다. 여행지 정보를 보여주는 앱이라고 가정하고 아래 모양처럼 구성하고자 합니다. 아이템뷰 당 이미지 1개, 제목글씨 1개, 메세지 글씨 1개로 구성하고자 합니다.

 

 

2) 리사이클러뷰가 보여줄 대량의 데이터 준비

 

※ 여행지 이미지 파일 3개 : newyork.jpg, paris.jpg, sydney.jpg     

newyork.jpg
0.23MB
paris.jpg
0.19MB
sydney.jpg
0.10MB

 

실제 앱들의 이미지는 서버에 있는 이미지를 불어와서 보여주기에 프로젝트 폴더 안에 이미지파일이 존재하지 않지만 실습의 편의를 위해 위 3개의 사진파일을 res 폴더 > drawable 폴더안에 넣어놓고 리소스 ID 로 불러와서 보여주도록 하겠습니다.

 

 

다음으로 아이템뷰가 보여줄 아이템 1개( 이미지, 제목, 메세지 ) 데이터를 저장하기 위한 데이터 클래스를 하나 만들겠습니다. 클래스명은 단순하게 Item 이라고 명명하겠습니다. 

 

코틀린은 클래스 중에서 data class 라는 것이 있습니다. class 앞에 data 키워드를 추가하면 데이터 클래스로 만들어집니다. 이름 그대로 데이터를 저장하는 용도의 클래스를 만들 때 사용합니다. 

데이터 클래스 : 데이터만 저장하는 목적은 클래스
- 일반 class와 다르게 자동으로 equals()할때 객체 참조주소의 비교가 아니라 주생성자의 멤버변수를 비교해주는 특별한 클래스 
- 별도의 기능 메소드를 가지고 있지 않는 클래스 이기에 클래스의 설계를 위한 중괄호{} 영역을 작성하지 않음.

 

Android Studio data class 만들기

//데이터를 저장하는 용도의 클래스 ( 제목, 메세지, 이미지 파일 리소스 경로)
data class Item constructor(var title:String, var msg:String, var img:Int) //- 특별한 기능메소드를 작성할 필요없기에 보통은 {}도 생략함.

 

대량의 데이터는 보통 서버나 DB에서 불어와야 하지만 지금은 리사이클러뷰 구현에 집중하기 위해 테스트용으로 더미 데이터를 직접 작성하여 추가하도록 하겠습니다. 

리사이클러뷰를 보여주는 MainActivity에서 Item 데이터 객체를 여러개 보관할 리스트를 멤버변수로 만들고 더미데이터를 추가하도록 하겠습니다.

class MainActivity : AppCompatActivity() {

    // 리사이클러뷰 참조변수 - 늦은 초기화
    val recycler: RecyclerView by lazy { findViewById(R.id.recycler) }

    //대량의 데이터 property[속성:멤버변수]
    var items= mutableListOf<Item>() //Java의 ArrayList<Item>와 비슷하게 동작
    //var items= arrayListOf<Item>()  //이렇게 ArrayList 객체를 만들어도 됨

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

        //대량의 데이터들 추가 [테스트목적]
        items.add( Item("NEW YORK","Hello. Kotlin", R.drawable.newyork) )
        items.add( Item("PARIS","Nice to meet you", R.drawable.paris) )
        items.add( Item("SYDNEY","Have a good day", R.drawable.sydney) )
        items.add( Item("뉴욕","Do you have fun", R.drawable.newyork) )
        items.add( Item("파리","Nice to meet you", R.drawable.paris) )
        items.add( Item("시드니","Have a good day", R.drawable.sydney) )
        items.add( Item("new york","Do you have fun", R.drawable.newyork) )
        items.add( Item("paris","Nice to meet you", R.drawable.paris) )
        items.add( Item("sydney","Have a good day", R.drawable.sydney) )
        
    }
    
}

 

 

3) Item 데이터를 보여줄 아이템뷰의 1개의 모양 디자인을 위한 레이아웃 파일 만들기

 

Item 객체 1개의 데이터( 제목, 메세지, 이미지)를 보여줄 아이템뷰 1개의 모양 디자인을 위한 레이아웃 xml 파일을 만들어 보겠습니다.

 

[ 디자인 스케치 - 도형 ]             |             [ 구현된 아이템뷰 1개의 실제 모양 스크린샷 ]

 

recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="160dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp"
    android:layout_margin="12dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/newyork"
            android:scaleType="centerCrop"/>
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TITLE"
            android:textStyle="bold"
            android:textSize="40sp"
            android:textColor="@color/white"
            android:layout_alignParentRight="true"
            android:layout_margin="16dp"/>
        <TextView
            android:id="@+id/tv_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is message"
            android:textColor="@color/white"
            android:textStyle="bold"
            android:textSize="18sp"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"
            android:layout_margin="16dp"/>

    </RelativeLayout>

</com.google.android.material.card.MaterialCardView>

 

 

4) 리사이클러뷰가 보여줄 아이템 뷰를 리스트 개수만큼 객체로 만들어서 제공하는 아답터 객체 생성

 

이제 테스트 목적으로 직접 추가한 9개의 Item 데이터를 사용자가 볼 수 있는 아이템뷰 객체로 만들어주는 아답터 클래스를 설계해 보겠습니다. 아이템 1개의 모양을 설계한 recycler_item.xml 파일의 레이아웃 모양으로 아이템 뷰를 리스트 개수만큼 만들어서 리사이클러뷰에 제공하도록 구현하겠습니다.

리사이클러뷰를 구현할 때 가장 핵심적인 역할을 수행하는 클래스로서 RecyclerView.Adapter 클래스를 상속받아 구현합니다. 또한 상속을 받을 때 아이템뷰의 자식뷰들의 참조값을 저장하는 ViewHolder 클래스를 <>제네릭 타입으로 지정하여 만듭니다.

 

아탑터 클래스의 이름은 내가 만든 아답터라는 의미로 MyAdapter 로 명명해 보겠습니다.

 

MyAdapter 클래스를 만들때 2개의 프로퍼티(멤버변수)를 만들겠습니다.

- context : 아답터 클래스 안에서 운영체제의 기능들을 사용해야 하는 경우가 많기에 보통 Context 를 멤버변수(프로퍼티)로 준비함.

- items : 대량의 데이터를 아이템뷰객체로 만들어 주기에 대량의 데이터인 items:MutableList 멤버변수(프로퍼티)로 준비함.

 

코틀린은 멤버변수를 만들고 생성할 때 초기화를 시키고자 한다면 주 생성자(Primary Constructor)를 사용할 것을 권장합니다. 주 생성자의 파라미터를 만들때 var or val 키워드를 추가하면 멤버변수면서 매개변수가 되기에 코드가 훨씬 간결해 집니다.

MyAdapter.kt
import android.content.Context

//주 생성자 - 클래스명 옆에 constructor 키워드로 추가. 파라미터에 var or val 키워드를 추가하면 멤버변수 면서 매개변수가 됨.
class MyAdapter constructor(val context: Context, val items:MutableList<Item>){
}

 

이제 RecyclerView를 보여주는 MainActivity 에서 MyAdapter 객체를 생성할 때 context와 대량의 Item 데이터인 items를 생성자 파라미터에 전달해 주면 됩니다.

 

앞에서 소개했듯이 아답터는 아이템뷰에 데이터인 Item의 값들을 설정해 주어야 하기에 아이템뷰의 자식뷰들을 참조하여 저장하는 뷰홀더클래스를 이너클래스로 만들어 사용합니다. 코틀린은 자바와 다르게 이너클래스를 만들 때 inner class 로 만들어야 온전하게 이너클래스가 됩니다. 또한 이 뷰홀더 클래스는 RecyclerView.ViewHolder 를 상속하여 만들어야 합니다. 

그럼. MyAdater 클래스의 이너 클래스로 뷰홀더 클래스를 만들어 보겠습니다. 이름은 뷰홀더라는 의미로 VH 라고 명명하겠습니다.

import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

//주 생성자 - 클래스명 옆에 constructor 키워드로 추가. 파라미터에 var or val 키워드를 추가하면 멤버변수 면서 매개변수가 됨.
class MyAdapter constructor(val context: Context, val items:MutableList<Item>){

    //아이템뷰 1개의 자식뷰들을 참조하는 참조변수를 저장하는 뷰홀더 클래스 (이너클래스) - 아이템뷰를 생성자 파라미터로 받아서 사용
    inner class VH constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
        // 아이템뷰의 자식뷰( 제목을 보여주는 텍스트뷰, 메세지를 보여주는 텍스트뷰, 이미지를 보여주는 이미지뷰 )
        // by lazy : 지연초기화를 이용하여 자식뷰들 참조
        val tvTitle: TextView by lazy { itemView.findViewById(R.id.tv_title) }
        val tvMsg: TextView by lazy { itemView.findViewById(R.id.tv_msg) }
        val iv: ImageView by lazy { itemView.findViewById(R.id.iv) }
    }
    
}

 

이제 리사이클러뷰의 아답터로서 반드시 구현해야 할 메소드 3개를 만들기 위해 MyAdapter에 RecyclerView.Adapter 를 상속해 주도록 하겠습니다. 이때 이너클래스로 설계한 VH 클래스를 제네릭으로 지정해 주면 뷰홀더 클래스로 활용할 수 있게 됩니다.

RecyclerView.Adapter 클래스는 3개의 추상메소드를 가지고 있어서 반드시 구현해야 합니다. 이를 통해 개발자가 아답터의 필수 구현기능 3개를 실수없이 제작할 수 있도록 강제하게 됩니다.

import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

//주 생성자 - 클래스명 옆에 constructor 키워드로 추가. 파라미터에 var or val 키워드를 추가하면 멤버변수 면서 매개변수가 됨. 
//## RecyclerView.Adapter 상속 ##
class MyAdapter constructor(val context: Context, val items:MutableList<Item>) : RecyclerView.Adapter<MyAdapter.VH>(){

    //아이템뷰 1개의 자식뷰들을 참조하는 참조변수를 저장하는 뷰홀더 클래스 (이너클래스) - 아이템뷰를 생성자 파라미터로 받아서 사용
    inner class VH constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
        // 아이템뷰의 자식뷰( 제목을 보여주는 텍스트뷰, 메세지를 보여주는 텍스트뷰, 이미지를 보여주는 이미지뷰 )
        // by lazy : 지연초기화를 이용하여 자식뷰들 참조
        val tvTitle: TextView by lazy { itemView.findViewById(R.id.tv_title) }
        val tvMsg: TextView by lazy { itemView.findViewById(R.id.tv_msg) }
        val iv: ImageView by lazy { itemView.findViewById(R.id.iv) }
    }

    //반드시 구현해야 할 추상 메소드 3개. ------------------------------------------------------------------

    //1] 아이템뷰 생성 기능 : recycler_item.xml 레이아웃 모양의 아이템뷰 객체를 생성하여 뷰홀더에 전달하여 리턴하는 메소드
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        TODO("Not yet implemented")
    }

    //2] 아답터가 만들 아이템뷰의 총 개수를 리턴하는 기능 : 대량의 데이터인 items 의 개수.
    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    //3] 아이템뷰에 Item 데이터 연결해주는 기능 : 현재 만들어야 할 position 번째 Item의 값을 뷰홀더의 멤버인 자식뷰들에 설정
    override fun onBindViewHolder(holder: VH, position: Int) {
        TODO("Not yet implemented")
    }

}

 

이제 필수 기능 메소드 3개의 코드를 차례로 작성해 보겠습니다. 이미 자바언어를 통해 리사이클러뷰의 아답터를 제작해 보았다는 것을 전제로 소개하고 있기에 코드에 대한 소개는 간략하게 주석으로 소개하겠습니다.

 

1] 아이템뷰 생성 기능 : recycler_item.xml 레이아웃 모양의 아이템뷰 객체를 생성하여 뷰홀더에 전달하여 리턴하는 메소드

//1] 아이템뷰 생성 기능 : recycler_item.xml 레이아웃 모양의 아이템뷰 객체를 생성하여 뷰홀더에 전달하여 리턴하는 메소드
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
    val inflater= LayoutInflater.from(context)  //xml 레이아웃 파일 모양으로 뷰 객체를 생성해 주는 객체 소환
    val itemView= inflater.inflate(R.layout.recycler_item, parent, false) //아이템뷰 객체 생성
    val holder= VH(itemView) //아이템뷰의 자식뷰들을 참조해주는 뷰홀더 객체 생성
    return holder //뷰홀더 객체 리턴
}

 

2] 아답터가 만들 아이템뷰의 총 개수를 리턴하는 기능 : 대량의 데이터인 items 의 개수

//2] 아답터가 만들 아이템뷰의 총 개수를 리턴하는 기능 : 대량의 데이터인 items 의 개수.
override fun getItemCount(): Int {
    return items.size  //멤버변수인 대량의 Item 리스트의 총 개수 리턴.
}

 

3] 아이템뷰에 Item 데이터 연결해주는 기능 : 현재 만들어야 할 position 번째 Item의 값을 뷰홀더의 멤버인 자식뷰들에 설정

//3] 아이템뷰에 Item 데이터 연결해주는 기능 : 현재 만들어야 할 position 번째 Item의 값을 뷰홀더의 멤버인 자식뷰들에 설정
override fun onBindViewHolder(holder: VH, position: Int) {
    
    val item= items.get(position)  //현재 아이템뷰에 보여줄 Item 데이터 얻어오기

    holder.tvTitle.setText(item.title)  // 현재 아이템뷰의 제목을 보여주는 텍스트뷰에 [제목] 설정
    holder.tvMsg.text= item.msg         // 현재 아이템뷰의 메세지를 보여주는 텍스트뷰에 [메세지] 설정

    holder.iv.setImageResource(item.img) // 현재 아이템뷰의 이미지를 보여주는 이미지뷰에 [도시 이미지] 설정
 
}

 

* 참고로 이미지뷰에 이미지를 설정할 때는 기본 기능인 setImageResource()보다는 이미지로딩 용 외부 라이브러리 사용을 권해 드립니다. 기본 기능과 다르게 이미지의 용량이 아주 클때 자동으로 scale 을 해주며, gif 와 같은 동적 이미지와 네트워크 이미지 로딩도 지원해 줍니다. 그래서 위 코드에서 마지막 holder.iv 에 이미지를 불러와 보여주는 코드는 Glide 라는 이미지로딩 외부 라이브러리를 사용하도록 수정해 보겠습니다. Glide 라이브러리 추가는 스스로 하실 수 있다고 보고 설명을 생략하도록 하겠습니다.

 

build.gradle.kts(Module : app)

 

Glide 를 추가했으니 이미지 설정 코드를 수정해 보겠습니다.

//3] 아이템뷰에 Item 데이터 연결해주는 기능 : 현재 만들어야 할 position 번째 Item의 값을 뷰홀더의 멤버인 자식뷰들에 설정
override fun onBindViewHolder(holder: VH, position: Int) {

    val item= items.get(position)  //현재 아이템뷰에 보여줄 Item 데이터 얻어오기

    holder.tvTitle.setText(item.title)  // 현재 아이템뷰의 제목을 보여주는 텍스트뷰에 [제목] 설정
    holder.tvMsg.text= item.msg         // 현재 아이템뷰의 메세지를 보여주는 텍스트뷰에 [메세지] 설정

    //holder.iv.setImageResource(item.img) // 현재 아이템뷰의 이미지를 보여주는 이미지뷰에 [도시 이미지] 설정
    // 이미지 로딩 라이브러리 Glide 로 이미지 설정
    Glide.with(context).load(item.img).into(holder.iv)
}

 

완성된 MyAdapter 클래스의 전체 코드 입니다.

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

//주 생성자 - 클래스명 옆에 constructor 키워드로 추가. 파라미터에 var or val 키워드를 추가하면 멤버변수 면서 매개변수가 됨.
//## RecyclerView.Adapter 상속 ##
class MyAdapter constructor(val context: Context, val items:MutableList<Item>) : RecyclerView.Adapter<MyAdapter.VH>(){

    //아이템뷰 1개의 자식뷰들을 참조하는 참조변수를 저장하는 뷰홀더 클래스 (이너클래스) - 아이템뷰를 생성자 파라미터로 받아서 사용
    inner class VH constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
        // 아이템뷰의 자식뷰( 제목을 보여주는 텍스트뷰, 메세지를 보여주는 텍스트뷰, 이미지를 보여주는 이미지뷰 )
        // by lazy : 지연초기화를 이용하여 자식뷰들 참조
        val tvTitle: TextView by lazy { itemView.findViewById(R.id.tv_title) }
        val tvMsg: TextView by lazy { itemView.findViewById(R.id.tv_msg) }
        val iv: ImageView by lazy { itemView.findViewById(R.id.iv) }
    }

    //반드시 구현해야 할 추상 메소드 3개. ------------------------------------------------------------------

    //1] 아이템뷰 생성 기능 : recycler_item.xml 레이아웃 모양의 아이템뷰 객체를 생성하여 뷰홀더에 전달하여 리턴하는 메소드
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val inflater= LayoutInflater.from(context)  //xml 레이아웃 파일 모양으로 뷰 객체를 생성해 주는 객체 소환
        val itemView= inflater.inflate(R.layout.recycler_item, parent, false) //아이템뷰 객체 생성
        val holder= VH(itemView) //아이템뷰의 자식뷰들을 참조해주는 뷰홀더 객체 생성
        return holder //뷰홀더 객체 리턴
    }

    //2] 아답터가 만들 아이템뷰의 총 개수를 리턴하는 기능 : 대량의 데이터인 items 의 개수.
    override fun getItemCount(): Int {
        return items.size  //멤버변수인 대량의 Item 리스트의 총 개수 리턴.
    }

    //3] 아이템뷰에 Item 데이터 연결해주는 기능 : 현재 만들어야 할 position 번째 Item의 값을 뷰홀더의 멤버인 자식뷰들에 설정
    override fun onBindViewHolder(holder: VH, position: Int) {

        val item= items.get(position)  //현재 아이템뷰에 보여줄 Item 데이터 얻어오기

        holder.tvTitle.setText(item.title)  // 현재 아이템뷰의 제목을 보여주는 텍스트뷰에 [제목] 설정
        holder.tvMsg.text= item.msg         // 현재 아이템뷰의 메세지를 보여주는 텍스트뷰에 [메세지] 설정

        //holder.iv.setImageResource(item.img) // 현재 아이템뷰의 이미지를 보여주는 이미지뷰에 [도시 이미지] 설정
        // 이미지 로딩 라이브러리 Glide 로 이미지 설정
        Glide.with(context).load(item.img).into(holder.iv)
    }

}

 

 

5) 아답터 객체 생성 및 리사이클러뷰에 아답터로 설정하여 완성하기

 

이제 리사이클러뷰를 보여주는 MainActivity 에서 MyAdapter 클래스를 객체로 생성하여 리사이클러뷰에 설정해 주겠습니다.

class MainActivity : AppCompatActivity() {

    // 뷰 참조변수들은 한번 뷰객체를 참조하면 다른 뷰객체로 바꾸어 참조하는 경우가 거의 없기에 var보다 val을 선호함
    // 이때, 초기화 안하면 에러 - 하지만 recycler의 객체 참조값을 지금 초기화 할 수 없음 [ onCreate() 에서 findViewById()를 해야 하기에..]
    // 그래서 늦은초기화 사용[ lateinit 은 var에만 사용할 수 있기에 by lazy 지연초기화 사용]
    val recycler: RecyclerView by lazy { findViewById(R.id.recycler) }

    //대량의 데이터 property[속성:멤버변수]
    var items= mutableListOf<Item>() //Java의 ArrayList<Item>와 비슷하게 동작
    //var items= arrayListOf<Item>()  //이렇게 ArrayList 객체를 만들어도 됨

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

        //대량의 데이터들 추가 [테스트목적]
        items.add( Item("NEW YORK","Hello. Kotlin", R.drawable.newyork) )
        items.add( Item("PARIS","Nice to meet you", R.drawable.paris) )
        items.add( Item("SYDNEY","Have a good day", R.drawable.sydney) )
        items.add( Item("뉴욕","Do you have fun", R.drawable.newyork) )
        items.add( Item("파리","Nice to meet you", R.drawable.paris) )
        items.add( Item("시드니","Have a good day", R.drawable.sydney) )
        items.add( Item("new york","Do you have fun", R.drawable.newyork) )
        items.add( Item("paris","Nice to meet you", R.drawable.paris) )
        items.add( Item("sydney","Have a good day", R.drawable.sydney) )

        // ----------------------------------------------------------------------------
        
        //리사이클러뷰안에 이미 아답터 프로퍼티(멤버변수)가 있어서 아답터객체를 대입해 주면 됨
        recycler.adapter= MyAdapter(this, items)

    }//onCreate method...
}//MainActivity class.....

 

마지막으로, 리사이클러뷰가 아이템뷰를 세로 목록형으로 나열되도록 레이아웃 매니저를 LinearLayoutManager 로 설정해 주겠습니다.

class MainActivity : AppCompatActivity() {

    // 뷰 참조변수들은 한번 뷰객체를 참조하면 다른 뷰객체로 바꾸어 참조하는 경우가 거의 없기에 var보다 val을 선호함
    // 이때, 초기화 안하면 에러 - 하지만 recycler의 객체 참조값을 지금 초기화 할 수 없음 [ onCreate() 에서 findViewById()를 해야 하기에..]
    // 그래서 늦은초기화 사용[ lateinit 은 var에만 사용할 수 있기에 by lazy 지연초기화 사용]
    val recycler: RecyclerView by lazy { findViewById(R.id.recycler) }

    //대량의 데이터 property[속성:멤버변수]
    var items= mutableListOf<Item>() //Java의 ArrayList<Item>와 비슷하게 동작
    //var items= arrayListOf<Item>()  //이렇게 ArrayList 객체를 만들어도 됨

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

        //대량의 데이터들 추가 [테스트목적]
        items.add( Item("NEW YORK","Hello. Kotlin", R.drawable.newyork) )
        items.add( Item("PARIS","Nice to meet you", R.drawable.paris) )
        items.add( Item("SYDNEY","Have a good day", R.drawable.sydney) )
        items.add( Item("뉴욕","Do you have fun", R.drawable.newyork) )
        items.add( Item("파리","Nice to meet you", R.drawable.paris) )
        items.add( Item("시드니","Have a good day", R.drawable.sydney) )
        items.add( Item("new york","Do you have fun", R.drawable.newyork) )
        items.add( Item("paris","Nice to meet you", R.drawable.paris) )
        items.add( Item("sydney","Have a good day", R.drawable.sydney) )

        // ----------------------------------------------------------------------------

        //리사이클러뷰안에 이미 아답터 프로퍼티(멤버변수)가 있어서 아답터객체를 대입해 주면 됨
        recycler.adapter= MyAdapter(this, items)

        //리사이클러뷰에 레이아웃매니저 붙이기
        recycler.layoutManager= LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

    }//onCreate method...
}//MainActivity class.....

 


 

이렇게 해서 리사이클러뷰를 보여주는 코드를 코틀린으로 구현해보는 작업을 완료했습니다.

잘 구현되었는지 확인해 볼까요?

 

 

 

이번 글은 좀 길었네요.

다음글에서 마지막으로 아이템뷰를 클릭하였을 때 Item 상세 화면 액티비티로 전환하도록 만들겠습니다.

반응형

+ Recent posts