(Android/안드로이드) RecyclerView 에서 TextView 더보기/접기 구현하기

✨ 개요


1. 구현 핵심 포인트 요약


2. Item 모델 & 레이아웃

2-1 Item 모델

data class Item(
    val id: String,
    val text: String,
    var canExpand: Boolean? = null, // 처음 한 번만 계산하기 위한 플래그
    var isExpanded: Boolean = false // 현재 펼침 여부
)

2-2 Item Layout

<?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"
                                                   android:layout_width="match_parent"
                                                   android:layout_height="wrap_content"
                                                   android:background="@drawable/border"
                                                   android:layout_margin="16dp">

    <TextView
            android:id="@+id/tvContent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="14dp"
            android:textColor="@color/white"
            app:layout_constraintTop_toTopOf="parent" />

    <Button
            android:id="@+id/btnMore"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/tvContent"
            android:text="더보기" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. ViewHolder & ListAdapter 에서 더보기/접기 구현

inner class ItemViewHolder(val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(item: Item) {
        binding.tvContent.text = item.text
        binding.tvContent.maxLines = 2

        // 기본은 접힌 상태(1줄)
        binding.tvContent.post {
            if (item.canExpand == null) { // 최초 1회만 호출되도록 설정
                val lineCount = binding.tvContent.layout?.lineCount ?: 0
                binding.btnMore.isVisible = (lineCount >= 2)
                item.canExpand = (lineCount >= 2)
                binding.tvContent.maxLines = if (item.isExpanded) 2 else 1
            }
        }

        if (item.canExpand != null) {
            binding.tvContent.maxLines = if (item.isExpanded) 2 else 1
            binding.btnMore.isVisible = item.canExpand == true
            binding.btnMore.text = if (item.isExpanded) "접기" else "더보기"
        }

        binding.btnMore.setOnClickListener {
            val pos = adapterPosition
            if (pos == RecyclerView.NO_POSITION) return@setOnClickListener

            if (item.isExpanded) {
                item.isExpanded = false
                binding.tvContent.maxLines = 1
                binding.btnMore.text = "더보기"
            } else {
                item.isExpanded = true
                binding.tvContent.maxLines = 2
                binding.btnMore.text = "접기"
            }
        }
    }
}

4. 왜 post{} 안에서 lineCount를 계산해야 할까?

// 잘못된 방식
binding.tvContent.text = item.text
val lines = binding.tvContent.lineCount // ❌ 잘못된 값

// 올바른 방식
binding.tvContent.post {
    val lines = binding.tvContent.layout?.lineCount ?: 0
}

5. canExpand 플래그가 필요한 이유


6. 더보기/접기 토글 UX 팁


7. 정리

기능 설명
2줄 이상 감지 lineCount로 정확히 판단
깜빡임 없음 canExpand 저장으로 재측정 방지
ViewHolder 재활용 OK stable한 값만 재바인딩
UX 좋은 확장/축소 버튼 토글 처리


Related Posts