본문 바로가기
Android Studio

[Android 앱개발 숙련] RecyclerView 리사이클러 뷰 (# 추가하기)

by 젼젼39 2024. 4. 11.

1. RecyclerView

1) 개념

    - 안드로이드 앱에서 리스트 형태의 데이터를 표시하는 데 사용되는 위젯 (AdapterView를 표현하는 여러 방법 중 하나)
    - 여러 아이템을 스크롤 가능한 리스트로 표현, 많은 아이템을 효율적으로 관리하고 보여줌 (세로, 가로 등)

    - 한정적인 화면에 많은 데이터를 넣을 수 있는 view (view를 재활용해서 사용하겠다는 것)

 

2) ListView 와 RecyclerView

ListView RecyclerView
- 사용자가 스크롤할 때 마다 위에 있던 아이템은 삭제되고, 맨 아래의 아이템은 생성되길 반복 - 사용자가 스크롤할 때 위에 있던 아이템이 재활용되어 아래로 이동, 재사용됨
- 삭제와 생성을 반복해 성능에 좋지 않음 - 10개정도의 view만 만들고 10개를 재활용해 사용함

 

3) Adapter

    - 데이터 테이블을 목록 형태로 보여주기 위해 사용됨
    - 데이터를 다양한 형식의 리스트 형식으로 보여주기 위해 데이터와 RecyclerView 사이에 존재하는 객체

    = 데이터와 RecyclerView 사이의 통신을 위한 연결체

    //콘센트 멀티어댑터 st... 인풋은 동일하지만 아웃풋의 형태가 달라지는 것.

 

4) ViewHolder

    - 화면에 표시될 데이터나 아이템들을 저장하는 역할

    - (스크롤해서 위로 올라간 View를 재활용하기 위해 View를 기억해두는 역할)

 

2. 예제

1) 메인화면 레이아웃(ex. activity_main.xml)에 ListView 위젯을 RecyclerView 위젯으로 변경

        - activity_main.xml에는 LinearLayout 안에 RecyclerView라는 위젯 하나만 들어가 있음.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

 

2) 어댑터 클래스 정의

    - 앞서 정의한 MyItem 타입의 객체들을 Arrayist로 관리하는 MyAdapter 클래스를 Recyclerview.Adapter를 파생해 정의

//MyItem.kt

data class MyItem (val aIcon : Int, val aName : String, val aAge : String){}
//MyAdapter.kt

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.customitemview.databinding.ItemRecyclerviewBinding

//RecyclerView의 Adapter를 상속받을것임.
//인자로 MyItem들의 List를 넣어줌(샘플데이터의 리스트들)
class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {

    // 클릭이벤트를 MainActivity 에 넘기는 ...
    //ItemClick이라는 인터페이스 만들기
    // 실제로 함수는 onClick, 매개변수로 view랑 position이 들어가게...
    // 그리고 메인액티비티로 이동
    interface ItemClick {
        fun onClick(view : View, position : Int)
    }

    var itemClick : ItemClick? = null

    //어댑터가 만들어지면서 viewHolder도 만들어짐
    // 홀더는 이런 식으로 만들거야 st... 홀더가 어떤 식으로 되어있는지는 아래 이너클래스에.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        //Holder 만들어주기!! 
        // 바인딩하고 inflate해서...
        val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        // Holder에 집어넣어주기. Hodlder를 생성하는 것!
        return Holder(binding)
    }

    //onBindViewHolder가 8번 돌면서 mItems에 있던 8개의 샘플데이터가 하나하나 실행됨, 실행 결과에 보여줌
    // 화면에 보이는 부분까지만 실행됨! 나머지는 스크롤해서 보여질 때 보이게 됨
    
    // holder: Holder 안에 차례대로 들어오는 것.
    // position: Int 는 0번째부터 들어오게 됨
    override fun onBindViewHolder(holder: Holder, position: Int) {
        
        //클릭이벤트 추가하는 부분
        // 1. 이 안에서 인텐트로 다른 액티비티를 호출하는 방법
        // 2. 이 MyAdapter에서 클릭 이벤트를 받고, 이 이벤트 받은 것을 메인액티비티로 넘겨서
        //        실제로 클릭에 대한 이벤트 처리를 메인에서 하게 함
        //    그러려면 MyAdapter과 MainActivity 사이에 통신할 수 있는 인터페이스가 필요함
        //    -> 윗부분에 interface ItemClick 부분 보기
        
        holder.itemView.setOnClickListener {
            itemClick?.onClick(it, position)
        }
        //이미지 넣어주고 텍스트 넣어주기...
        holder.iconImageView.setImageResource(mItems[position].aIcon)
        holder.name.text = mItems[position].aName
        holder.age.text = mItems[position].aAge
    }

    //오버라이드
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }
    //오버라이드
    override fun getItemCount(): Int {
        return mItems.size
    }

    //Holder는 이너클래스로 만들어짐...
    // ItemRecyclerviewBinding 은 item_recyclerview.xml 에 연결됨
    inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
        //그 안에 있던 3가지들... 재활용할것임
        val iconImageView = binding.iconItem
        val name = binding.textItem1
        val age = binding.textItem2
    }
}

   - 위에 쓰인 item_recyclerview.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"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iconItem"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:padding="8dp"
        android:scaleType="centerCrop"
        android:src="@drawable/sample_0"/>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2">
        <TextView
            android:id="@+id/textItem1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="20dp"
            android:padding="4dp"
            android:hint="Name"/>
        <TextView
            android:id="@+id/textItem2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="16dp"
            android:padding="4dp"
            android:hint="Age"/>
        
    </LinearLayout>
    
</LinearLayout>
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 데이터 원본 준비
        // 보통 데이터는 데이터클래스로 만들어짐.
        // 정보들을 담은 하나의 객체로 만들어 MyItem 클래스의 리스트 형태로 만들기
        val dataList = mutableListOf<MyItem>()
        //여긴 샘플데이터지만 실제에서는 데이터를 외부에서 받아올것임
        dataList.add(MyItem(R.drawable.sample_0, "Bella", "1"))
        dataList.add(MyItem(R.drawable.sample_1, "Charlie", "2"))
        dataList.add(MyItem(R.drawable.sample_2, "Daisy", "1.5"))
        dataList.add(MyItem(R.drawable.sample_3, "Duke", "1"))
        dataList.add(MyItem(R.drawable.sample_4, "Max", "2"))
        dataList.add(MyItem(R.drawable.sample_5, "Happy", "4"))
        dataList.add(MyItem(R.drawable.sample_6, "Luna", "3"))
        dataList.add(MyItem(R.drawable.sample_7, "Bob", "2"))

        //아래 코드는 중복이라 뺌
        //binding.recyclerView.adapter = MyAdapter(dataList)

        //recyclerView를 사용하기 위해서는 adapter랑 viewHolder가 필요...
        //adapter 클래스를 만들기 위해 MyAdapter 클래스 만들어줌


        //어댑터를 하나 만들어서 그 안에 데이터를 집어넣음
        val adapter = MyAdapter(dataList)
        //어댑터에 집어넣어줌
        binding.recyclerView.adapter = adapter
        //레이아웃을 어떻게 구성할건지를...
        binding.recyclerView.layoutManager = LinearLayoutManager(this)

        // MyAdapter에서 받아서...
        //어댑터의 itemClick(MyAdapter에서 ItemClick? 인터페이스 타입의 itemClick)에
        //  MyAdapter.ItemClick 타입의 object를 걸어주는 것
        // MyAdapter에서 onBindViewHolder에서 setOnClickListener 밑의 itemClick?.onClick(it,position)이 발생되면
        //  it은 view(item_recyclerview.xml의 전체. 통.)가 되고,
        //   그게 클릭되면 그것의 view와 position을 onClick의 매개변수로 넣어서 itemClick을 호출시킴.
        //    콜백은 메인의 override fun onClick 부분으로 들어옴

        adapter.itemClick = object : MyAdapter.ItemClick {
            override fun onClick(view: View, position: Int) {
                //dataList의 포지션의 aName을 가져옴
                val name: String = dataList[position].aName
                Toast.makeText(this@MainActivity," $name 선택!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

 

 

// 주석부분 다시 읽어보기