RecyclerView und SwipeRefreshLayout

Lesezeit: 9 Minuten

Benutzer-Avatar
Lukas Olsen

Ich benutze die neue RecyclerView-Layout in einem SwipeRefreshLayout und erlebte ein seltsames Verhalten. Beim Zurückscrollen der Liste nach oben wird manchmal die Ansicht oben abgeschnitten.

Liste nach oben gescrollt

Wenn ich jetzt versuche, nach oben zu scrollen, wird der Pull-To-Refresh ausgelöst.

abgeschnittene Reihe

Wenn ich versuche das Swipe-Refresh-Layout um die Recycler-Ansicht herum zu entfernen, ist das Problem weg. Und es ist auf jedem Telefon reproduzierbar (nicht nur auf L-Preview-Geräten).

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/contentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/hot_fragment_recycler"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

Das ist mein Layout – die Zeilen werden dynamisch vom RecyclerViewAdapter (2 Viewtypes in dieser Liste) erstellt.

public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter<GameRow> {

private static final int VIEWTYPE_GAME_TITLE = 0;
private static final int VIEWTYPE_GAME_TEAM = 1;

@Inject
Picasso picasso;

public HotRecyclerAdapter(Injector injector) {
    super(injector);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            TitleGameRowViewHolder holder = (TitleGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
        case VIEWTYPE_GAME_TEAM: {
            TeamGameRowViewHolder holder = (TeamGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            View view = inflater.inflate(R.layout.game_row_title, viewGroup, false);
            return new TitleGameRowViewHolder(view);
        }
        case VIEWTYPE_GAME_TEAM: {
            View view = inflater.inflate(R.layout.game_row_team, viewGroup, false);
            return new TeamGameRowViewHolder(view);
        }
    }
    return null;
}

@Override
public int getItemViewType(int position) {
    GameRow row = getItem(position);
    if (row.isTeamGameRow()) {
        return VIEWTYPE_GAME_TEAM;
    }
    return VIEWTYPE_GAME_TITLE;
}

Hier ist der Adapter.

   hotAdapter = new HotRecyclerAdapter(this);

    recyclerView.setHasFixedSize(false);
    recyclerView.setAdapter(hotAdapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

    contentView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            loadData();
        }
    });

    TypedArray colorSheme = getResources().obtainTypedArray(R.array.main_refresh_sheme);
    contentView.setColorSchemeResources(colorSheme.getResourceId(0, -1), colorSheme.getResourceId(1, -1), colorSheme.getResourceId(2, -1), colorSheme.getResourceId(3, -1));

Und der Code der Fragment enthält die Recycler und die SwipeRefreshLayout.

Hat jemand anderes dieses Verhalten erlebt und gelöst oder zumindest den Grund dafür gefunden?

  • Ich hatte genau das gleiche Problem. Außerdem ist mir aufgefallen, dass RecyclerView von getScrollY() immer 0 zurückgibt. Vielleicht ist dies logisch, da verschiedene LayoutManager ausgetauscht werden können, nicht unbedingt scrollende. Ich bin mir nicht sicher, weil ich nicht weiter recherchiert habe. Ich freue mich, eine Lösung zu finden, werde die Antwort aber positiv bewerten, wenn ich sie nach meinem Urlaub in zwei Wochen testen kann.

    – Cybergen

    14. August 2014 um 18:10 Uhr


  • Lukas Olsen, dass “texturiert” unglaublich aussieht.

    – Jared Burrows

    29. Oktober 2014 um 3:06 Uhr


Benutzer-Avatar
Krunal Patel

schreiben Sie den folgenden Code hinein addOnScrollListener des RecyclerView

So was:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            int topRowVerticalPosition =
                    (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
            swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);

        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }
    });

  • Ich verwende Recyclerview – nicht Listview

    – Lukas Olsen

    8. August 2014 um 10:58 Uhr

  • Nur um die Antwort ein wenig zu erweitern: Falls Sie die Polsterung für Ihre Elemente mithilfe von festlegen addItemDecoration und haben auch eine obere Polsterung auf der ReyclerView selbst würden Sie Ihre umschalten SwipeRefreshLayout wie folgt: boolean canEnableSwipeRefresh = mLayoutManager.findFirstVisibleItemPosition() == 0 && recyclerView.getChildAt(0).getTop() - (offset + mRecView.getPaddingTop()) == 0; wo offset wenn die obere Polsterung Ihrer Artikel

    – Droidenmann

    10. Dezember 2014 um 11:53 Uhr


  • @krunalpatel Ihre Parameter für onScrollStateChanged sind falsch. Sie sollten sein: onScrollStateChanged(RecyclerView recyclerView, int scrollState)

    – IgorGanapolsky

    7. Januar 2015 um 20:43 Uhr

  • recyclerView.setOnScrollListener ist veraltet, verwenden Sie stattdessen addOnScrollListener

    – jiawen

    11. November 2015 um 5:31 Uhr

Benutzer-Avatar
tom91136

Bevor Sie diese Lösung verwenden: RecyclerView ist noch nicht vollständig. VERSUCHEN SIE, ES NICHT IN DER PRODUKTION ZU VERWENDEN, ES SEI DENN, SIE SIND WIE ICH!

Wie für November 2014 gibt es immer noch Fehler in RecyclerView, die dazu führen würden canScrollVertically falsch vorzeitig zurückzugeben. Diese Lösung löst alle Probleme beim Scrollen.

Die Drop-in-Lösung:

public class FixedRecyclerView extends RecyclerView {
    public FixedRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean canScrollVertically(int direction) {
        // check if scrolling up
        if (direction < 1) {
            boolean original = super.canScrollVertically(direction);
            return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
        }
        return super.canScrollVertically(direction);

    }
}

Sie müssen nicht einmal ersetzen RecyclerView in deinem Code mit FixedRecyclerView, das Ersetzen des XML-Tags würde ausreichen! (Das stellt sicher, dass der Übergang nach Abschluss von RecyclerView schnell und einfach ist.)

Erläuterung:

Grundsätzlich, canScrollVertically(boolean) gibt false zu früh zurück, also prüfen wir, ob die RecyclerView wird ganz nach oben in der ersten Ansicht gescrollt (wo die Spitze des ersten untergeordneten Elements 0 wäre) und dann zurückkehrt.

EDIT: Und wenn Sie nicht verlängern möchten RecyclerView Aus irgendeinem Grund können Sie verlängern SwipeRefreshLayout und überschreiben die canChildScrollUp() -Methode und fügen Sie dort die Prüflogik ein.

EDIT2:
RecyclerView wurde veröffentlicht und bisher besteht keine Notwendigkeit, diesen Fix zu verwenden.

  • Ich habe den Code aktualisiert, um einen Fall zu beheben, in dem der Adapter 0 für getCount() zurückgibt

    – tom91136

    11. August 2014 um 14:56 Uhr

  • Ich denke, es sollte sein: if (direction < 0)

    – Cybergen

    3. September 2014 um 8:22 Uhr


  • Das sagst du also canScrollVertically wird nirgendwo überhaupt angerufen? Dann verwenden Sie möglicherweise immer noch die Bibliotheksversion von RecyclerView. Fügen Sie einige hinzu Log zum Konstruktor und sehen, ob das überhaupt aufgerufen wird.

    – tom91136

    28. Dezember 2014 um 0:32 Uhr

  • Ich sah dasselbe wie @Marky17, die Methode wurde nicht aufgerufen. Nach dem Graben war der Grund, dass mein FixedRecyclerView (FRV) kein DIREKTER Nachkomme des SwipeRefreshLayout (SRL) war. Mein FRV befand sich in einem FrameLayout (damit ich eine QuickReturn-Leiste hinzufügen konnte). Als SRL also versuchte, sein Kind “CanScrollVertically?” Es wurde das umschließende FrameLayout abgefragt, nicht das FRV. Ihr Ansatz funktioniert perfekt, wenn das FRV das (einzige?) direkte Kind des SRL ist. Für meine App habe ich SRL optimiert, um nach einem FRV-Kind zu suchen, wenn es auf ein FrameLayout trifft. Vielen Dank!

    – MojoTosh

    2. Januar 2015 um 17:39 Uhr

  • Nur um dies hinzuzufügen, wenn Ihre Recycler-Ansicht oben eine Auffüllung für ein schnelles Rückkehr-Symbolleistenmuster hinzugefügt hat, müssen wir dies berücksichtigen, indem wir getChildAt(0).getTop() < 0 in getChildAt(0).getTop() < getPaddingTop ändern ()

    – Steve G.

    3. Februar 2015 um 20:13 Uhr


Benutzer-Avatar
Balachandarkm

Ich bin vor kurzem auf das gleiche Problem gestoßen. Ich habe den von @Krunal_Patel vorgeschlagenen Ansatz ausprobiert, aber er hat die meiste Zeit in meinem Nexus 4 funktioniert und in Samsung Galaxy S2 überhaupt nicht funktioniert. Beim Debuggen ist recyclerView.getChildAt(0).getTop() immer nicht korrekt für RecyclerView. Nachdem ich verschiedene Methoden durchgegangen war, dachte ich mir, dass wir die Methode findFirstCompletelyVisibleItemPosition() des LayoutManagers verwenden können, um vorherzusagen, ob das erste Element der RecyclerView sichtbar ist oder nicht, um SwipeRefreshLayout zu aktivieren. Finden Sie den folgenden Code. Hoffe, es hilft jemandem, der versucht, das gleiche Problem zu beheben. Prost.

    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        }

        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
        }
    });

  • Obwohl ich mit der Verwendung eines Scroll-Listeners nicht einverstanden bin, habe ich dieselbe Bedingung verwendet, um die zu überschreiben SwipeRefreshLayout‘s canChildScrollUp Methode und es scheint gut zu funktionieren.

    – verdammter Maurer

    26. November 2014 um 11:55 Uhr

  • Version 21.0.2 von AppCompat hat in meinem Fall die Notwendigkeit einer Problemumgehung beseitigt

    – verdammter Maurer

    8. Februar 2015 um 12:44 Uhr

  • @darnmason Gut zu wissen 🙂

    – Balachandarkm

    8. Februar 2015 um 12:56 Uhr

  • @darnmason Ich habe es mit 21.0.2 versucht. Aber bei mir ist es immer noch so. Es hat nicht funktioniert.

    – Balachandarkm

    25. Februar 2015 um 11:37 Uhr

  • linearLayoutManager.findFirstCompletelyVisibleItemPosition() gibt bei mir immer null zurück!

    – Saravanabalagi Ramachandran

    9. April 2016 um 16:16 Uhr

Benutzer-Avatar
Amrut Bidri

So habe ich dieses Problem in meinem Fall gelöst. Es könnte für jemanden nützlich sein, der hier landet, um nach ähnlichen Lösungen zu suchen.

recyclerView.addOnScrollListener(new OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            // TODO Auto-generated method stub
            super.onScrolled(recyclerView, dx, dy);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState)
        {
            // TODO Auto-generated method stub
            //super.onScrollStateChanged(recyclerView, newState);
            int firstPos=linearLayoutManager.findFirstCompletelyVisibleItemPosition();
            if (firstPos>0)
            {
                swipeLayout.setEnabled(false);
            }
            else {
                swipeLayout.setEnabled(true);
            }
        }
    });

Ich hoffe, dies könnte definitiv jemandem helfen, der nach einer ähnlichen Lösung sucht.

Benutzer-Avatar
Keshav Gera

Quellcode
https://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM

recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    }

    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
    }
});

  • Bitte erwägen Sie die Verwendung eines Open-Source-Dateihostingdienstes (z. B. GitHub oder BitBucket), anstatt Ihr gesamtes Projekt zu komprimieren und auf Google Drive hochzuladen.

    – Edric

    10. Dezember 2018 um 6:59 Uhr

Benutzer-Avatar
Maria Rodionova

Keine der Antworten hat bei mir funktioniert, aber ich habe es geschafft, meine eigene Lösung zu implementieren, indem ich eine benutzerdefinierte Implementierung von LinearLayoutManager erstellt habe. Poste es hier, falls jemand anderes es braucht.

class LayoutManagerScrollFixed(context: Context) : LinearLayoutManager(context) {

override fun smoothScrollToPosition(
    recyclerView: RecyclerView?,
    state: RecyclerView.State?,
    position: Int
) {
    super.smoothScrollToPosition(recyclerView, state, position)
    val child = getChildAt(0)
    if (position == 0 && recyclerView != null && child != null) {
        scrollVerticallyBy(child.top - recyclerView.paddingTop, recyclerView.Recycler(), state)
    }
}

Dann rufen Sie einfach an

recyclerView?.layoutManager = LayoutManagerScrollFixed(requireContext())

Und es funktioniert!

  • Bitte erwägen Sie die Verwendung eines Open-Source-Dateihostingdienstes (z. B. GitHub oder BitBucket), anstatt Ihr gesamtes Projekt zu komprimieren und auf Google Drive hochzuladen.

    – Edric

    10. Dezember 2018 um 6:59 Uhr

Benutzer-Avatar
jagit

Leider ist dies ein bekannter Fehler in LinearLayoutManager. Es berechnetScrollOffset nicht richtig, wenn das erste Element sichtbar ist. wird behoben, wenn es veröffentlicht wird.

  • Dies scheint nicht behoben worden zu sein

    – verdammter Maurer

    26. November 2014 um 11:06 Uhr

  • Ich habe eine Beispiel-App erstellt und funktioniert einwandfrei. Können Sie eine Beispiel-App und einen Fehlerbericht auf b.android.com erstellen? Vielen Dank.

    – jagit

    1. Dezember 2014 um 19:03 Uhr

  • Mein Layout ist ziemlich komplex und als ich versuchte, das Problem in einer Beispiel-App zu reproduzieren, stellte ich fest, dass es eine Nebenversion 21.0.2 von AppCompat gab, die es behoben hat. 😀

    – verdammter Maurer

    2. Dezember 2014 um 12:22 Uhr

  • Das sind tolle Neuigkeiten, danke fürs Prüfen. Ich empfehle Ihnen dringend, auch Ihr Hauptprojekt zu aktualisieren, da es auch andere Fehlerbehebungen gibt.

    – jagit

    2. Dezember 2014 um 16:51 Uhr

1215820cookie-checkRecyclerView und SwipeRefreshLayout

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy