RecyclerView StaggeredLayoutManager
RecyclerView의 LayoutManager를 StaggeredLayoutManager로 아래 사진과 같은 레이아웃이 그려진다.
사진과 같이 Height에 따라 빈 공간을 채우게 되는데, 주의할 점은 인덱스가 채워지는 순서로 매겨진다는 것이다. 일반적인 Grid 레이아웃의 인덱스와는 다른 형태인 것을 볼 수 있다.
ItemDecorator를 이용하여 RecyclerView 아이템에 여백을 설정할 때, 인덱스로 위치를 구분하곤 하는데, StaggeredLayoutManager에선 인덱스로 위치를 구분하긴 힘들기 때문에 조금 다른 방법을 사용해야한다.
아이템 위치 구분하기
위의 사진과 같이 아이템 끼리의 간격을 동일하게 지정해주려면, 각 아이템이 어떤 열에 포함되어 있는지를 알아내는 게 중요하다.
StaggeredLayoutManager에서 열을 나타내는 속성이 spanIndex 인데, layoutParmas를 통해 가져올 수 있다.
layoutParams.viewLayoutPosition은 화면 위치 상의 순서를 뜻한다.
이 두 속성을 가지고 아이템의 위치를 구분하는 방법은 아래와 같다.
* spanCount는 StaggeredLayoutManager의 열 개수를 뜻한다.
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val layoutParams = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
val position = parent.getChildAdapterPosition(view)
val spanIndex = layoutParams.spanIndex
val layoutPosition = layoutParams.viewLayoutPosition
val itemCount = parent.adapter?.itemCount ?: 0
val leftEdge = spanIndex == 0 // 첫 번째 열이면 왼쪽 가장자리에 위치 (spanIndex == 0)
val rightEdge = spanIndex == spanCount-1 // 마지막 열이면 오른쪽 가장자리에 위치
val topEdge = position < spanCount // 열의 개수보다 작으면 최상위 아이템
val bottomEdge = layoutPosition >= (itemCount - spanCount) // 아이템 수 - 열 개수 보다 크면 마지막 아이템
outRect.setMargins(
left = if(leftEdge) baseMargin * 2 else baseMargin / 2,
right = if(rightEdge) baseMargin * 2 else baseMargin / 2,
top = if(topEdge) topMargin else 0,
bottom = baseMargin
)
}
setMargins 메소드는 아래와 같이 확장 함수로 만들어 사용했다.
( Rect 클래스의 set 메소드가 자바로 작성 되었기 때문에 매개변수를 넘겨줄 때 이름을 지정할 수 없다. 여백을 지정하는 메소드를 이용할 때 매개변수를 잘못 전달하는 등의 실수가 많이 일어나기 때문에 확장 함수를 추가하였다. )
fun Rect.setMargins(left:Int, right:Int, top:Int, bottom:Int){
this.set(left, top, right, bottom)
}
데코레이터 적용
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".exam01.Exam01Activity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_grid"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
LayoutManager와 spanCount는 xml에서 바로 지정하였다.
private fun initRecyclerView(){
rvGridView.addItemDecoration(Exam02Decorator(this, 2))
rvGridView.adapter = Exam02Adapter(arrayListOf<ExamItem>().apply {
repeat(20){
add(
ExamItem(
height = (100..300).random().dpToPx(this@Exam02Activity),
color = Color.rgb((20..255).random(), (20..255).random(), (20..255).random())
)
)
}
})
}
StaggeredLayoutManager는 Height가 다른 아이템을 위한 레이아웃이므로, Height는 랜덤 값을 지정해주었다.
실행 결과
참고 자료
'안드로이드 > 이론' 카테고리의 다른 글
[ 안드로이드 ] View에 블러처리 하기 - BlurView 라이브러리 (0) | 2023.09.17 |
---|---|
[ 안드로이드 ] Fragment에서 onBackPressed(뒤로가기 이벤트) 처리하기 (0) | 2023.09.09 |
[ 안드로이드 ] WebView 동영상 전체화면 처리 ( + 상단바 숨기기) (0) | 2023.07.12 |
[ 안드로이드 ] ViewPager2 안에 WebView 중첩 Swipe 처리 (0) | 2023.07.12 |
[ 안드로이드 ] WebView 페이지 탐색 처리 (0) | 2023.07.06 |