[Android/Kotlin] RecyclerView Expandable - 확장되는 리스트

Notepad96

·

2022. 9. 3. 15:04

300x250

 

 

1. 요약

 

결과

이번 글에서는 RecyclerView와 Expandable Layout을 활용하여 확장이 되는 리스트를 구현하는 방법에 관하여 기술한다.

 

해당 예시에서는 확장되는 레이아웃의 데이터를 TextView들을 추가하는 식으로 간단하게 구현하였으며 여기서 사용할 데이터나, 레이아웃이 더욱 복잡해질 경우 이중(중첩) RecyclerView를 사용할 수도 있다.

 

 

 

 

2. 레이아웃

2-1. activity_main.xml

메인 레이아웃으로서 RecyclerView 1개로 구성하였다.

 

여기서 부모 레이아웃으로서 NestedScrollView를 사용하였는데 이는 리스트의 항목들이 확장되어 화면이 넘어가버려도 자연스럽게 나타나게 만들기 위해서이다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

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

</androidx.core.widget.NestedScrollView>

 

 


2-2. item_list.xml

리스트 항목을 나타내는 레이아웃이다. 

 

대분류를 나타내기 위하여 CardView를 사용하여서 Layout을 구성하였으며 Layout을 클릭하였을 때 Layout 아래 숨겨져 있던 레이아웃을 보이게 처리함으로써 확장되는 레이아웃을 구현할 수 있다.

 

레이아웃 확장에 관해서 자세한 사항은 이전에 작성하였던 아래 글을 참조하면 될 것 같다.

 

2022.09.02 - [Android] - [Android/Kotlin] Expandable Layout - 레이아웃 확장하기

 

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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    android:layout_margin="5dp">

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/layout01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:strokeWidth="1dp"
        app:cardCornerRadius="3dp"
        app:strokeColor="@color/black">

        <TextView
            android:id="@+id/textView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_gravity="center_vertical"
            android:text="Layout 01"
            android:textColor="@color/black"
            android:textSize="22sp" />
        <ImageButton
            android:id="@+id/layoutBtn01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:src="@drawable/arrow_up"
            android:clickable="false"
            android:background="@android:color/transparent"
            android:layout_gravity="right"
            android:textSize="22sp" />
    </com.google.android.material.card.MaterialCardView>

    <LinearLayout
        android:id="@+id/layoutDetail01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:visibility="gone"
        android:background="#D8D7D7">
    </LinearLayout>

</LinearLayout>

 

 

 

 

3. 코드 및 설명

3-1. MainActivity.kt

메인 파일에서는 정의한 리스트 어뎁터를 사용하여 RecyclerView를 초기화해준다.

 

package com.notepad96.recyclerviewexpandable

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.notepad96.recyclerviewexpandable.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.recycler01.apply {
            adapter = ListAdapter()
            layoutManager = LinearLayoutManager(context)
            setHasFixedSize(true)
        }
    }
}

 


3-2. ListAdapter.kt

리스트 어뎁터를 정의한 파일이다.

 

리스트에 나타낼 데이터로 Map 형식에 데이터를 사용하였으며 Map은 <Key, Value> 형태로 여기서 Key는 대분류며 Value에는 그 분류 아래 항목들이 들어가도록 만들었다.

 


다음으로 View 초기화 부분을 살펴보면 init에서 Click Event를 정의함으로써 레이아웃을 클릭하였을 때 레이아웃이 확장되며 화살표가 움직이도록 애니메이션을 적용하였다.

 

bind 함수에서는 각 리스트 항목을 초기화하며 각 항목에 해당하는 Value는 TextView를 정의하여 확장된 레이아웃의 addView 해줌으로써 항목들을 나타내었다.

 

이 부분에서 다른 정의된 복잡한 레이아웃을 사용하거나 Data가 많거나, 표시할 내용이 많다고 한다면 이중(중첩) RecyclerView를 사용하여 구현할 수도 있다.

 

 

package com.notepad96.recyclerviewexpandable

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.notepad96.recyclerviewexpandable.databinding.ItemListBinding

class ListAdapter: RecyclerView.Adapter<ListAdapter.MyView>() {
    val data = mapOf(
        "커피" to listOf("아메리카노", "바닐라라떼", "카페라떼", "카페모카"),
        "프라페" to listOf("녹차프라페", "커피프라페", "쿠키프라페"),
        "에이드" to listOf("레몬에이드", "자몽에이드"),
        "음료" to listOf("나랑드", "토레타")
    )

    inner class MyView(private val binding: ItemListBinding): RecyclerView.ViewHolder(binding.root) {
        init {
            binding.layout01.setOnClickListener {
                if(binding.layoutDetail01.visibility == View.VISIBLE) {
                    binding.layoutDetail01.visibility = View.GONE
                    binding.layoutBtn01.animate().apply {
                        duration = 200
                        rotation(0f)
                    }
                } else {
                    binding.layoutDetail01.visibility = View.VISIBLE
                    binding.layoutBtn01.animate().apply {
                        duration = 200
                        rotation(180f)
                    }
                }
            }
        }

        fun bind(pos: Int) {
            binding.textView01.text = data.keys.elementAt(pos)
            data.values.elementAt(pos).forEach {
                val view = TextView(binding.root.context).apply {
                    text = "· $it"
                    textSize = 20f
                    setPadding(10, 10, 5, 10)
                }
                binding.layoutDetail01.addView(view)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyView {
        val view = ItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MyView(view)
    }

    override fun onBindViewHolder(holder: MyView, position: Int) {
        holder.bind(position)
    }

    override fun getItemCount(): Int {
        return data.size
    }
}

 

 

 

 

4. 전체 파일

 

 

GitHub - Notepad96/BlogExample02

Contribute to Notepad96/BlogExample02 development by creating an account on GitHub.

github.com

 

 


 

● Click Event 추가

 

결과

 

fun bind(pos: Int) {
    binding.textView01.text = data.keys.elementAt(pos)
    data.values.elementAt(pos).forEach {
        val view = TextView(binding.root.context).apply {
            text = "· $it"
            textSize = 20f
            setPadding(10, 10, 5, 10)
        }

        val intent = Intent(binding.root.context, DetailActivity::class.java)
            .putExtra("name", view.text.toString())

        view.setOnClickListener {
            binding.root.context.startActivity(intent)
        }

        binding.layoutDetail01.addView(view)
    }
}

 

세부 메뉴를 Click 할 경우 이벤트 처리를 하기 위해서는 ListAdapter.kt 파일의 bind 함수 부분을 위와 같이 추가를 해주시면 됩니다.

 

 

상세 페이지로 이동하기 위해서는 상세 페이지를 나타낼 Detail Activity를 추가해준 후 Intent를 초기화해줍니다. 

 

이 부분에 관하여 상세한 내용은 아래글을 참고하시면 될 것 같습니다.

 

 

Android Kotlin 액티비티 전환 - intent, putExtra

1. 결 과 2. activity_main.xml <?xml version="1.0" encoding="utf-8"?> 3. activity_main2.xml <?xml version="1.0" encoding="utf-8"?> 4. MainActiviy.kt package com.example.myapplication import andro..

notepad96.tistory.com

 

 

이후 View 즉 Text를 Click 할 경우 이동하는 Event가 발생하도록 하기 위해서 view.setOnClickListener를 정의하여 Click 시 startActivity로 Detail Activity로 이동하게 됩니다.

 

 

 

300x250