Last active
April 22, 2025 22:37
-
-
Save kelvinc1024/b1419de9ad6bbcdbb68bad72ff58e069 to your computer and use it in GitHub Desktop.
Nested Scrolling Layout
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.support.annotation.Nullable; | |
import android.support.design.widget.CoordinatorLayout; | |
import android.support.v4.view.NestedScrollingChild2; | |
import android.support.v4.view.NestedScrollingChildHelper; | |
import android.support.v4.view.ViewCompat; | |
import android.util.AttributeSet; | |
import android.view.View; | |
public class NestedScrollCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild2 { | |
private NestedScrollingChildHelper mChildHelper; | |
public NestedScrollCoordinatorLayout(Context context) { | |
super(context); | |
mChildHelper = new NestedScrollingChildHelper(this); | |
setNestedScrollingEnabled(true); | |
} | |
public NestedScrollCoordinatorLayout(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
mChildHelper = new NestedScrollingChildHelper(this); | |
setNestedScrollingEnabled(true); | |
} | |
public NestedScrollCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
mChildHelper = new NestedScrollingChildHelper(this); | |
setNestedScrollingEnabled(true); | |
} | |
@Override | |
public boolean isNestedScrollingEnabled() { | |
return mChildHelper.isNestedScrollingEnabled(); | |
} | |
@Override | |
public void setNestedScrollingEnabled(boolean enabled) { | |
mChildHelper.setNestedScrollingEnabled(enabled); | |
} | |
@Override | |
public boolean hasNestedScrollingParent() { | |
return mChildHelper.hasNestedScrollingParent(); | |
} | |
@Override | |
public boolean hasNestedScrollingParent(int type) { | |
return mChildHelper.hasNestedScrollingParent(type); | |
} | |
@Override | |
public boolean onStartNestedScroll(View child, View target, int axes, int type) { | |
boolean superResult = super.onStartNestedScroll(child, target, axes, type); | |
return startNestedScroll(axes, type) || superResult; | |
} | |
@Override | |
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { | |
boolean superResult = super.onStartNestedScroll(child, target, nestedScrollAxes); | |
return startNestedScroll(nestedScrollAxes) || superResult; | |
} | |
@Override | |
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) { | |
dispatchNestedPreScroll(dx, dy, consumed, null, type); | |
if (consumed[1] == 0) { | |
super.onNestedPreScroll(target, dx, dy, consumed, type); | |
} | |
} | |
@Override | |
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { | |
dispatchNestedPreScroll(dx, dy, consumed, null); | |
if (consumed[1] == 0) { | |
super.onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); | |
} | |
} | |
@Override | |
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { | |
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type); | |
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, type); | |
} | |
@Override | |
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { | |
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); | |
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); | |
} | |
@Override | |
public void onStopNestedScroll(View target, int type) { | |
super.onStopNestedScroll(target, type); | |
stopNestedScroll(type); | |
} | |
@Override | |
public void onStopNestedScroll(View target) { | |
super.onStopNestedScroll(target); | |
stopNestedScroll(); | |
} | |
@Override | |
public boolean onNestedPreFling(View target, float velocityX, float velocityY) { | |
boolean superResult = super.onNestedPreFling(target, velocityX, velocityY); | |
return dispatchNestedPreFling(velocityX, velocityY) || superResult; | |
} | |
@Override | |
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { | |
boolean superResult = super.onNestedFling(target, velocityX, velocityY, consumed); | |
return dispatchNestedFling(velocityX, velocityY, consumed) || superResult; | |
} | |
@Override | |
public boolean startNestedScroll(int axes, int type) { | |
return mChildHelper.startNestedScroll(axes, type); | |
} | |
@Override | |
public boolean startNestedScroll(int axes) { | |
return mChildHelper.startNestedScroll(axes); | |
} | |
@Override | |
public void stopNestedScroll() { | |
mChildHelper.stopNestedScroll(); | |
} | |
@Override | |
public void stopNestedScroll(int type) { | |
mChildHelper.stopNestedScroll(type); | |
} | |
@Override | |
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) { | |
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type); | |
} | |
@Override | |
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) { | |
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); | |
} | |
@Override | |
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) { | |
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH); | |
} | |
@Override | |
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) { | |
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); | |
} | |
@Override | |
public boolean dispatchNestedPreFling(float velocityX, float velocityY) { | |
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); | |
} | |
@Override | |
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { | |
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); | |
} | |
} |
Doesn't work when user scroll up. You have to implement NestedScrollingChild3 and method dispatchNestedScroll. It will work. Add this:
override fun onNestedScroll( target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, consumed: IntArray ) { super.onNestedScroll( target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed ) dispatchNestedScroll( dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, type, consumed ) }
override fun dispatchNestedScroll( dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?, type: Int, consumed: IntArray ) { mChildHelper.dispatchNestedScroll( dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed ) }
Thx! It was really helpful :)
This solution works, but based on my experience, it has some subtle nested scrolling bugs when there is more than one level of nesting. I spent a few days fixing it, and this seems to be the most reliable version:
class NestedScrollCoordinatorLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : CoordinatorLayout(context, attrs, defStyleAttr), NestedScrollingChild3 {
private val helper = NestedScrollingChildHelper(this)
init {
isNestedScrollingEnabled = true
}
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
val superResult = super.onStartNestedScroll(child, target, axes, type)
return startNestedScroll(axes, type) || superResult
}
override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean {
val superResult = super.onStartNestedScroll(child, target, axes)
return startNestedScroll(axes) || superResult
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
val parentConsumed = intArrayOf(0, 0)
val thisConsumed = intArrayOf(0, 0)
// Dispatch the pre-scroll to the scrolling parent for it to consume first
dispatchNestedPreScroll(dx, dy, parentConsumed, null, type)
// Then let this view consume the rest
super.onNestedPreScroll(
target,
dx - parentConsumed[0],
dy - parentConsumed[1],
thisConsumed,
type,
)
// The final total consumed must take into account both the parent and this view's
// consumed
consumed[0] += thisConsumed[0] + parentConsumed[0]
consumed[1] += thisConsumed[1] + parentConsumed[1]
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
val parentConsumed = intArrayOf(0, 0)
val thisConsumed = intArrayOf(0, 0)
// Dispatch the pre-scroll to the scrolling parent for it to consume first
dispatchNestedPreScroll(dx, dy, parentConsumed, null)
// Then let this view consume the rest
super.onNestedPreScroll(
target,
dx - parentConsumed[0],
dy - parentConsumed[1],
thisConsumed,
)
// The final total consumed must take into account both the parent and this view's
// consumed
consumed[0] += thisConsumed[0] + parentConsumed[0]
consumed[1] += thisConsumed[1] + parentConsumed[1]
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray,
) {
val parentConsumed = intArrayOf(0, 0)
val thisConsumed = intArrayOf(0, 0)
// Dispatch the newsted-scroll to the scrolling parent for it to consume first
dispatchNestedScroll(
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
null,
type,
thisConsumed,
)
// Then let this view consume the rest
super.onNestedScroll(
target,
dxConsumed + thisConsumed[0],
dyConsumed + thisConsumed[1],
dxUnconsumed - thisConsumed[0],
dyUnconsumed - thisConsumed[1],
type,
parentConsumed,
)
// The final total consumed must take into account both the parent and this view's
// consumed
consumed[0] += thisConsumed[0] + parentConsumed[0]
consumed[1] += thisConsumed[1] + parentConsumed[1]
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
) {
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, type)
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
) {
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null)
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
}
override fun onStopNestedScroll(target: View, type: Int) {
super.onStopNestedScroll(target, type)
stopNestedScroll(type)
}
override fun onStopNestedScroll(target: View) {
super.onStopNestedScroll(target)
stopNestedScroll()
}
override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
val superResult = super.onNestedPreFling(target, velocityX, velocityY)
return dispatchNestedPreFling(velocityX, velocityY) || superResult
}
override fun onNestedFling(
target: View,
velocityX: Float,
velocityY: Float,
consumed: Boolean,
): Boolean {
val superResult = super.onNestedFling(target, velocityX, velocityY, consumed)
return dispatchNestedFling(velocityX, velocityY, consumed) || superResult
}
override fun isNestedScrollingEnabled(): Boolean = helper.isNestedScrollingEnabled
override fun setNestedScrollingEnabled(enabled: Boolean) {
helper.isNestedScrollingEnabled = enabled
}
override fun hasNestedScrollingParent(type: Int): Boolean =
helper.hasNestedScrollingParent(type)
override fun hasNestedScrollingParent(): Boolean = helper.hasNestedScrollingParent()
override fun startNestedScroll(axes: Int, type: Int): Boolean =
helper.startNestedScroll(axes, type)
override fun startNestedScroll(axes: Int): Boolean = helper.startNestedScroll(axes)
override fun stopNestedScroll(type: Int) {
helper.stopNestedScroll(type)
}
override fun stopNestedScroll() {
helper.stopNestedScroll()
}
override fun dispatchNestedScroll(
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
offsetInWindow: IntArray?,
type: Int,
consumed: IntArray,
) {
helper.dispatchNestedScroll(
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
offsetInWindow,
type,
consumed,
)
}
override fun dispatchNestedScroll(
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
offsetInWindow: IntArray?,
type: Int,
): Boolean = helper.dispatchNestedScroll(
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
offsetInWindow,
type,
)
override fun dispatchNestedScroll(
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
offsetInWindow: IntArray?,
): Boolean = helper.dispatchNestedScroll(
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
offsetInWindow,
)
override fun dispatchNestedPreScroll(
dx: Int,
dy: Int,
consumed: IntArray?,
offsetInWindow: IntArray?,
type: Int,
): Boolean = helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
override fun dispatchNestedPreScroll(
dx: Int,
dy: Int,
consumed: IntArray?,
offsetInWindow: IntArray?,
): Boolean =
helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean =
helper.dispatchNestedPreFling(velocityX, velocityY)
override fun dispatchNestedFling(
velocityX: Float,
velocityY: Float,
consumed: Boolean,
): Boolean =
helper.dispatchNestedFling(velocityX, velocityY, consumed)
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Doesn't work when user scroll up. You have to implement NestedScrollingChild3 and method dispatchNestedScroll. It will work.
Add this: