ViewPager2 with horizontal scrollView inside

柔情痞子 提交于 2020-08-25 07:02:29

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!