RecyclerView Itemdecoration 올바르게 사용하기

DoDoBest

·

2024. 7. 24. 12:08

1. 구현 요구 사항

 

프로필 화면에서 사용자들이 모임에서 찍었던 사진을 GridView 타입으로 보여줄 수 있는 UI가 필요했습니다.

이때, 인스타그램과 같이 사진 사이에 동일한 Margin이 필요했습니다.

 

2. 구현 방법

 

사진들을 표시하기 위해 구현한 RecyclerView 입니다.

한 열에 여러 이미지를 표시하기 위해 GridLayoutManager를 이용했으며, 개수는 3개로 설정했습니다.

 

 

ViewHolder에 사용한 XML 입니다.

RecyclerView에서 한 열의 아이템을 3개로 설정했기 때문에, 3개의 아이템이 모든 width를 차지하도록 Layout의 width를 match_parent로 설정했습니다.

이미지정사각형으로 표시되도록 Style을 정의해 설정했습니다.

아이템 사이의 세로 방향 Margin 값은 xml에서 2dp로 설정했습니다.

 

 

가로 방향 Margin 값은 ItemDecoration을 이용해 ViewHolder의 View 위치에 따라 다른 값이 적용되도록 구현했습니다.

 

사진의 크기는 동일해야 하므로, ViewHolder에 적용되는 전체 Margin 값이 동일하도록 설정했습니다.

 

아이템 사이의 세로 간격은 상단 2dp, 하단 2dp, 총 4dp이므로, 아이템 사이의 가로 간격도 4dp가 되어야 합니다.

왼쪽 또는 오른쪽 아이템의 margin 값 3dp + 가운데 아이템의 margin 값 1dp, 총 4dp가 되어 세로 간격과 동일하게 설정되도록 ItemDecoration에 사용할 marginSize3dp로 설정했습니다.

 

ViewHolder가 Adapter로부터 제거된 경우 AdapterPosition 값은 -1이 됩니다.

위치를 나타내는 Location enum class에서 -1을 입력하면 null이 반환되고, when 조건 문에서 null block이 실행됩니다. 여기서 Margin 값을 0으로 설정했습니다.

 

 

 

3. 구현 결과

 

완성된 UI 입니다.

단순히 봤을 때는 문제가 없어 보이지만, 오류가 존재합니다.

 

 

 

4. 문제 상황

 

ListAdapter에 입력된 아이템을 삭제할 때, 삭제되는 ViewHolder의 크기가 변경되어 삭제된 아이템이 속한 열의 하단에 의도하지 않은 여백이 생겼습니다.

 

 

느리게 재생해서 다시 확인해보면 명확하게 확인할 수 있습니다.

 

 

 

Layout Inspector에서 확인해본 결괏값입니다.

Viewmargin이 아닌 Viewheight 자체가 커진 것을 알 수 있습니다.

 

 

5. 원인 분석

 

View의 Height가 늘어난 문제의 원인을 분석해보겠습니다.

Margin 값을 적용했던, ItemDecoration getItemOffsets 함수에 전달되는 Rect는 무엇일까요?

 

 

 

Rect는 ViewHolder가 그려질 도화지입니다.

이 도화지에 Margin 값을 적용하면 ViewHolder가 그려질 공간이 줄어들게 됩니다.

 

 

ItemDecoration 전체 코드를 다시 확인해보겠습니다.

ViewHolder가 삭제될 예정으로 -1인 NO_POSITION 값이 반환되었을 때, when의 null block이 실행됩니다.

이때, margin 값을 0으로 설정했기 때문에, 기존에 줄어들었던 View가 그려질 도화지가 원래 크기로 돌아오며 커지는 것처럼 보인 것입니다.

 

 

6. 해결 방법

 

해결 방법은 아이템이 삭제 되더라도, 이전 Margin 값이 유지되도록 설정하면 됩니다.

getItemOffsets의 파라미터로 전달되는 outRect에는 이전에 적용된 Margin 값이 적용되지 않은 0,0,0,0 상태로 전달됩니다.

그래서 View의 setTag 함수를 이용해 이전 Position 값을 저장했습니다.

 

 

setTag 함수의 설명을 보면, View의 tag로 지정하는 Key 값은 Android resource에 정의된 고유 id 값이어야 함을 알 수 있습니다.

그 외의 값을 입력하면 런타임에 IllegalArgumentException이 발생합니다.

 

 

그래서 Android resource에 정의된 고유 id 값을 key로 사용하기 위해 values에 key.xml을 만들었습니다.

여기에 view tag의 key로 사용할 값을 정의했습니다.

정의한 값은 ItemDecoration의 property 변수에 넣어줬습니다.

 

 

outRect에 margin을 설정할 때, view의 setTag 함수로 View의 Position을 저장했습니다.

View의 adpaterPosition이 -1인 경우에는 getTag 함수를 이용해 이전 Position 값에 따라 margin을 설정했습니다.

 

 

그 결과 문제를 해결할 수 있었습니다.