web-dev-qa-db-ja.com

Androidレイアウト:スクロール動作を備えたビューページャー内の垂直Recyclerview内の水平Recyclerview

これは、以下にすべての要素をマッピングして構築しようとしているアプリです。

Veggies app

すべてが機能しますが、内側の水平リサイクラービューで垂直スクロールをキャプチャしないようにします。すべての垂直スクロールは、水平スクロールではなく、外側の垂直リサイクラビューに移動する必要があります。これにより、垂直スクロールにより、scrollFlagに応じてツールバーが非表示になります。

Recyclerviewの「StrawBerry Plant」部分に指を置いて上にスクロールすると、ツールバーがスクロールアウトします。

strawberry plant

水平スクロールビューに指を置いて上にスクロールしても、ツールバーはまったくスクロールアウトされません。

これまでの私のxmlレイアウトコードは次のとおりです。

Activityxmlレイアウト:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:id="@+id/fragment_container"
    Android:clipChildren="false">

    <Android.support.design.widget.CoordinatorLayout
        Android:orientation="vertical"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:id="@+id/container"
        >

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appBarLayout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:minHeight="?attr/actionBarSize"
            Android:background="?attr/colorPrimary"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlways">

        </Android.support.v7.widget.Toolbar>

        <Android.support.design.widget.TabLayout
            Android:id="@+id/sliding_tabs"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:background="?attr/colorPrimary"
            style="@style/CustomTabLayout"
            />

    </Android.support.design.widget.AppBarLayout>
    <Android.support.v4.view.ViewPager
        Android:id="@+id/viewPager"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />

    </Android.support.design.widget.CoordinatorLayout>

</FrameLayout>

"Fruits" fragmentxmlレイアウト(フラグメントのコードです-フラグメントは上の図でラベル付けされています):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:orientation="vertical"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <ProgressBar
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:id="@+id/progressBar"
        Android:visibility="gone"
        Android:layout_centerInParent="true"
        Android:indeterminate="true"/>

<!--    <Android.support.v7.widget.RecyclerView-->
    <com.example.simon.customshapes.VerticallyScrollRecyclerView
        Android:id="@+id/main_recyclerview"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        />

</RelativeLayout>

ビューグループでタッチイベントを処理するgoogleの例に続くVerticallyScrollRecyclerViewというカスタムクラスを使用しました。その目的は、すべての垂直スクロールイベントをインターセプトして消費し、ツールバーをスクロールイン/アウトすることです。 http://developer.Android.com/training/gestures/viewgroup.html

VerticallyScrollRecyclerViewのコードは次のとおりです。

public class VerticallyScrollRecyclerView extends RecyclerView {

    public VerticallyScrollRecyclerView(Context context) {
        super(context);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    ViewConfiguration vc = ViewConfiguration.get(this.getContext());
    private int mTouchSlop = vc.getScaledTouchSlop();
    private boolean mIsScrolling;
    private float startY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mIsScrolling = false;
            startY = ev.getY();
            return super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                Log.e("VRecView", "its moving");
                if (mIsScrolling) {
                    // We're currently scrolling, so yes, intercept the
                    // touch event!
                    return true;
                }
                // If the user has dragged her finger horizontally more than
                // the touch slop, start the scroll
                // left as an exercise for the reader
                final float yDiff = calculateDistanceY(ev.getY());
                Log.e("yDiff ", ""+yDiff);
                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (Math.abs(yDiff) > 5) {
                    // Start scrolling!
                    Log.e("Scroll", "we are scrolling vertically");
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }


    private float calculateDistanceY(float endY) {
        return startY - endY;
    }

}

"Favourite" layoutは、垂直recyclerview内のrecyclerviewです。

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:background="@color/white"
    xmlns:app="http://schemas.Android.com/apk/res-auto">

    <TextView
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:text="Favourite"
        Android:layout_marginTop="8dp"
        Android:layout_marginBottom="8dp"
        Android:layout_marginLeft="16dp"
        Android:id="@+id/header_fav"/>

    <Android.support.v7.widget.RecyclerView
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        Android:layout_below="@+id/header_fav"
        Android:id="@+id/recyclerview_fav">
    </Android.support.v7.widget.RecyclerView>

</RelativeLayout>

これはしばらくの間私を悩ませており、私は解決策を思い付くことができませんでした。誰もこの問題を解決する方法を知っていますか?

正しい答えを得るためにGriffindorに5ポイント、そしてもちろんSOの評判ポイント。

57
Simon

テスト済みソリューション:

必要なのは、内部のRecyclerViewsでmInnerRecycler.setNestedScrollingEnabled(false);を呼び出すことだけです


説明

RecyclerViewは、NestedScrollingChildインターフェイスを実装することにより、API 21で導入されたネストされたスクロールをサポートしています。これは、同じ方向にスクロールする別のビュー内にスクロールビューがあり、フォーカスされているときにのみ内側のViewをスクロールする場合に役立つ機能です。

いずれの場合でも、RecyclerViewはデフォルトで、初期化時にそれ自体でRecyclerView.setNestedScrollingEnabled(true);を呼び出します。さて、問題に戻ります。両方のRecyclerViewsがViewPagerを持つ同じAppBarBehavior内にあるため、CoordinateLayoutは、内側のRecyclerViewからスクロールするときに、どのスクロールに応答するかを決定する必要があります。内側のRecyclerViewのネストされたスクロールが有効になっている場合、スクロールフォーカスを取得し、CoordinateLayoutは外側のRecyclerViewのスクロールを介したスクロールに応答することを選択します。つまり、内部のRecyclerViewsは垂直にスクロールしないため、(CoordinateLayoutの観点から)垂直スクロールの変更はなく、変更がなければAppBarLayoutも変更されません。

あなたの場合、内側のRecyclerViewsは異なる方向にスクロールしているため、無効にすることができます。したがって、CoordinateLayoutはそのスクロールを無視し、外側のRecyclerViewのスクロールに応答します。


注意

Xml属性Android:nestedScrollingEnabled="boolean"RecyclerViewとともに使用することを目的としておらず、Android:nestedScrollingEnabled="false"を使用しようとするとJava.lang.NullPointerExceptionになります。したがって、少なくとも今のところは、コード。

103
Ari

私は少し遅れていますが、これは同じ問題に直面している他の人にとって間違いなく機能します

 mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                int action = e.getAction();
               // Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
                switch (action) {
                    case MotionEvent.ACTION_POINTER_UP:
                        rv.getParent().requestDisallowInterceptTouchEvent(true);

                        break;
                }
                return false;
            }
2
Shahid Sarwar

まだ見ている人がいれば、これを試してください:

private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f

mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
        override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {

            }

        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
                when (e.action) {
                    MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
                    MotionEvent.ACTION_MOVE -> {
                        if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
                            rv.parent.requestDisallowInterceptTouchEvent(true)
                        } else if (Math.abs(e.y - preY) > Y_BUFFER) {
                            rv.parent.requestDisallowInterceptTouchEvent(false)
                        }

                    }
                }
                preX = e.x
                preY = e.y
                return false
            }

            override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
            }
        })

現在水平にスクロールしているかどうかを確認し、親がイベントを処理できないようにします

2
Ahmed na

テスト済みのソリューション、カスタムNestedScrollView()を使用します。

コード:

public class CustomNestedScrollView extends NestedScrollView {
    public CustomNestedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
         // Explicitly call computeScroll() to make the Scroller compute itself
            computeScroll();
        }
        return super.onInterceptTouchEvent(ev);
    }
}
2
Anuj

試してみる

public OuterRecyclerViewAdapter(List<Item> items) {
    //Constructor stuff
    viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //Create viewHolder etc
    holder.innerRecyclerView.setRecycledViewPool(viewPool);

}

内側のrecylerviewは同じビュープールを使用し、よりスムーズになります

1
Abhilash Das

Googleアプリのようなフラグメント内に水平リサイクラービューを追加することをお勧めします

0
Daniel Nyamasyo