중첩 스크롤 가능 요소 지원 ViewPager2는 스크롤 보기가 포함된 ViewPager2 개체와 방향이 같은 경우 중첩된 스크롤 보기를 기본적으로 지원하지 않습니다. 예를 들어, 수직 방향 ViewPager2 객체 내부의 수직 스크롤 보기에서는 스크롤이 작동하지 않습니다. 동일한 방향의 ViewPager2 개체 내에서 스크롤 보기를 지원하려면 중첩된 요소를 스크롤해야 하는 경우 ViewPager2 개체에서 requestDisallowInterceptTouchEvent()를 대신 호출해야 합니다. ViewPager2 중첩 스크롤 샘플은 다양한 사용자 정의 래퍼 레이아웃으로 이 문제를 해결하는 한 가지 방법을 보여줍니다.
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
*/classNestedScrollableHost : FrameLayout {constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
privatevar touchSlop = 0privatevar initialX = 0fprivatevar initialY = 0fprivateval parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && v !is ViewPager2) {
v = v.parent as? View
}
return v as? ViewPager2
}
privateval child: View? get() = if (childCount > 0) getChildAt(0) elsenullinit {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
privatefuncanChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
returnwhen (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false1 -> child?.canScrollVertically(direction) ?: falseelse -> throw IllegalArgumentException()
}
}
overridefunonInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
returnsuper.onInterceptTouchEvent(e)
}
privatefunhandleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return// Early return if child can't scroll in same direction as parentif (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} elseif (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of childval scaledDx = dx.absoluteValue * if (isVpHorizontal) .5felse1fval scaledDy = dy.absoluteValue * if (isVpHorizontal) 1felse.5fif (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possibleif (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
사용방법은 위 코드를 그대로 복사해, 클래스를 만들어 붙여 넣는다.
ScrollView(RecyclerView)가 있는 xml로 가서 해당 뷰 위에 <NestedScrollableHost>로 감싸준다.
ViewPager -> NestedScrollableHost -> ScrollView(RecyclerView) 순으로 자식이어야 한다.