티스토리 뷰

목표 : RecyclerView의 상단에 Header Item, 하단에 Footer Item 을 추가

 

1. header, footer item layout 추가

- 둘 다 간단하게 텍스트 뷰를 중앙에 위치시킴

<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/rv_default_height"
        android:layout_margin="8dp"
        android:background="@color/item_background"
        android:paddingStart="8dp"
        android:paddingEnd="8dp">

        <TextView
            android:id="@+id/tv_item_header_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:lineSpacingExtra="0dp"
            android:text="@string/recyclerview_header_title"
            android:textAlignment="center"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/rv_default_height"
        android:layout_margin="8dp"
        android:background="@color/item_background"
        android:paddingStart="8dp"
        android:paddingEnd="8dp">

        <TextView
            android:id="@+id/tv_item_footer_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:lineSpacingExtra="0dp"
            android:text="@string/recyclerview_footer_title"
            android:textAlignment="center"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

 

2. Custom Adapter에 Header, Footer에 대한 처리 구현

  • getItemCount에 헤더,풋터 갯수를 포함
  • getItemViewType에 position에 따른 타입 반환 추가
  • 각 타입에 따른 ViewHolder Class 추가
  • onCreateViewHolder에서 각 타입에 따른 ViewHolder Class 반환 추가
  • onBindViewHolder에 각 ViewHolder에 따른 처리 추가
class RvExampleDefaultAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val TYPE_HEADER = 0

    private val TYPE_ITEM = 1

    private val TYPE_FOOTER = 2


    // 내부 데이터
    private var tasks: ArrayList<Task> = arrayListOf()

    // onCreateViewHolder에서 inflate하는 부분을 간단하게 하기 위해 추가
    private fun ViewGroup.inflate(layoutRes: Int): View = LayoutInflater.from(context).inflate(layoutRes, this, false)

    // 각 View의 Type에 따른 ViewHolder 반환
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            TYPE_HEADER -> HeaderViewHolder(parent.inflate(R.layout.item_recyclerview_header))
            TYPE_FOOTER -> FooterViewHolder(parent.inflate(R.layout.item_recyclerview_footer))
            else -> TaskViewHolder(parent.inflate(R.layout.item_recyclerview_default))
        }
    }

    // 아이템의 전체 갯수 + 헤더(1) + 풋터(1)
    override fun getItemCount(): Int {
        return tasks.size + 2
    }

    // 아이템의 타입을 반환 (position은 0 기반이므로 (전체 갯수 - 1) 일 경우에 Footer 타입 반환)
    override fun getItemViewType(position: Int): Int {
        return when (position) {
            0 -> TYPE_HEADER
            itemCount - 1 -> TYPE_FOOTER
            else -> TYPE_ITEM
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        // Holder에 따른 Binding 처리
        when (holder) {
            is HeaderViewHolder -> {
                holder.itemView.setOnClickListener {
                    Snackbar.make(it, "Header is Clicked!!", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show()
                }
            }
            is FooterViewHolder -> {
                holder.itemView.setOnClickListener {
                    Snackbar.make(it, "Footer is Clicked!!", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show()
                }
            }
            else -> {
                val item = tasks[position - 1]

                // 내부 데이터를 사용하여 각 아이템 값 설정
                holder.itemView.apply {
                    tv_item_default_name.text = item.name
                    tv_item_default_details.text = item.details
                    iv_item_default_img.setImageFromUrl(item.image)
                }
            }
        }
    }

    // 내부 데이터 전체 값 갱신
    fun setTaskList(list: ArrayList<Task>) {
        tasks.clear()
        tasks.addAll(list)

        notifyDataSetChanged()
    }

    // 내부 데이터 값 추가
    fun addTask(task: Task) {
        tasks.add(task)

        notifyDataSetChanged()
    }

    // 내부 데이터 값 제거 (헤더 아이템때문에 -1)
    fun removeTask(position: Int) {
        tasks.removeAt(position - 1)

        notifyDataSetChanged()
    }

    // Item Type에 따른 View Holder Class
    class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    
    class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    
    class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

 

3. ItemTouchHelper에 Header/Footer는 삭제할 수 없게 처리 구현

override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
  // Header, Footer 에서는 동작하지 않도록 처리
  return if (viewHolder is RvExampleDefaultAdapter.TaskViewHolder)
  	super.getSwipeDirs(recyclerView, viewHolder)
  else
  	0
}

 

4. 완성되면?

Header - Item - Footer

 

RecyclerView

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함