RecyclerView wewnątrz ScrollView nie działa


276

Próbuję zaimplementować układ zawierający RecyclerView i ScrollView w tym samym układzie.

Szablon układu:

<RelativeLayout>
    <ScrollView android:id="@+id/myScrollView">
       <unrelated data>...</unrealated data>

           <android.support.v7.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/my_recycler_view"
            />
    </ScrollView>


</RelativeLayout>

Problemy: mogę przewijać do ostatniego elementu ScrollView

Rzeczy, których próbowałem:

  1. widok karty w ScrollView(teraz ScrollViewzawiera RecyclerView) - widzi kartę aż doRecyclerView
  2. początkową myślą było zaimplementowanie tego viewGroupza pomocą RecyclerViewzamiast tego, ScrollViewgdzie jednym z jego typów widoków jest, CardViewale uzyskałem dokładnie takie same wyniki jak w przypadkuScrollView


15
w wielu przypadkach jest to proste rozwiązanie NestedScrollView, ponieważ obsługuje wiele problemów z przewijaniem
Richard Le Mesurier

Richard dał ci odpowiedź w lutym. Użyj NestedScrollViewzamiast zamiast ScrollView. Właśnie po to jest.
Mike M.

2
Nic dla mnie nie zmienia.
Luke Allison

2
Dla porównania w przyszłości, jeśli ktoś przeżywa podobny problem tylko prawoślazu / nugat (API 23, 24) urządzenia, sprawdź mój obejście w stackoverflow.com/a/38995399/132121
Hossain Khan

Odpowiedzi:


653

użyj NestedScrollViewzamiastScrollView

Proszę przejść dokumencie referencyjnym NestedScrollView aby uzyskać więcej informacji.

i dodaj recyclerView.setNestedScrollingEnabled(false);do swojegoRecyclerView


24
Współpracuje z: android.support.v4.widget.NestedScrollView
Cocorico

7
trzymaj android:layout_height="wrap_content"zawyżony układ dla ViewHolder
Sarath Babu

4
W złożonym układzie jest NestedScrollViewdla mnie opóźniony, w przeciwieństwie do ScrollView. Poszukiwanie rozwiązania bez użyciaNestedScrollView
Roman Samoylenko

7
Możesz także dodać Androida: nestedScrollingEnabled = "false" do XML zamiast recyclinglerView.setNestedScrollingEnabled (false);
kostyabakay

2
To zadziałało dla mnie, ale należy pamiętać, że elementy w oknie recyclinglerView nie podlegają recyklingowi.
Mostafa Arian Nejad

102

Wiem, że spóźniłem się z grą, ale problem nadal występuje, nawet jeśli google naprawiło android.support.v7.widget.RecyclerView

Problem mam teraz jest RecyclerViewz layout_height=wrap_contentnie biorąc wysokość wszystkich kwestii pozycji wewnątrz ScrollViewże tylko dzieje się na wersjach prawoślazu i nugat + (API 23, 24, 25).
(UPDATE: Wymiana ScrollViewz android.support.v4.widget.NestedScrollViewdziała na wszystkich wersjach jakoś brakowało testowania. Przyjętego rozwiązania dodanych w tym w moim projekcie github jako demo.).

Po wypróbowaniu różnych rzeczy znalazłem obejście, które rozwiązuje ten problem.

Oto moja struktura układu w pigułce:

<ScrollView>
  <LinearLayout> (vertical - this is the only child of scrollview)
     <SomeViews>
     <RecyclerView> (layout_height=wrap_content)
     <SomeOtherViews>

Rozwiązaniem jest Owiń RecyclerViewsię RelativeLayout. Nie pytaj mnie, jak znalazłem to obejście !!!¯\_(ツ)_/¯

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="blocksDescendants">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

Pełny przykład jest dostępny w projekcie GitHub - https://github.com/amardeshbd/android-recycler-view-wrap-content

Oto zrzut ekranu pokazowy pokazujący poprawkę w akcji:

Screencast


6
Dzięki człowieku ... bardzo pomaga ... ale przewijanie staje się trudne po rozwiązaniu, więc ustawiłem recyclinglerview.setNestedScrollingEnabled (false); a teraz jego praca jak urok.
Sagar Chavada

4
Czy ta metoda przetwarza widoki? jeśli mamy około setek przedmiotów do recyklingu. To jest hack, a nie rozwiązanie.
androidXP

1
Tak, @androidXP ma rację, ten hack nie jest rozwiązaniem dla długiej listy. Mój przypadek użycia został naprawiony element w widoku listy, który był mniejszy niż 10. A jeśli chodzi o to, jak znalazłem inne obejście, próbowałem losowych rzeczy, to była jedna z nich :-)
Hossain Khan

idealny. rozwiązałem mój problem w przypadku pianki i cholewki Adnroid. ;)
Amir Ziarati

2
jeśli użyję tego rozwiązania, onBindView zostanie wywołany dla wszystkich pozycji na liście, co nie jest przypadkiem użycia recyclinglerview.
thedarkpassenger

54

Chociaż zalecenie to

nigdy nie należy umieszczać przewijanego widoku w innym przewijalnym widoku

To dobra rada, jednak jeśli ustawisz stałą wysokość w widoku urządzenia do recyklingu, powinno działać dobrze.

Jeśli znasz wysokość układu elementu adaptera, możesz po prostu obliczyć wysokość RecyclerView.

int viewHeight = adapterItemSize * adapterData.size();
recyclerView.getLayoutParams().height = viewHeight;

10
Jak zdobyć adapterItemSize w recyklerze Zobacz jakiś pomysł?
Krutik

1
To działa jak urok! Mała poprawka powinna być: int viewHeight = adapterItemSize * adapterData.size (); recyclerView.getLayoutParams (). height = viewHeight;
Vaibhav Jani

3
Jak znaleźć adapterItemSize?
droidster.me,

2
@JoakimEngstrom Czym jest adapterItemSizezmienna?
IgorGanapolsky

1
Unikaj widoku recyklera w widoku przewijania, ponieważ widok przewijania daje dziecku nieskończoną przestrzeń. Powoduje to, że widok recyklera mający zawartość wrap_content jako wysokość mierzy nieskończenie w kierunku pionowym (do ostatniego elementu widoku recyklera). Zamiast używać widoku makulatury w widoku przewijania, używaj tylko widoku makulatury z różnymi typami przedmiotów. Dzięki tej implementacji dzieci widoku przewijania będą zachowywać się jak typ widoku. Obsługuj te typy widoków w widoku recyklera.
abhishh

28

W przypadku, gdy ustawienie stałej wysokości dla RecyclerView nie działało dla kogoś (takiego jak ja), oto co dodałem do rozwiązania o stałej wysokości:

mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                rv.getParent().requestDisallowInterceptTouchEvent(true);
                break;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
});

1
Rzeczywiście, działało to dla mnie dobrze. Dzięki temu mogłem przesuwać widok przewijania w górę i w dół, a kiedy wybieram widok recyklera do przewijania, ma on pierwszeństwo przed widokiem przewijania. Dzięki za wskazówkę
PGMacDesign,

1
dla mnie też zadziałało, po prostu użyj getParent na bezpośrednim podglądzie przewijania
BigBen3216

Ta metoda działa również na rozkładach cyjanogenodycznych. Rozwiązanie o stałej wysokości w trybie cyjanogenodalnym działa, ale tylko wtedy, gdy stała wysokość jest wysokością bezwzględną wszystkich pozycji na liście, co w pierwszej kolejności jest sprzeczne z punktem widzenia programu do recyklingu. Pozytywne.
HappyKatz,

Potrzebowałem także recyclinglerView.setNestedScrollingEnabled (false);
Analizer,


15

RecyclerViews można umieszczać w ScrollViews, o ile same się nie przewijają. W takim przypadku sensowne jest ustawienie stałej wysokości.

Właściwym rozwiązaniem jest użycie wrap_contentna wysokości RecyclerView, a następnie zaimplementowanie niestandardowego LinearLayoutManager, który może poprawnie obsługiwać zawijanie.

Skopiuj ten LinearLayoutManager do swojego projektu: https://github.com/serso/android-linear-layout-manager/blob/master/lib/src/main/java/org/solovyev/android/views/llm/LinearLayoutManager.java

Następnie zawiń RecyclerView:

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

I skonfiguruj tak:

    RecyclerView list = (RecyclerView)findViewById(R.id.list);
    list.setHasFixedSize(true);
    list.setLayoutManager(new com.example.myapp.LinearLayoutManager(list.getContext()));
    list.setAdapter(new MyViewAdapter(data));

Edycja: Może to powodować komplikacje podczas przewijania, ponieważ RecyclerView może ukraść zdarzenia dotykowe ScrollView. Moim rozwiązaniem było po prostu porzucić RecyclerView i przejść z LinearLayout, programowo zawęzić podview i dodać je do układu.


4
Czy nie można nazwać setNestedScrollingEnabled (false) w widoku programu do recyklingu?
Eshaan,

14

Dla ScrollView, można użyć fillViewport=truei zrobić layout_height="match_parent"jak poniżej i umieścić recykler widok wewnątrz:

<ScrollView
    android:fillViewport="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/llOptions">
          <android.support.v7.widget.RecyclerView
            android:id="@+id/rvList"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
</ScrollView>

Kod nie wymaga dalszej regulacji wysokości.


Przetestowano poprawne działanie przy użyciu v23.2.1. Używałem go do dodawania układu powyżej widoku recyklera.
winsontan520

tylko przewijanie RecyclerView i ScrollView nie przewijanie
Samad

13

RecyclerViewRęczne obliczanie wysokości nie jest dobre, lepiej jest użyć niestandardowego LayoutManager.

Powodem powyższego problemu jest każdy widok, który ma swój przewijania ( ListView, GridView, RecyclerView) udało się obliczyć wysokość, gdy jest to dodatek jako dziecko w innym widoku ma zwój. Przesłonięcie tej onMeasuremetody rozwiąże problem.

Zastąp domyślnego menedżera układu poniższym:

public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;

private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;

private final int[] childDimensions = new int[2];
private final RecyclerView view;

private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context) {
    super(context);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view) {
    super(view.getContext());
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
    super(view.getContext(), orientation, reverseLayout);
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

public void setOverScrollMode(int overScrollMode) {
    if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
        throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
    if (this.view == null) throw new IllegalStateException("view == null");
    this.overScrollMode = overScrollMode;
    ViewCompat.setOverScrollMode(view, overScrollMode);
}

public static int makeUnspecifiedSpec() {
    return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);

    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);

    final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
    final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;

    final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
    final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

    final int unspecified = makeUnspecifiedSpec();

    if (exactWidth && exactHeight) {
        // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
        super.onMeasure(recycler, state, widthSpec, heightSpec);
        return;
    }

    final boolean vertical = getOrientation() == VERTICAL;

    initChildDimensions(widthSize, heightSize, vertical);

    int width = 0;
    int height = 0;

    // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
    // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
    // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
    // called whiles scrolling)
    recycler.clear();

    final int stateItemCount = state.getItemCount();
    final int adapterItemCount = getItemCount();
    // adapter always contains actual data while state might contain old data (f.e. data before the animation is
    // done). As we want to measure the view with actual data we must use data from the adapter and not from  the
    // state
    for (int i = 0; i < adapterItemCount; i++) {
        if (vertical) {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, widthSize, unspecified, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            height += childDimensions[CHILD_HEIGHT];
            if (i == 0) {
                width = childDimensions[CHILD_WIDTH];
            }
            if (hasHeightSize && height >= heightSize) {
                break;
            }
        } else {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, unspecified, heightSize, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            width += childDimensions[CHILD_WIDTH];
            if (i == 0) {
                height = childDimensions[CHILD_HEIGHT];
            }
            if (hasWidthSize && width >= widthSize) {
                break;
            }
        }
    }

    if (exactWidth) {
        width = widthSize;
    } else {
        width += getPaddingLeft() + getPaddingRight();
        if (hasWidthSize) {
            width = Math.min(width, widthSize);
        }
    }

    if (exactHeight) {
        height = heightSize;
    } else {
        height += getPaddingTop() + getPaddingBottom();
        if (hasHeightSize) {
            height = Math.min(height, heightSize);
        }
    }

    setMeasuredDimension(width, height);

    if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
        final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
                || (!vertical && (!hasWidthSize || width < widthSize));

        ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
    }
}

private void logMeasureWarning(int child) {
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
                "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
    }
}

private void initChildDimensions(int width, int height, boolean vertical) {
    if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
        // already initialized, skipping
        return;
    }
    if (vertical) {
        childDimensions[CHILD_WIDTH] = width;
        childDimensions[CHILD_HEIGHT] = childSize;
    } else {
        childDimensions[CHILD_WIDTH] = childSize;
        childDimensions[CHILD_HEIGHT] = height;
    }
}

@Override
public void setOrientation(int orientation) {
    // might be called before the constructor of this class is called
    //noinspection ConstantConditions
    if (childDimensions != null) {
        if (getOrientation() != orientation) {
            childDimensions[CHILD_WIDTH] = 0;
            childDimensions[CHILD_HEIGHT] = 0;
        }
    }
    super.setOrientation(orientation);
}

public void clearChildSize() {
    hasChildSize = false;
    setChildSize(DEFAULT_CHILD_SIZE);
}

public void setChildSize(int childSize) {
    hasChildSize = true;
    if (this.childSize != childSize) {
        this.childSize = childSize;
        requestLayout();
    }
}

private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
    final View child;
    try {
        child = recycler.getViewForPosition(position);
    } catch (IndexOutOfBoundsException e) {
        if (BuildConfig.DEBUG) {
            Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e);
        }
        return;
    }

    final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

    final int hPadding = getPaddingLeft() + getPaddingRight();
    final int vPadding = getPaddingTop() + getPaddingBottom();

    final int hMargin = p.leftMargin + p.rightMargin;
    final int vMargin = p.topMargin + p.bottomMargin;

    // we must make insets dirty in order calculateItemDecorationsForChild to work
    makeInsetsDirty(p);
    // this method should be called before any getXxxDecorationXxx() methods
    calculateItemDecorationsForChild(child, tmpRect);

    final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
    final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

    final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
    final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());

    child.measure(childWidthSpec, childHeightSpec);

    dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
    dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

    // as view is recycled let's not keep old measured values
    makeInsetsDirty(p);
    recycler.recycleView(child);
}

private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
    if (!canMakeInsetsDirty) {
        return;
    }
    try {
        if (insetsDirtyField == null) {
            insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
            insetsDirtyField.setAccessible(true);
        }
        insetsDirtyField.set(p, true);
    } catch (NoSuchFieldException e) {
        onMakeInsertDirtyFailed();
    } catch (IllegalAccessException e) {
        onMakeInsertDirtyFailed();
    }
}

private static void onMakeInsertDirtyFailed() {
    canMakeInsetsDirty = false;
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
    }
}
}

Jak mogę wywołać tę klasę, gdy ustawimy dane na adapter?
Milan Gajera,

11

Spróbuj tego. Bardzo późna odpowiedź. Ale na pewno pomóż każdemu w przyszłości.

Ustaw Scrollview na NestedScrollView

<android.support.v4.widget.NestedScrollView>
 <android.support.v7.widget.RecyclerView>
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.NestedScrollView>

W twoim Recyclerview

recyclerView.setNestedScrollingEnabled(false); 
recyclerView.setHasFixedSize(false);

8
używanie RecyclerView wewnątrz NestedScrollView wywołuje onBindView dla każdego elementu na liście, nawet jeśli element nie jest widoczny. Jakieś rozwiązanie tego problemu?
thedarkpassenger

8

AKTUALIZACJA: ta odpowiedź jest nieaktualna, ponieważ istnieją widżety takie jak NestedScrollView i RecyclerView, które obsługują przewijanie zagnieżdżone.

nigdy nie należy umieszczać przewijanego widoku w innym przewijalnym widoku!

Proponuję zrobić widok głównego układu recyklingu i umieścić swoje widoki jako elementy widoku układu recyklingu.

spójrz na ten przykład, który pokazuje, jak używać wielu widoków wewnątrz adaptera widoku recyklera. link do przykładu


mam stronę z więcej niż jednym Recyklerem , czy jest jakiś inny sposób, aby to przekonać? coś takiego jak komentarz na Instagramie lub w Google Play, który ładuje więcej rekordów, gdy klikniesz więcej komentarzy
Ashkan

uczyń go jednym recyklerem Zobacz i umieść swoje poglądy jako przedmioty dla tego recyklera
EC84B4

6
To nonsens. Możesz dobrze zagnieżdżać RecyclerViews.
IgorGanapolsky

Ta odpowiedź jest teraz nieaktualna. Mamy takie funkcje, jak NestedScrollView, które pozwalają na zagnieżdżane widoki przewijalne. Zagnieżdżone widoki RecyclerView również teraz działają.
Corey Horn

7

Jeśli umieścisz RecyclerViewwewnątrz NestedScrollViewi umożliwiają recyclerView.setNestedScrollingEnabled(false);przewijanie będzie działa dobrze .
Istnieje jednak problem

RecyclerView nie poddawaj recyklingowi

Na przykład twój RecyclerView(wewnątrz NestedScrollViewlub ScrollView) ma 100 pozycji.
Po Activityuruchomieniu utworzy się 100 przedmiotów ( onCreateViewHolderi onBindViewHolder100 przedmiotów zostanie wywołanych w tym samym czasie).
Przykład: dla każdego elementu załadujesz duży obraz z API => utworzone działanie - załaduje się> 100 obrazów.
Powoduje spowolnienie i opóźnienie rozpoczęcia działania.
Możliwe rozwiązanie :
- Myślenie o korzystaniu RecyclerViewz wielu typów.

Jednakże, jeśli w Twoim przypadku, istnieje tylko kilka artykuł RecyclerViewi recyklingu lub nie recycle nie wpływa na wydajność dużo, można użyć RecyclerViewśrodka ScrollViewdo prostych


Pokaż, jak mogę RecyclerView poddawać recyklingowi nawet w ScollView? Dzięki!
Kłamca

2
@Liar, obecnie nie ma możliwości RecyclerViewrecyklingu po włożeniu ScrollView. Jeśli chcesz poddać recyklingowi, pomyśl o innym podejściu (np. Użyciu RecyclerView z wieloma typami)
Phan Van Linh

4

Możesz użyć tej metody:

Dodaj ten wiersz do swojego recyklera Wyświetl widok xml:

        android:nestedScrollingEnabled="false"

spróbuj, widok recyklera będzie płynnie przewijany z elastyczną wysokością

mam nadzieję, że to pomogło.


2

Wydaje się, że NestedScrollViewto rozwiązuje problem. Testowałem przy użyciu tego układu:

<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/dummy_text"
        />

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        >
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </android.support.v7.widget.CardView>

</LinearLayout>

I działa bez problemów


Bro, nadal mam ten sam problem po zmianie na NestedScrollview z Scrollview.
MohanRaj S

mmm ... czy możesz podzielić się jakimś kodem ... Mam zero problemów, ale nigdy tego nie wiesz
royB

jak mogę udostępnić mój kod przez e-mail lub przez przepełnienie stosu
MohanRaj S

2
za pomocą tego kodu wywołuje onBindView dla wszystkich elementów na liście, nawet jeśli te elementy nie są widoczne na liście. To przeczy celowi recyklingu.
thedarkpassenger

2

Użyłem CustomLayoutManager, aby wyłączyć przewijanie RecyclerView. Nie używaj również Recycler View jako WrapContent, użyj go jako 0dp, waga = 1

public class CustomLayoutManager extends LinearLayoutManager {
    private boolean isScrollEnabled;

    // orientation should be LinearLayoutManager.VERTICAL or HORIZONTAL
    public CustomLayoutManager(Context context, int orientation, boolean isScrollEnabled) {
        super(context, orientation, false);
        this.isScrollEnabled = isScrollEnabled;
    }

    @Override
    public boolean canScrollVertically() {
        //Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
        return isScrollEnabled && super.canScrollVertically();
    }
}

Użyj CustomLayoutManager w RecyclerView:

CustomLayoutManager mLayoutManager = new CustomLayoutManager(getBaseActivity(), CustomLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(mLayoutManager);
        ((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 
        recyclerView.setAdapter(statsAdapter);

XML interfejsu użytkownika:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background_main"
    android:fillViewport="false">


    <LinearLayout
        android:id="@+id/contParentLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <edu.aku.family_hifazat.libraries.mpchart.charts.PieChart
                android:id="@+id/chart1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/x20dp"
                android:minHeight="@dimen/x300dp">

            </edu.aku.family_hifazat.libraries.mpchart.charts.PieChart>


        </FrameLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">


        </android.support.v7.widget.RecyclerView>


    </LinearLayout>


</ScrollView>

2

Miałem ten sam problem. Właśnie tego próbowałem i działa. Udostępniam mój kod XML i Java. Mam nadzieję, że to komuś pomoże.

Oto xml

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

< NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/iv_thumbnail"
            android:layout_width="match_parent"
            android:layout_height="200dp" />

        <TextView
            android:id="@+id/tv_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Description" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Buy" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Reviews" />
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rc_reviews"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        </android.support.v7.widget.RecyclerView>

    </LinearLayout>
</NestedScrollView >

Oto powiązany kod Java. To działa jak urok.

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setNestedScrollingEnabled(false);

Co z układami poniżej RecyclerView?
Tapa Zapisz

to też działa. Musisz tylko użyć. NestedScrollViewzamiast przewijania widoku
Zeeshan Shabbir

Tak, tylko NestedScrollView. Ale rozwiązanie z NestedScrollView + RecycleView wywołuje bardzo powolne populacje RecyclerView (myślę o obliczeniu pozycji Y pierwszego widoku po RecyclerView)
Tapa Zapisz


0

W rzeczywistości głównym celem RecyclerViewjest kompensacja ListViewi ScrollView. Zamiast robić to, co faktycznie robisz: mając RecyclerVieww domu ScrollView, sugerowałbym posiadanie tylko takiego, RecyclerViewktóry poradziłby sobie z wieloma typami dzieci.


1
Działałoby to tylko pod warunkiem, że twoje dzieci będą mogły być garażowane, gdy tylko przewiniesz je poza zasięgiem wzroku. Jeśli masz dzieci, które są MapFragmentami lub widokami ulic, nie ma to sensu, ponieważ są zmuszone przeładowywać za każdym razem, gdy przewijają widok recyklera. Osadzenie ich w widoku przewijania, a następnie wygenerowanie widoku recyklera na dole ma większy sens.
Simon

1
@Simon jest przydatny setIsRecyclable () w ViewHolder
ernazm

RecyclerView zastępuje ListView Nie ma na celu zastąpienia ScrollView.
cyroxis

Ten komentarz zasługuje na coś lepszego. To rozwiązanie, a nawet lepsze niż przyjęte.
Kai Wang

0

Najpierw powinieneś użyć NestedScrollViewzamiast ScrollViewi umieścić RecyclerViewwnętrze NestedScrollView.

Użyj niestandardowej klasy układu, aby zmierzyć wysokość i szerokość ekranu:

public class CustomLinearLayoutManager extends LinearLayoutManager {

public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        if (getOrientation() == HORIZONTAL) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    heightSpec,
                    mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                    widthSpec,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    recycler.bindViewToPosition(view, position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}
}

I zaimplementuj poniższy kod w działaniu / fragmencie RecyclerView:

 final CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(mAdapter);

    recyclerView.setNestedScrollingEnabled(false); // Disables scrolling for RecyclerView, CustomLinearLayoutManager used instead of MyLinearLayoutManager
    recyclerView.setHasFixedSize(false);

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            int visibleItemCount = layoutManager.getChildCount();
            int totalItemCount = layoutManager.getItemCount();
            int lastVisibleItemPos = layoutManager.findLastVisibleItemPosition();
            Log.i("getChildCount", String.valueOf(visibleItemCount));
            Log.i("getItemCount", String.valueOf(totalItemCount));
            Log.i("lastVisibleItemPos", String.valueOf(lastVisibleItemPos));
            if ((visibleItemCount + lastVisibleItemPos) >= totalItemCount) {
                Log.i("LOG", "Last Item Reached!");
            }
        }
    });

0

Jeśli RecyclerView pokazuje tylko jeden wiersz w ScrollView. Musisz tylko ustawić wysokość swojego rzędu na android:layout_height="wrap_content".


0

możesz również zastąpić LinearLayoutManager, aby przeglądanie recyklera przebiegało płynnie

@Override
    public boolean canScrollVertically(){
        return false;
    }

0

Przepraszam za spóźnienie na imprezę, ale wygląda na to, że istnieje inne rozwiązanie, które działa idealnie w przypadku, o którym wspomniałeś.

Jeśli używasz widoku recyklera w widoku recyklingu, wydaje się, że działa on idealnie. Osobiście próbowałem i użyłem go, i wydaje się, że nie powoduje to powolności ani szarpnięcia. Teraz nie jestem pewien, czy jest to dobra praktyka, czy nie, ale zagnieżdżanie wielu widoków recyklera, nawet zagnieżdżony widok przewijania spowalnia. Ale to wydaje się działać dobrze. Spróbuj. Jestem pewien, że zagnieżdżanie będzie w tym doskonale pasować.



-2

** Rozwiązanie, które działało dla mnie
Użyj NestedScrollView o wysokości jako wrap_content

<br> RecyclerView 
                android:layout_width="match_parent"<br>
                android:layout_height="wrap_content"<br>
                android:nestedScrollingEnabled="false"<br>
                  app:layoutManager="android.support.v7.widget.LinearLayoutManager"
                tools:targetApi="lollipop"<br><br> and view holder layout 
 <br> android:layout_width="match_parent"<br>
        android:layout_height="wrap_content"

// Twoja treść wiersza idzie tutaj


Niektóre komentarze do podobnej odpowiedzi mówią, że wyłączenie zagnieżdżonego przewijania nie pozwala na użycie RecyclerView, tzn. Że nie przetwarza widoków. Nie jestem pewien, jak to potwierdzić.
jk7

1
Ustawienie nestedScrollingEnabled = "false" powoduje, że RecyclerView NIE przetwarza swoich widoków, przynajmniej w mojej konfiguracji (RecyclerView wewnątrz NestedScrollView). Potwierdzono, dodając RecyclerListener do RecyclerView i ustawiając punkt przerwania wewnątrz jego metody onViewRecycled ().
jk7
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.