I am using VerticalViewPager and two fragments in it.
One of this fragment contains RecyclerView with just regular vertical list.
The problem is that I need this list to get to end and then change page at view pager and now view pager always try to intercept RecyclerView scroll.
My VerticalViewPager class:
public class VerticalViewPager extends ViewPager {
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setPageTransformer(true, new VerticalPageTransformer());
setOverScrollMode(OVER_SCROLL_NEVER);
requestDisallowInterceptTouchEvent(false);
}
private class VerticalPageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(View view, float position) {
if (position < -1) {
view.setAlpha(0);
} else if (position <= 1) {
view.setAlpha(1);
view.setTranslationX(view.getWidth() * -position);
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else {
view.setAlpha(0);
}
}
}
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev);
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
}
And my layout tree:
- -Fragment
- --ViewPager
- ---Fragment_A
- ----RecyclerView
- ---Fragment_B
I already tried different combinations of
.setNestedScrollingEnabled(false/true);
.requestDisallowInterceptTouchEvent(false/true);
Tried to wrap RecyclerView with NestedScrollView, but scroll is still messy and laggy, ViewPager change its page at the list beginning or in the middle.
Thanks for your help!
I figure it with writing custom OnTouchListener for my RecyclerView:
public class VerticalTouchListener implements View.OnTouchListener {
private float lastMotionY = Float.MIN_VALUE;
private float downY = Float.MIN_VALUE;
private final OnScrollableViewPager scrollableViewPager;
public VerticalTouchListener(@NonNull OnScrollableViewPager scrollableViewPager) {
this.scrollableViewPager = scrollableViewPager;
}
@Override
public boolean onTouch(@NonNull View v, @NonNull MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (downY == Float.MIN_VALUE && lastMotionY == Float.MIN_VALUE) {
downY = event.getRawY();
break;
}
float diff = event.getRawY() - (lastMotionY == Float.MIN_VALUE ? downY : lastMotionY);
lastMotionY = event.getRawY();
if (scrollableViewPager.getScrollX() != scrollableViewPager.getBaseScrollX()) {
if (fakeDragVp(v, event, diff)) return true;
} else {
if (ViewCompat.canScrollVertically(v, (-diff) > 0 ? 1 : -1)) {
break;
} else {
scrollableViewPager.beginFakeDrag();
fakeDragVp(v, event, diff);
adjustDownMotion(v, event);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (scrollableViewPager.isFakeDragging()) {
try {
scrollableViewPager.endFakeDrag();
} catch (Exception e) {
e.printStackTrace();
}
}
reset();
break;
}
return false;
}
private boolean fakeDragVp(@NonNull View v, @NonNull MotionEvent e, float diff) {
if (scrollableViewPager.isFakeDragging()) {
float step = diff;
int expScrollX = (int) (scrollableViewPager.getScrollX() - step);
if (isDiffSign(expScrollX - scrollableViewPager.getBaseScrollX(), scrollableViewPager.getScrollX() - scrollableViewPager.getBaseScrollX())) {
step = scrollableViewPager.getScrollX() - scrollableViewPager.getBaseScrollX();
}
scrollableViewPager.fakeDragBy(step);
adjustDownMotion(v, e);
return true;
}
return false;
}
private void adjustDownMotion(@NonNull View v, @NonNull MotionEvent e) {
MotionEvent fakeDownEvent = MotionEvent.obtain(e);
fakeDownEvent.setAction(MotionEvent.ACTION_DOWN);
v.dispatchTouchEvent(fakeDownEvent);
}
private boolean isDiffSign(float a, float b) {
return Math.abs(a + b) < Math.abs(a - b);
}
private void reset() {
downY = Float.MIN_VALUE;
lastMotionY = Float.MIN_VALUE;
}
public interface OnScrollableViewPager {
int getScrollX();
int getBaseScrollX();
boolean isFakeDragging();
boolean beginFakeDrag();
void fakeDragBy(float step);
void endFakeDrag();
}
}
and changed my VerticalViewPager:
public class VerticalViewPager extends ViewPager implements VerticalTouchListener.OnScrollableViewPager {
private int baseScrollX;
public VerticalViewPager(@NonNull Context context) {
super(context);
init();
}
public VerticalViewPager(@NonNull Context context, @NonNull AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setPageTransformer(false, new VerticalTransformer());
setOverScrollMode(OVER_SCROLL_NEVER);
addOnPageChangeListener(new SimpleOnPageChangeListener() {
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
if (state == ViewPager.SCROLL_STATE_IDLE) {
baseScrollX = getScrollX();
}
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
@Override
public int getBaseScrollX() {
return baseScrollX;
}
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
private class VerticalTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View view, float position) {
float alpha = 0;
if (0 <= position && position <= 1) {
alpha = 1 - position;
} else if (-1 < position && position < 0) {
alpha = position + 1;
}
view.setAlpha(alpha);
view.setTranslationX(view.getWidth() * -position);
view.setTranslationY(position * view.getHeight());
}
}
}
This repo helped me a little bit.