Sticky Header Recyclerview using ItemDecoration without library - 1 (Kotlin)
Sticky Header 란? 사용자가 페이지를 아래로 스크롤 할 때, 화면 상단에 고정된 뷰를 말한다.
검색해 보면 라이브러리 한 줄로 사용하기도 쉽게 해뒀지만, 각자의 상황에 맞게 커스텀 하려면 라이브러리는 불편하다
ItemDecoration을 커스텀 해서 고정할 헤더 뷰를 그리기로 했다. 아래는 예제 샘플이다.
바쁜 사람은 여기 를 눌러 소스를 보러 가시길...
아래 부턴 삽질 히스토리
사실 맨 위의 상단을 고정하는 건 검색해보면 되게 많이 나온다.
1.
https://github.com/paetztm/recycler_view_headers
paetztm/recycler_view_headers
Simple Recycler View Section Header implementation - paetztm/recycler_view_headers
github.com
나는 이 분 소스를 많이 참고 했다..
How can I make sticky headers in RecyclerView? (Without external lib)
I want to fix my header views in the top of the screen like in the image below and without using external libraries. In my case, I don't want to do it alphabetically. I have two different types of...
stackoverflow.com
하지만 내가 구현해야 할 상황은 고정 될 뷰 위에 함께 스크롤 되어야할 뷰가 하나 더 존재하는 상황이다.
즉, 전체 RecyclerView에 고정되어야 할 뷰 인덱스가 0이 아니라 1인 경우이다.
먼저 RecyclerView를 만든다.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
</LinearLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var adapter: MainAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupAdapter()
}
private val peopleInfoList = listOf(
Data("apple", 17),
Data("banana", 31),
Data("car", 26),
Data("dog", 26),
Data("egg", 24),
Data("fish", 33),
Data("great", 40),
Data("happy", 10),
Data("ice", 20),
Data("juice", 32),
Data("key", 50),
Data("lonnie", 30),
Data("mom", 58),
Data("notice", 11),
Data("object", 36),
Data("people", 38),
Data("queen", 15),
Data("right", 22),
Data("sight", 27),
Data("tiger", 13),
Data("uv", 29),
Data("virus", 44),
Data("wow", 36),
Data("xylitol", 24),
Data("yes", 26),
Data("zoo", 36)
)
private fun setupAdapter() {
adapter = MainAdapter(peopleInfoList)
val layoutManager = LinearLayoutManager(this)
recycler.layoutManager = layoutManager
recycler.adapter = adapter
}
}
Data.kt
data class Data(var name: String = "", var age: Int = -1)
RecyclerView에 집어넣을 데이터 클래스를 만들고 RecyclerView와 Adapter를 연결 시킨다.
Adapter엔 Data를 모델로 한 리스트를 매개 변수로 넣어준다.
ItemViewType을 정해보자.
최 상단에 올라갈 뷰를 TYPE_TOP 으로 정한다.
그 다음 리스트에서 고정될 뷰는 TYPE_HOLDER
리스트가 그려질 뷰는 TYPE_ITEM
리스트가 없으면 TYPE_EMPTY
companion object {
const val TYPE_TOP = 0
const val TYPE_HOLDER = 1
const val TYPE_EMPTY = 2
const val TYPE_LIST = 3
}
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> TYPE_TOP
1 -> TYPE_HOLDER
2 -> TYPE_EMPTY
else -> TYPE_LIST
}
}
여기서 ViewType이 TYPE_HOLDER일 경우에 스크롤 시 상단에 그려진다는 것을 유의하자 TYPE_TOP과 혼돈 주의
각 뷰에 해당하는 layout xml을 만들고 뷰홀더에 bind 해준다.
top_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:background="#F98740"
android:gravity="center"
android:minHeight="54dp"
android:paddingLeft="16dp"
android:paddingTop="20dp"
android:paddingRight="16dp"
android:paddingBottom="20dp"
android:text="This is a top type view"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
hold_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:background="#ff8181"
android:gravity="center"
android:minHeight="54dp"
android:paddingLeft="16dp"
android:paddingTop="20dp"
android:paddingRight="16dp"
android:paddingBottom="20dp"
android:text="This is a hold type view"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="19dp"
android:paddingBottom="19dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_weight="1"
android:text="text"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="13dp"
android:drawablePadding="10.3dp"
android:text="30"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
empty_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|top"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:gravity="center"
android:text="There are currently no registered Item."
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
Adapter 내에서 각 View Type 에 맞게 뷰 홀더를 만들어 바인드 해준다.
override fun onCreateViewHolder(parent: ViewGroup, position: Int): RecyclerView.ViewHolder {
val view: View
return when (recyclerItemList[position].type) {
TYPE_TOP -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.top_item, parent, false)
TopViewHolder(view)
}
TYPE_HOLDER -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.hold_item, parent, false)
HolderViewHolder(view)
}
TYPE_EMPTY -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.empty_item, parent, false)
EmptyViewHolder(view)
}
else -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
ItemViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is TopViewHolder -> {
holder.bindView()
}
is HolderViewHolder -> {
holder.bindView()
}
is EmptyViewHolder -> {
holder.bindView()
}
is ItemViewHolder -> {
holder.bindView()
}
}
}
inner class TopViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindView() {
}
}
inner class HolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindView() {
}
}
inner class EmptyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindView() {
}
}
inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindView() {
}
}
다음 페이지에서 계속.. NEXT