Android 최상단에서만 SwipeRefresh 동작시키기 – RecyclerView/NestedScrollView/WebView 한 번에 정리

✨ 개요

당겨서 새로고침(Swipe to Refresh)은 자연스러운 UX지만, 스크롤 중에도 오작동하면 좋지 않습니다.
이 글에서는 “스크롤이 최상단일 때만 새로고침”이 동작하도록 만드는 실전 패턴을 뷰 타입별로 정리합니다.

핵심: SwipeRefreshLayout의 canChildScrollUp을 올바르게 판단시키면 됨
최신 API: setOnChildScrollUpCallback { … }
레거시: SwipeRefreshLayout 상속 → canChildScrollUp() 오버라이ㄷㅡ


1. 최신 권장: setOnChildScrollUpCallback (AndroidX SwipeRefreshLayout 1.1+)

SwipeRefreshLayout자식이 위로 더 스크롤 가능한지를 직접 물어봅니다.
콜백에서 최상단 여부를 판단해 true/false를 반환하세요.

1-1. RecyclerView (가장 흔한 케이스)

binding.swipeRefresh.setOnChildScrollUpCallback { _, child ->
    val rv = child as? RecyclerView ?: return@setOnChildScrollUpCallback child.canScrollVertically(-1)
    // 첫 아이템이 0이고, 맨 위로 더 스크롤할 수 없으면 = 최상단
    rv.canScrollVertically(-1) // true면 '아직 위로 더 스크롤 가능' → 새로고침 막음
}

1-2. NestedScrollView

binding.swipeRefresh.setOnChildScrollUpCallback { _, child ->
    val nsv = child as? NestedScrollView ?: return@setOnChildScrollUpCallback child.canScrollVertically(-1)
    nsv.canScrollVertically(-1) // 최상단이면 false → 새로고침 허용
}

1-3. WebView

binding.swipeRefresh.setOnChildScrollUpCallback { _, child ->
    val web = child as? WebView ?: return@setOnChildScrollUpCallback child.canScrollVertically(-1)
    web.canScrollVertically(-1)
}

2. 레거시 호환: 커스텀 SwipeRefreshLayout

class TopOnlySwipeRefresh @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {

    var scrollableChild: View? = null

    override fun canChildScrollUp(): Boolean {
        val v = scrollableChild ?: getChildAt(0)
        return v?.canScrollVertically(-1) == true
    }
}

val swipe = findViewById<TopOnlySwipeRefresh>(R.id.swipe)
swipe.scrollableChild = binding.recyclerView // 또는 NestedScrollView/WebView

3. AppBarLayout/CollapsingToolbar와 함께 쓰는 경우

툴바가 아직 접히는 중(스크롤 shrink)이라면 새로고침이 끼어들 수 있습니다. AppBarLayout의 오프셋을 같이 체크해 완전히 펼쳐졌을 때만 허용하세요.

binding.swipeRefresh.setOnChildScrollUpCallback { _, child ->
    val atTop = !child.canScrollVertically(-1)
    val allow = appBarExpanded && atTop
    !allow.not() // allow가 true면 canChildScrollUp == false가 되어 새로고침 허용
}

4. 결론



Related Posts