问题
I implemented the new ViewPager for my project. The viewPager2 contains a list of fragment
private class ViewPagerAdapter extends FragmentStateAdapter {
private ArrayList<Integer> classifiedIds;
ViewPagerAdapter(@NonNull Fragment fragment, final ArrayList<Integer> classifiedIds) {
super(fragment);
this.classifiedIds = classifiedIds;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return DetailsFragment.newInstance(classifiedIds.get(position));
}
@Override
public int getItemCount() {
return classifiedIds.size();
}
}
Inside the fragment I got an horizontal recyclerView
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
recyclerViewPicture.setLayoutManager(layoutManager);
The issue is when I try to scroll the recyclerview the viewPager take the touch and swap to the next fragment
When I was using the old ViewPager I didn't have this issue
回答1:
I find a solution it's a know bug as you can see here https://issuetracker.google.com/issues/123006042 maybe they would solve it in the next updates
Thanks to TakeInfos and the exemple project inside the link
recyclerViewPicture.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
int lastX = 0;
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) e.getX();
break;
case MotionEvent.ACTION_MOVE:
boolean isScrollingRight = e.getX() < lastX;
if ((isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findLastCompletelyVisibleItemPosition() == recyclerViewPicture.getAdapter().getItemCount() - 1) ||
(!isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
viewPager.setUserInputEnabled(true);
} else {
viewPager.setUserInputEnabled(false);
}
break;
case MotionEvent.ACTION_UP:
lastX = 0;
viewPager.setUserInputEnabled(true);
break;
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
I'm checking if the user scroll on the right or on the left. If the user reach the end or the start of the recyclerView I'm enable or disable the swipe on the view pager
回答2:
I met the same problem: using AndroidX, a ViewPager2 (with horizontal orientation) having a RecyclerView (with horizontal orientation) inside one of its page.
The working solution I found is from Google issueTracker. Here is my Java translation of the Kotlin class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
// from https://issuetracker.google.com/issues/123006042#comment21
/**
* 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).
*/
public class NestedScrollableHost extends FrameLayout {
private int touchSlop = 0;
private float initialX = 0.0f;
private float initialY = 0.0f;
private ViewPager2 parentViewPager() {
View v = (View)this.getParent();
while( v != null && !(v instanceof ViewPager2) )
v = (View)v.getParent();
return (ViewPager2)v;
}
private View child() { return (this.getChildCount() > 0 ? this.getChildAt(0) : null); }
private void init() {
this.touchSlop = ViewConfiguration.get(this.getContext()).getScaledTouchSlop();
}
public NestedScrollableHost(@NonNull Context context) {
super(context);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.init();
}
private boolean canChildScroll(int orientation, Float delta) {
int direction = (int)(Math.signum(-delta));
View child = this.child();
if( child == null )
return false;
if( orientation == 0 )
return child.canScrollHorizontally(direction);
if( orientation == 1 )
return child.canScrollVertically(direction);
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
this.handleInterceptTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
private void handleInterceptTouchEvent(MotionEvent ev) {
ViewPager2 vp = this.parentViewPager();
if( vp == null )
return;
int orientation = vp.getOrientation();
// Early return if child can't scroll in same direction as parent
if( !this.canChildScroll(orientation, -1.0f) && !this.canChildScroll(orientation, 1.0f) )
return;
if( ev.getAction() == MotionEvent.ACTION_DOWN ) {
this.initialX = ev.getX();
this.initialY = ev.getY();
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else if( ev.getAction() == MotionEvent.ACTION_MOVE ) {
float dx = ev.getX() - this.initialX;
float dy = ev.getY() - this.initialY;
boolean isVpHorizontal = (orientation == ViewPager2.ORIENTATION_HORIZONTAL);
// assuming ViewPager2 touch-slop is 2x touch-slop of child
float scaleDx = Math.abs(dx) * (isVpHorizontal ? 0.5f : 1.0f);
float scaleDy = Math.abs(dy) * (isVpHorizontal ? 1.0f : 0.5f);
if( scaleDx > this.touchSlop || scaleDy > this.touchSlop ) {
if( isVpHorizontal == (scaleDy > scaleDx) ) {
// Gesture is perpendicular, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
else {
// Gesture is parallel, query child if movement in that direction is possible
if( this.canChildScroll(orientation, (isVpHorizontal ? dx : dy)) ) {
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else {
// Child cannot scroll, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
}
}
}
}
}
Then, just embed your nested RecyclerView inside a NestedScrollableHost container:
<mywishlist.sdk.Base.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/photolist_collection_background"
android:orientation="horizontal">
</androidx.recyclerview.widget.RecyclerView>
</mywishlist.sdk.Base.NestedScrollableHost>
It solved my scrolling conflict between the nested RecyclerView and its hosting ViewPager2.
回答3:
Call ViewGroup#onInterceptTouchEvent(MotionEvent).
See This Documentation
来源:https://stackoverflow.com/questions/57587618/viewpager2-with-horizontal-scrollview-inside