I have a VerticalViewPager
where each child fragment contain something like
<MyScrollView>
<LinearLayout orientation=vertical>
...
...
</LinearLayout>
</MyScrollView>
When MyScrollView
is scrolled to the bottom, I'd like the VerticalViewPager
to kick in and do its magic. Likewise if I scroll the MyScrollView
to the very top.
I've experimented a bunch with intercepting the touch events in the ScrollView
to somehow dispatch the MotionEvent
to the VerticalViewPager
without any good luck.
This obviously doesn't work, but to get the conversation going I currently do something like the following in MyScrollView
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return super.onInterceptTouchEvent(ev) && atBottom
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
atBottom = getChildAt(0).bottom <= (height + scrollY)
super.onScrollChanged(l, t, oldl, oldt)
}
This works shaky and untrustworthy and not really at all. Any input is greatly appreciated!
Solution is probably very similar to this one over here:
(original answer is for webview, but logic is the same)
Scroll webview horizontally inside a ViewPager
I think your idea to have the ScrollView dictate when the ViewPager should kick in is pretty good, it's already using requestDisallowInterceptTouchEvent
to prevent the ViewPager from intercepting the scroll. What you need is use this same method to allow the intercept in the circumstances you've indicated, which is when the ScrollView has reached the top or bottom.
So if the ScrollView handles an ACTION_MOVE
touch event but the resulting scroll offset hasn't changed we can assume that we've scrolled past the top or bottom (and are now overscrolling). There may be a better way to detect overscroll so have an experiment with this as a starting point:
//MyScrollView.java
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = super.onTouchEvent(ev);
if (handled && ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
int y = getScrollY();
boolean moved = y != prevY;
prevY = y;
// This is the important part to allow the parent to intercept
if (!moved) {
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(false);
}
}
// Also important to re
return moved;
}
return handled;
}
EDIT - I had a quick play around and think this works nicely
public class MyScrollView extends ScrollView {
private boolean clampedY;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = super.onTouchEvent(ev);
if (handled) {
ViewParent parent = getParent();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
if (clampedY && parent != null) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_DOWN:
clampedY = false;
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
break;
}
}
return handled;
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
this.clampedY = clampedY;
}
}
EDIT 2 - This will keep ownership of the touch if there has been any scroll, so a separate touch is required to switch page
public class MyScrollView extends ScrollView {
private boolean scrolled;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = super.onTouchEvent(ev);
if (handled) {
ViewParent parent = getParent();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
if (!scrolled && parent != null) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_DOWN:
scrolled = false;
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
break;
}
}
return handled;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (t != oldt) {
scrolled = true;
}
}
}