[Android ๊ธฐ์ดˆ] 12. ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ (RecyclerView)
๋ฐ˜์‘ํ˜•

 

 

 

 

 

1. RecyclerView

์ถœ์ฒ˜: https://blog.hexabrain.net/363

  • ์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ทฐ
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์—์„œ ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์œ„์ ฏ
  • ์—ฌ๋Ÿฌ ์•„์ดํ…œ์„ ์Šคํฌ๋กค ๊ฐ€๋Šฅํ•œ ๋ฆฌ์ŠคํŠธ๋กœ ํ‘œํ˜„ํ•˜๋ฉฐ, ๋งŽ์€ ์•„์ดํ…œ์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• 
  • ํ•œ์ •์ ์ธ ํ™”๋ฉด์— ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” View
  • Recycle์„ ํ•œ๊ตญ์–ด๋กœ ํ•˜๋ฉด ์žฌํ™œ์šฉํ•˜๋‹ค๋ผ๋Š” ๋œป → View๋ฅผ ์žฌํ™œ์šฉํ•ด์„œ ์‚ฌ์šฉ

 

 

 

02. ListView ์™€ RecyclerView์˜ ์ฐจ์ด์ 

 

์™ผ์ชฝ ๋ฆฌ์ŠคํŠธ๋ทฐ, ์˜ค๋ฅธ์ชฝ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ

2-1. ListView

  • ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค ํ•  ๋•Œ๋งˆ๋‹ค ์œ„์— ์žˆ๋˜ ์•„์ดํ…œ์€ ์‚ญ์ œ๋˜๊ณ , ๋งจ ์•„๋ž˜์˜ ์•„์ดํ…œ์€ ์ƒ์„ฑ ๋˜๊ธธ ๋ฐ˜๋ณต
  • ์•„์ดํ…œ์ด 100๊ฐœ๋ฉด 100๋ฒˆ ์‚ญ์ œ ๋ฐ ์ƒ์„ฑ
  • ๊ณ„์† ์‚ญ์ œ์™€ ์ƒ์„ฑ์„ ๋ฐ˜๋ณตํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ์— ์ข‹์ง€ ์•Š์Œ

 

2.2 RecyclerView

  • ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค ํ•  ๋•Œ, ์œ„์— ์žˆ๋˜ ์•„์ดํ…œ์€ ์žฌํ™œ์šฉ ๋ผ์„œ ์•„๋ž˜๋กœ ์ด๋™ํ•˜์—ฌ ์žฌ์‚ฌ์šฉ
  • ์ฆ‰ ์•„์ดํ…œ์ด 100๊ฐœ์—ฌ๋„ 10๊ฐœ์ •๋„์˜ View๋งŒ ๋งŒ๋“ค๊ณ  10๊ฐœ๋ฅผ ์žฌํ™œ์šฉํ•ด์„œ ์‚ฌ์šฉ
  • View๋ฅผ ๊ณ„์† ๋งŒ๋“œ๋Š” ListView์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅ

 

 

 

 

03. RecyclerView ์‚ฌ์šฉ๋ฒ•

 

๐Ÿ’ก Adapter

  • ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์„ ๋ชฉ๋ก ํ˜•ํƒœ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ
  • ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ๋ฐ์ดํ„ฐ์™€ RecyclerView ์‚ฌ์ด์— ์กด์žฌํ•˜๋Š” ๊ฐ์ฒด
  • ๋ฐ์ดํ„ฐ์™€ RecyclerView ์‚ฌ์ด์˜ ํ†ต์‹ ์„ ์œ„ํ•œ ์—ฐ๊ฒฐ์ฒด

 

๐Ÿ’ก ViewHolder

  • ํ™”๋ฉด์— ํ‘œ์‹œ๋  ๋ฐ์ดํ„ฐ๋‚˜ ์•„์ดํ…œ๋“ค์„ ์ €์žฅํ•˜๋Š” ์—ญํ• 
  • ์Šคํฌ๋กค ํ•ด์„œ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ„ View๋ฅผ ์žฌํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ด View๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ์—ญํ• 

 

2-1. XML ์ •์˜

res/layout/activity_main.xml

<?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 // ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ XML์— ์ž…๋ ฅ
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />
       
</LinearLayout>

 

2-2. ์–ด๋Œ‘ํ„ฐ ํด๋ž˜์Šค์ •์˜

  • ์•ž์„œ ์ •์˜ํ•œ MyItem ํƒ€์ž…์˜ ๊ฐ์ฒด๋“ค์„ ArrayList๋กœ ๊ด€๋ฆฌํ•˜๋Š” MyAdapterํด๋ž˜์Šค๋ฅผ
    RecyclerView.Adapter๋กœ ํŒŒ์ƒํ•˜์—ฌ ์ •์˜
MyAdapter.kt 


class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {

    interface ItemClick {
        fun onClick(view : View, position : Int)
    }

    var itemClick : ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        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
    }

    inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
        val iconImageView = binding.iconItem
        val name = binding.textItem1
        val age = binding.textItem2
    }
}

 

2-3. ๋ฉ”์ธ์•กํ‹ฐ๋น„ํ‹ฐ ์ •์˜

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

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

        // ๋ฐ์ดํ„ฐ ์›๋ณธ ์ค€๋น„
        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)

        val adapter = MyAdapter(dataList)
        binding.recyclerView.adapter = adapter
        binding.recyclerView.layoutManager = LinearLayoutManager(this)

        adapter.itemClick = object : MyAdapter.ItemClick {
            override fun onClick(view: View, position: Int) {
                val name: String = dataList[position].aName
                Toast.makeText(this@MainActivity," $name ์„ ํƒ!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

 

 

 

โœจ ๋ทฐ ๋ฐ”์ธ๋”ฉ์„ ์ด์šฉํ•œ ์ตœ์‹  ์˜ˆ์‹œ, ๋ฉ€ํ‹ฐ ๋ทฐํƒ€์ž…

 

๋”๋ณด๊ธฐ

 

์–ด๋Œ‘ํ„ฐ

package com.limheejin.camp_standard4.presentation

import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.limheejin.camp_standard4.data.CardData
import com.limheejin.camp_standard4.data.priceDecimal
import com.limheejin.camp_standard4.databinding.ItemCard1Binding
import com.limheejin.camp_standard4.databinding.ItemCard2Binding
import com.limheejin.camp_standard4.databinding.ItemCard3Binding

class CardAdapter(private val cardItems: List<CardData>) : // ์–ด๋Œ‘ํ„ฐ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, ๋งŒ๋“ค์–ด๋†“์€ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„์คŒ
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {  // RecyclerView.Adapter<์•„๋ž˜์— ๋งŒ๋“ค ๋ทฐํ™€๋” ํด๋ž˜์Šค>๋ฅผ ์ƒ์† (๋ทฐํ™€๋” ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„)

        interface ItemClick {
            fun onClick(view: View, position: Int)
        }

    var itemClick: ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { // ์œ„์—์„œ ๋งŒ๋“  ๋ทฐํ™€๋”์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„, 10๋ฒˆ ์ •๋„ ์‹คํ–‰๋˜๊ณ  ์•ˆ ๋จ
        // ์•„์ดํ…œ์„ ๋‹ด์€ xml์˜ ๋ทฐ ๋ฐ”์ธ๋”ฉ ๊ฐ์ฒด ์ƒ์„ฑ
        return when(viewType){
                TYPE_1 -> {
                    val binding = ItemCard1Binding.inflate(LayoutInflater.from(parent.context), parent, false)
                    MultiViewHolder1(binding)
                }
                TYPE_2 -> {
                    val binding = ItemCard2Binding.inflate(LayoutInflater.from(parent.context), parent, false)
                    MultiViewHolder2(binding)
                }
                else -> {
                    val binding = ItemCard3Binding.inflate(LayoutInflater.from(parent.context), parent, false)
                    MultiViewHolder3(binding)
                }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { // ์ •๋ณด๋ฅผ ๋ฐ”์ธ๋”ฉ ํ•˜๋Š” ๋ถ€๋ถ„. ์ฒ˜์Œ ์–ด๋Œ‘ํ„ฐ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ธ์ˆ˜๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ, ์œ„์˜ ๋ทฐํ™€๋” ๊ฐ์ฒด์— ์–ด๋–ป๊ฒŒ ๋„ฃ์–ด์ค„์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๋ถ€๋ถ„
       //ํฌ์ง€์…˜์€ ๋ฆฌ์ŠคํŠธ์—์„œ ์–ด๋Š ์œ„์น˜์— ์žˆ๋Š”์ง€

        var currentItem = cardItems[position]
        when(holder.itemViewType){
            TYPE_1 -> {
                val viewHolder = holder as MultiViewHolder1
                viewHolder.bind(currentItem)
//                holder.itemView.setOnClickListener {
//                    onClick(currentItem)
//                }
            }
            TYPE_2 -> {
                val viewHolder = holder as MultiViewHolder2
                viewHolder.bind(currentItem)
            }
            TYPE_3 -> {
                val viewHolder = holder as MultiViewHolder3
                viewHolder.bind(currentItem)
            }
        }
    }

    override fun getItemViewType(position: Int): Int { // position๋งˆ๋‹ค ์–ด๋–ค ๋ทฐํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ์—ฐ๊ฒฐํ•ด์ค˜์•ผ ํ•จ
        return when (position) {
            0 -> TYPE_1
            1 -> TYPE_2
            2 -> TYPE_3
            else -> {
                throw IllegalAccessException("ERROR")
            }
        }
    }

    override fun getItemCount(): Int { // ๋ฐ์ดํ„ฐ๊ฐ€ ๋ช‡ ๊ฐœ์ธ์ง€ ๋ณ€ํ™˜
        // ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ ์•„์ดํ…œ ๊ฐœ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ์˜ ํฌ๊ธฐ
        return cardItems.size
    }
    // ๊ฐ„๋‹จํ•˜๊ฒŒ override fun getItemCount(): Int = cardItems.size ํ•œ ์ค„์งœ๋ฆฌ ์ฝ”๋“œ๋กœ ์ž…๋ ฅํ•ด๋„ ๋จ

    inner class MultiViewHolder1(private val binding: ItemCard1Binding) : // ๋ทฐํ™€๋” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ, RecyclerView.ViewHolder ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•จ. binding: ์•„์ดํ…œxml์ด๋ฆ„ + Binding
        RecyclerView.ViewHolder(binding.root) {

        // ๋ทฐ ๋ฐ”์ธ๋”ฉ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” root ๋ณ€์ˆ˜๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์„ ์˜๋ฏธ
        fun bind(card: CardData){
            val context = binding.root.context //์•ˆํ‹ฐํŒจํ„ด์ด๋ผ ์ง€์›Œ์•ผ๋จ

            binding.tvName.text = card.name
            binding.tvCardNumber.text = card.cardNumber
            binding.tvExpDate.text = card.expDate
            binding.tvPrice.text = priceDecimal(card.price)

            binding.itemCard1.setOnClickListener { // ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
                val intent = Intent(context, DetailActivity::class.java)
                intent.putExtra("CardData", card)
                context.startActivity(intent)
            }

        }
    }

    inner class MultiViewHolder2(private val binding: ItemCard2Binding) : // ๋ทฐํ™€๋” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ, RecyclerView.ViewHolder ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•จ. binding: ์•„์ดํ…œxml์ด๋ฆ„ + Binding
        RecyclerView.ViewHolder(binding.root) {

        // ๋ทฐ ๋ฐ”์ธ๋”ฉ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” root ๋ณ€์ˆ˜๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์„ ์˜๋ฏธ
        fun bind(card: CardData){
            val context = binding.root.context //์•ˆํ‹ฐํŒจํ„ด์ด๋ผ ์ง€์›Œ์•ผ๋จ

            binding.tvName.text = card.name
            binding.tvCardNumber.text = card.cardNumber
            binding.tvExpDate.text = card.expDate
            binding.tvPrice.text = priceDecimal(card.price)

            binding.itemCard2.setOnClickListener { // ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
                val intent = Intent(context, DetailActivity::class.java)
                intent.putExtra("CardData", card)
                context.startActivity(intent)
            }

        }
    }

    inner class MultiViewHolder3(private val binding: ItemCard3Binding) : // ๋ทฐํ™€๋” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ, RecyclerView.ViewHolder ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•จ. binding: ์•„์ดํ…œxml์ด๋ฆ„ + Binding
        RecyclerView.ViewHolder(binding.root) {

        // ๋ทฐ ๋ฐ”์ธ๋”ฉ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” root ๋ณ€์ˆ˜๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์„ ์˜๋ฏธ
        fun bind(card: CardData){
            val context = binding.root.context //์•ˆํ‹ฐํŒจํ„ด์ด๋ผ ์ง€์›Œ์•ผ๋จ

            binding.tvName.text = card.name
            binding.tvCardNumber.text = card.cardNumber
            binding.tvExpDate.text = card.expDate
            binding.tvPrice.text = priceDecimal(card.price)

            binding.itemCard3.setOnClickListener { // ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
                val intent = Intent(context, DetailActivity::class.java)
                intent.putExtra("CardData", card)
                context.startActivity(intent)
            }

        }
    }


}

 

๋ฉ”์ธํŽ˜์ด์ง€

package com.limheejin.camp_standard4.presentation

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.limheejin.camp_standard4.data.CardData
import com.limheejin.camp_standard4.data.CardList
import com.limheejin.camp_standard4.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val cardList = CardList()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setRecyclerView()
    }


    private fun setRecyclerView() { // ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ ์„ค์ •
        binding.rvRecyclerview.adapter = CardAdapter(cardList)
        // xml ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ์˜ ์–ด๋Œ‘ํ„ฐ๋กœ ์œ„์—์„œ ๋งŒ๋“  ์–ด๋Œ‘ํ„ฐ ์„ค์ •
        binding.rvRecyclerview.layoutManager = LinearLayoutManager(this)
        // xml ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ์˜ ๋ ˆ์ด์•„์›ƒ ๋งค๋‹ˆ์ € ์„ค์ •
    }

//    private fun adapterOnClick(card : Card) {
//        //bundle๋กœ ๋„˜๊ธฐ๋Š”์ž‘์—…
//        val intent = Intent(this, DetailActivity::class.java)
//        val bundle = Bundle().apply{
//            putParcelable(DetailActivity.EXTRA_CARD, card)
//        }
//        intent.putExtra(bundle)
//        startActivity(intent)
//    }

}

 

๋””ํ…Œ์ผ ํŽ˜์ด์ง€

package com.limheejin.camp_standard4.presentation

import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.limheejin.camp_standard4.data.CardData
import com.limheejin.camp_standard4.data.priceDecimal
import com.limheejin.camp_standard4.databinding.ActivityDetailBinding

class DetailActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDetailBinding
//    companion object {
//        const val EXTRA_CARD: String = "extra_card"
//    }

    private val selectedCard by lazy {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
            intent.getParcelableExtra("CardData", CardData::class.java)
        } else {
            intent.getParcelableExtra("CardData")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityDetailBinding.inflate(layoutInflater)
        setContentView(binding.root)

        selectedCard?.let {
            binding.tvName.text = it.name
            binding.tvCardNumber.text = it.cardNumber
            binding.tvExpDate.text = it.expDate
            binding.tvPrice.text = priceDecimal(it.price)
        }

//        val cardItem = intent.getParcelableArrayExtra<Card>(EXTRA_CARD)
//
//        cardItem?.name ๋“ฑ๋“ฑ
    }
}

 

xml ์ƒ์˜ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_recyclerview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:clipToPadding="false"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            tools:listitem="@layout/item_card1" />

 

 

๋ฐ˜์‘ํ˜•
 ๐Ÿ’ฌ C O M M E N T