Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- ListAdapter DiffUtil
- 스피너
- ListAdapter
- doc2vec
- exoplayer cache
- list map
- ktor client
- ExoPlayer
- build with ai
- android
- 시행착오
- 유튜브
- llm
- android custom view
- ktor api call
- android ktor
- DiffUtil.ItemCallback
- kotlin list
- Zsh
- AWS EC2
- ChatGPT
- 독서
- FastAPI
- kotlin collection
- getChangePayload
- android exoplayer
- video caching
- Python
- 안드로이드
- map
Archives
- Today
- Total
버튼 수집상
[안드로이드] 스크롤 상단에 margin 적용한 sticky view 만들기 본문
배경
이전에 sticky view 를 구현할 땐
LinearLayout.firstVisibleItemPosition 을 가지고 sticky를 띄울지 말지 판단했었다.
// 예시
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lm = recyclerView.layoutManager as LinearLayoutManager
val firstVisiblePosition = lm.findFirstVisibleItemPosition()
stickyView.root.visibility = if (firstVisiblePosition >= stickyIndex) View.VISIBLE else View.GONE
}
})
그런데 이번엔 sticky 위에 헤더가 있었다.
firstVisibleItemPosition은 index 값이기 때문에
헤더 높이만큼 margin을 주기 위해선 스크롤 offset 값을 알아야했다.
과정1
RecyclerView.computeVerticalScrollOffset() 함수는
화면에서 첫번째로 보이는 뷰홀더의 top에서부터
리사이클러뷰 최상단 사이의 offset 값을 리턴한다.
그러나 스크롤을 내리면서 뷰홀더들이 onBind되거나 disappeared 될 때마다 영향을 받는다.
스크롤을 내릴 때 offset 누적값을 구하는 레이아웃 매니저를 만들었다.
class CustomLayoutManager @JvmOverloads constructor(
context: Context,
orientation: Int,
reverseLayout: Boolean
) : LinearLayoutManager(context, orientation, reverseLayout) {
private var currentScrollOffset = 0
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
val scrolled = super.scrollVerticallyBy(dy, recycler, state)
currentScrollOffset += scrolled
return scrolled
}
fun computeVerticalScrollOffset() = currentScrollOffset
}
(혹시 다른 함수를 아신다면 댓글로 공유 부탁드립니다)
만든 레이아웃 매니저를 리사이클러뷰에 등록한다.
//...
recyclerView.adapter = mAdapter
recyclerView.layoutManager = CustomLayoutManager(this@MainActivity, VERTICAL, false)
과정2
이제 sticky view에 도달할 때까지 보이는 뷰홀더들의 높이의 총합을 구한다.
목표는 scrollListener 안에서 for문을 돌리지 않는 것.
// sticky view 도달전까지 뷰홀더 높이값들을 담을 Int 리스트
val heightList : MutableList<Int> = List(stickyIndex){ 0 }.toMutableList()
val stickyScrollListener = {
val lm = list.layoutManager as CustomLayoutManager
val currentIndex = lm.findFirstVisibleItemPosition()
val currentViewHolderHeight = lm.findViewByPosition(currentIndex)?.height ?: 0
// 뷰홀더가 처음 그려졌을 때 한 번만 리스트에 높이값 저장
if (currentIndex < stickyIndex && heightList[currentIndex] == 0) {
heightList[currentIndex] = currentViewHolderHeight
}
// sticky가 뜨는 지점 = sticky 도달전까지 뷰홀더 높이 총합 - 헤더 높이
val totalHeight = heightList.sum() - header.height
if (heightList.all { it != 0 } && lm.computeVerticalScrollOffset() >= totalHeight) {
sticky.root.visibility = View.VISIBLE
} else {
sticky.root.visibility = View.GONE
}
}
//...
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (shouldShowSticky) stickyScrollListener()
}
})
추가
sticky view를 붙일 때 계산된 마진값을 줄 게 아니라
header 뷰 하단에 앵커링되게 뷰를 붙일 수 있지 않을까..
728x90
'TIL - 안드로이드' 카테고리의 다른 글
[안드로이드] 싱글액티비티 구조를 언제 쓰면 좋을까? (0) | 2023.05.23 |
---|---|
[안드로이드] onBindViewHolder()가 호출될 때마다 뷰홀더 내부 가로 스크롤이 리셋되는 이슈 (0) | 2023.05.17 |
[안드로이드] 스피너 커스텀UI 만들기 (두 줄 리스트 스피너) (0) | 2023.05.02 |
[안드로이드] 가변적인 json 키를 동일한 클래스로 파싱하기 (0) | 2023.04.18 |
[안드로이드] Invisible된 액티비티가 onStop을 타지 않는 이슈 (0) | 2023.02.22 |