본문 바로가기

Android.log

RecyclerView 지나간 높이 구하기

반응형


RecyclerView를 사용하여 리스트 형태의 화면에서 특정 포지션이 상단에 위치 했을때 뭔가를 처리(상단에 고정된 뷰를 보여준다던지..) 하기 위해서 RcyclerView.OnScrollListener 에서 LayoutManager.findFirstVisibleItemPosition 메서드로 보여지는 첫번째 포지션의 정보를 가져와서 처리한다.


override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)

if (layoutManager.findFirstCompletelyVisibleItemPosition() == SOME_POSITION) {
//Do Something
}
}


이런 개발 방법은 주로 리시트 특정 홀더가 스크롤 되면서 넘어 갈때 동일한 상단 고정된 뷰를 보여주기 위해서 이런식으로 많이 처리한다.



하지만 이번에 업무에서 조금 특별한 미션을 받았다!!


RecyclerView의 특정 Holder 중간 위치에 상단 고정 되야 하는 경우가 생겼다.


물론 홀더를 나누고 개발하면 쉽게 되겠지만, 이부분이 좌우 스크롤 되야 하는 레이아웃이라 스크롤 싱크 문제등의 또 다른 문제가 발생 하였다. 그래서 하나의 홀더로 개발하고 아이템의 원하는 위치에 스크롤이 도달 했을 경우를 체크 하는 방법을 찾아 보았다.



설명을 위해 미션에 대해서 예를 들어보자.


0번 포지션 - 높이 70dp 이미지

1번 포지션 - 높이 50dp 이미지+ 12sp 텍스트 

2번 포지션 이후 50dp 아이템


이렇게 리스트가 구성되어 있고 1번 포지션에 텍스트 영역 리스트 상단에 도달 했을 때는 체크 해야 한다.




첫번째 시도 


 onScrolled 에서 원하는 아이템에 도달했을때의 y 값을 저장하고 저장된 y 값보다 50dp 가 더 이동되었을때는 체크해보았다.


천천히 스크롤 해보면서 동작하는것처럼 보였으나, 빠르게 스크롤 하면 onScrolled에서 y 값의 저장 시점이 변하면서 원하는 포지션을 완벽하게 구할 수가 없었다.



두번째 시도


 구글링 해보니 많은 stackoverflow, 블로그 등에서 computeVerticalScrollOffset() 메서드를 사용해서 RecycerView에서 지나간 영역을 체크 하도록 가이드 하고 있었다.


그래서 첫번째 시도와 같이 onScrolled() 에서 computeVerticalScrollOffset() 체크 하면서 원하는 위치를 찾도록 구현 하였다.

구현하고 테스트 했을 때 원하는대로 동작 하였다. 하지만 QA시 개발테스트한 단말이 아닌 다른 해상도 단말에서 오동작을 하고 있었다. 

 디버깅을 해보니 computeVerticalScrollOffset()  메서드로 가져오는 값이 스크롤시 일정하게 변경될거라고 생각했었는데, 그렇지 않았다. 예를들어 스크롤 하면서 computeVerticalScrollOffset()  값이 1, 3, 5 ,7, 9... 같은 형식으로 변경될줄 알았으나... 실제로 로그로 확인해 본 값은 1, 3, 5, 19, 21... 같이 중간에 갑자기 값이 튀는 경우가 있었다.


ReyclerView의 홀더들이 같은 크기라면 computeVerticalScrollOffset() 으로 값을 구하면 된다. 



세번째 시도 - 진짜 최종, 마지막, final!!


 RecyclerView의 동작을 다시 이해해 보자. RecyclerView는 보여지는 영역의 홀더 +@ 만 보여주고 지나간 홀더들은 사라지고 재사용 되게 된다.  computeVerticalScrollOffset() 은 RecyclerView 에서 보여지고 있는 Holder들의 크기의 평균 * position 으로 계산되게 된다. 예를 들어 1번, 2번이 높이 100px 이고, 3번부터는 10px인 Holder를 가지는 RecyclerView 가 있으면 1,2 번은 이전에 지나갔고 현재 10번 Holder에 있을때 높이를 구하게 되면 280px 이 되어야 하지만 computeVerticalScrollOffset() 으로는 100px 이 나오게 된다.

 

 해결 방법은 LayoutManager를 상속 받아서 포지션과 높이를 저장할 map을 멤버로 생성하고 onLayoutCompleted 에서 만들어지는 item들의 포지션과 높이를 map에 저장한다.

 그리고 map에서 현재 위치까지의 높이값들을 계산하는 함수를 만들면 된다.

private SparseIntArray childSizeMap = new SparseIntArray();
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
childSizeMap.put(getPosition(child), child.getHeight());
}
}

public int getOffset() {
if (getChildCount() == 0) {
return 0;
}
int firstChildPosition = findFirstVisibleItemPosition();
View firstChild = findViewByPosition(firstChildPosition);

int loopCount = firstChildPosition;
int scrolledY = (int) -firstChild.getY();

for (int i = 0; i < loopCount; i++) {
scrolledY += childSizeMap.get(i, 0);
}
return scrolledY;
}



(예전에 작업했던 부분이라 예제 코드가 java로 되어있습니다. 글을 작성하다가 마무리 정리를 못하고 1년째 방치하다가 이제 마무리 합니다.)


참고 링크


https://medium.com/@rituel521/improving-accuracy-of-computeverticalscrolloffset-for-linearlayoutmanager-38699a9d03b


http://gogorchg.tistory.com/entry/Android-RecyclerView-%EC%97%90%EC%84%9C-Scroll-%EC%A0%95%EB%B3%B4-%ED%98%95%ED%83%9C


https://stackoverflow.com/questions/30361403/recyclerview-linearlayoutmanager-computeverticalscrolloffset-not-returning-cor/50925862#50925862

반응형