wake-up-neo.com

Android-Layout: Horizontales Recycling in einem vertikalen Recycling in einem Viewpager mit Bildlaufverhalten

Dies ist die App, die ich mit allen folgenden Elementen erstellen möchte:

 Veggies app

Alles funktioniert, jedoch möchte ich, dass der innere horizontale Recycler keine der vertikalen Schriftrollen erfasst. Alle vertikalen Bildlaufleisten müssen in Richtung der äußeren vertikalen Recyclingansicht gehen, nicht in die horizontale, so dass die vertikale Bildlaufleiste die Symbolleiste entsprechend ihrem scrollFlag aus dem Sichtbereich verlassen kann.

Wenn ich den Finger auf den Bereich "StrawBerry Plant" der Recyclingübersicht lege und nach oben scrolle, scrolle er aus der Symbolleiste heraus:

 strawberry plant

Wenn ich den Finger auf die horizontale Bildlaufansicht lege und nach oben scrolle, wird die Symbolleiste überhaupt nicht angezeigt.

Das Folgende ist mein XML-Layout-Code bisher.

Das Activity XML-Layout:

<?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>

Das "Fruits" -Fragment XML-Layout (das ist der Code für das Fragment - das Fragment ist im obigen Bild gekennzeichnet):

<?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>

Ich habe eine benutzerdefinierte Klasse namens VerticallyScrollRecyclerView verwendet, die dem Beispiel von Google für die Handhabung von Berührungsereignissen in einer Ansichtsgruppe folgt. Ihr Ziel ist es, alle vertikalen Bildlaufereignisse abzufangen und zu konsumieren, damit sie in der Symbolleiste ein- und ausgeblendet werden: http://developer.Android.com/training/gestures/viewgroup.html

Der Code für VerticallyScrollRecyclerView befindet sich unten:

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;
    }

}

Das "Favorite" -Layout, das die Recyclingübersicht innerhalb der vertikalen Recyclingübersicht ist:

<?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>

Das nervt mich schon seit einiger Zeit und ich habe keine Lösung gefunden. Weiß jemand, wie man dieses Problem löst? 

5 Punkte an Griffindor für die richtige Antwort und natürlich Reputationspunkte für SO.

53
Simon

Getestete Lösung:

Alles, was Sie brauchen, ist, mInnerRecycler.setNestedScrollingEnabled(false); auf Ihrem inneren RecyclerViews aufzurufen 


Erklärung :

RecyclerView bietet Unterstützung für verschachteltes Scrollen in API 21 durch Implementierung der NestedScrollingChild-Schnittstelle. Dies ist eine wertvolle Funktion, wenn Sie eine Bildlaufansicht in einer anderen haben, die in dieselbe Richtung blättert und Sie das innere View nur bei Fokussierung scrollen möchten. 

In jedem Fall ruft RecyclerView standardmäßig RecyclerView.setNestedScrollingEnabled(true); bei der Initialisierung auf. Nun zurück zum Problem: Da sich beide RecyclerViews innerhalb desselben ViewPager befinden, das über das AppBarBehavior verfügt, muss das CoordinateLayout entscheiden, auf welche Bildlaufleiste Sie reagieren, wenn Sie von Ihrem inneren RecyclerView aus scrollen. Wenn der verschachtelte Bildlauf Ihres inneren RecyclerView aktiviert ist, wird der Bildlauffokus angezeigt, und der CoordinateLayout wählt, ob er auf den Bildlauf des äußeren RecyclerView-Bildlaufs reagiert. Die Sache ist, dass, da Ihre inneren RecyclerViews nicht vertikal scrollen, keine vertikale Bildlaufänderung erfolgt (aus Sicht von CoordinateLayout), und wenn keine Änderung vorliegt, ändert sich auch AppBarLayout nicht.

In Ihrem Fall können Sie, da Ihre inneren RecyclerViews in eine andere Richtung blättern, die Funktion deaktivieren, so dass CoordinateLayout das Scrollen ignoriert und auf das Scrollen des äußeren RecyclerView reagiert.


Beachten :

Das XML-Attribut Android:nestedScrollingEnabled="boolean" ist nicht für die Verwendung mit RecyclerView vorgesehen. Ein Versuch, Android:nestedScrollingEnabled="false" zu verwenden, führt zu einem Java.lang.NullPointerException. Daher müssen Sie es zumindest im Code tun. 

97
Ari

Ich bin etwas spät dran, aber das funktioniert definitiv für andere, die das gleiche Problem haben

 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

Getestete Lösung, verwenden Sie eine benutzerdefinierte NestedScrollView().

Code:

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

versuchen

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);

}

inneres Recylerview verwendet den gleichen Viewpool und wird glatter

1
Abhilash Das

Ich würde vorschlagen, dass Sie die horizontale Recyclingübersicht in Fragmenten wie der Google-App hinzufügen

0
Daniel Nyamasyo

wenn jemand noch auf der Suche ist, versuchen Sie Folgendes:

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) {
            }
        })

es prüft, ob gerade ein horizontaler Bildlauf ausgeführt wird, und lässt dann nicht zu, dass das übergeordnete Element das Ereignis behandelt

0
Ahmed na