정상에서 IT를 외치다

[Android, StickyHeader] StickyHeader in RecyclerView 본문

안드로이드

[Android, StickyHeader] StickyHeader in RecyclerView

Black-Jin 2018. 6. 11. 12:09
반응형


안녕하세요. 블랙진입니다.


이번에는 RecyclerView 에서 StickyHeader 를 만드는 법에 대해 포스팅 하겠습니다.



StickyHeader?


리스트 중 하나의 아이템을 상단에 고정 시키는 것을 의미합니다.


아래의 화면과 같이 리스트를 스크롤 할 때 중간에 있던 아이템이 상단에 고정되는 것을 확인하실 수 있습니다.


저의 Github 예제 중 MyMaterialDesign 에 적용해 보았는데요.


제가 사용한 StickyHeader 라이브러리는 아래 링크입니다.


How to Use StickyHeader in RecyclerView


라이브러리 사용법은 위 링크를 통해 보시면 잘 나와있습니다. 


아래 게시글은  MyMaterialDesign 을 수정하여 아래 gif 와 같은 효과를 나타내는 방법으로 제 git hub 예제 파일을 다운받아 따라하셔야 합니다.







1. app 단계의 build.gradle dependencies 에 아래 코드를 추가해 줍니다. 


아래 라이브러리에 대한 최신 버전은 How to Use StickyHeader in RecyclerView 에서 확인 하실 수 있습니다.

//sticky
implementation 'com.brandongogetap:stickyheaders:0.5.0'



2. StickyLayoutManager 를 상속 하는 MyStickyLayoutManager 를 만들어 줍니다.

class MyStickyLayoutManager constructor(context: Context, headerHandler: StickyHeaderHandler)
: StickyLayoutManager(context, headerHandler) {

override fun scrollToPosition(position: Int) {
super.scrollToPositionWithOffset(position, 0)
}
}



3. MaterialActivity 에서 RecyclerView 의 layoutManager 을 StickyLayoutManager 로 변경해 줍니다.

val stickyLayoutManager = StickyLayoutManager(this@MaterialActivity, adapter)

stickyLayoutManager.elevateHeaders(true) // Default elevation of 5dp

// init recycler view
with(rvActivityMaterial) {
//layoutManager = LinearLayoutManager(this@MaterialActivity)
layoutManager = stickyLayoutManager
adapter = this@MaterialActivity.adapter
}

adapter.setItems(getMovieItems())

stickyLayoutManager 의 elevateHeaders 값을 설정하여 Header 의 그림자 효과를 컨트롤 할 수 있습니다.




4. activity_material.xml 의 recycler view 를 FrameLayout 으로 감싸줍니다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
android:id="@+id/rvActivityMaterial"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>

</FrameLayout>


</android.support.constraint.ConstraintLayout>



5. MaterialAdapter 에 StickyHeaderHandler 을  상속하면 getAdapterData() 를 오버라이딩 해야 합니다.

class MaterialAdapter: RecyclerView.Adapter<CommonViewHolder>(), StickyHeaderHandler {


...

override fun getAdapterData(): MutableList<*> {
return items
}

}



6. 기존에는 MovieHolder 를 하나만 사용했지만 StickyHeader 로 사용할 Holder 를 6-1 ~ 6-3 과정을 따라 추가해줍니다.



6-1 CommonViewHolder 를 만들어 공통으로 사용할 함수와 상속 변수를 설정해 줍니다.

abstract class CommonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

abstract fun onBindView(item: Movie)
}


6-2 기존에 사용 했던 MovieHolder 를 Adapter 내부가 아닌 밖에 새로운 Class 로 만들어 줍니다.

class MovieHolder(private val movieView: View)
: CommonViewHolder(movieView) {

private val placeholder = ColorDrawable(Color.GRAY)

override fun onBindView(item: Movie) {

with(movieView) {

Glide.with(context)
.load(item.url)
.placeholder(placeholder)
.into(ivItemMovie)

tvItemMovieGenre.text = item.genre
tvItemMovieTitle.text = item.title
tvItemMovieContent.text = item.content

}

}

companion object {

fun newInstance(parent: ViewGroup): MovieHolder {

val itemView = LayoutInflater.from(parent.context)

.inflate(R.layout.item_movie, parent, false)

return MovieHolder(itemView)

}

}
}



6-3 StickyHeader 로 사용할 StickyHolder 또한 새로운 Class 로 생성합니다.

class StickyHolder(private val movieView: View)

: CommonViewHolder(movieView) {

override fun onBindView(item: Movie) {

with(movieView) {

}

}

companion object {

fun newInstance(parent: ViewGroup): StickyHolder {

val itemView = LayoutInflater.from(parent.context)

.inflate(R.layout.item_sticky_header, parent, false)

return StickyHolder(itemView)

}

}
}


item_sticky_header

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#000000">

<TextView
android:text="StickyHeader"
android:textSize="20dp"
android:textColor="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>

</android.support.constraint.ConstraintLayout>



7. Movie 에 MovieHolder 와 StickyHolder 를 구분한 listType 변수를 추가해 줘야 합니다.


listType 이 1 일때는 MovieHolder 를 2일때는 StickyHolder 를 생성해 줄 겁니다.

open class Movie(val listType: Int,
val url: String?,
val genre: String?,
val title: String?,
val content: String?): Parcelable {

constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString())

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(listType)
parcel.writeString(url)
parcel.writeString(genre)
parcel.writeString(title)
parcel.writeString(content)
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<Movie> {
override fun createFromParcel(parcel: Parcel): Movie {
return Movie(parcel)
}

override fun newArray(size: Int): Array<Movie?> {
return arrayOfNulls(size)
}
}
}



8. MaterialAdapter 의 getItemViewType, onCreateVIewHolder, onBindViewHolder 를 수정하면 MaterialAdapter 의 전체 모습은 아래와 같습니다.

class MaterialAdapter: RecyclerView.Adapter<CommonViewHolder>(), StickyHeaderHandler {

private var items = mutableListOf<Movie>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {

when(viewType) {

1 -> return MovieHolder.newInstance(parent)

2 -> return StickyHolder.newInstance(parent)
}

return MovieHolder.newInstance(parent)
}

override fun onBindViewHolder(holder: CommonViewHolder, position: Int) {
holder.onBindView(items[position])
}

override fun getItemCount() = items.size


/**
* 1 -> MovieHolder
* 2 -> StickyHolder
*/
override fun getItemViewType(position: Int): Int = items[position].listType

/**
* StickyHeaderHandler
*/
override fun getAdapterData(): MutableList<*> {
return items
}

// 아이템 설정
fun setItems(items: MutableList<Movie>) {
this.items = items
}


}



9.  헤더로 사용할 아이템을 만들어 StickyHeader 를 상속해 줍니다.  


위 예제에서는  MovieHeader class 를 아래와 같이 만듭니다.

class MovieHeader(headerListType: Int, headerUrl: String?, headerGenre: String?, headerTitle: String?, headerContent: String?)
: Movie(headerListType, headerUrl, headerGenre, headerTitle, headerContent), StickyHeader



10. MovieHeader 를 SampleData 에 추가해 줍니다.

val items = mutableListOf<Movie>()

val movieHeader = MovieHeader(2, null, null, null, null)

val movie1 = Movie(1, "http://static.hubzum.zumst.com/hubzum/2018/02/06/09/962ec338ca3b4153b037168ec92756ac.jpg",

"action", "Black Panther", "this movie open in 2018.01")

val movie2 = Movie(
1, "https://t1.daumcdn.net/cfile/tistory/0138F14A517F77713A",

"action", "Iron Man 3", "this movie open in 2013.04")

val movie3 = Movie(1, "https://i.ytimg.com/vi/5-mWvUR7_P0/maxresdefault.jpg",

"action", "Ant Man", "this movie open in 2015.06")

items.add(movieHeader)
items.add(movie1)
items.add(movieHeader)
items.add(movie2)
items.add(movieHeader)
items.add(movie3)








반응형
Comments