RecyclerView bên trong ScrollView không hoạt động


276

Tôi đang cố gắng thực hiện một bố cục có chứa RecyclerView và ScrollView ở cùng một bố cục.

Mẫu bố cục:

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

Vấn đề: tôi có thể cuộn cho đến khi phần tử cuối cùng của ScrollView

Những điều tôi đã thử:

  1. xem thẻ bên trong ScrollView(hiện ScrollViewRecyclerView) - có thể thấy thẻ lên cho đến khiRecyclerView
  2. suy nghĩ ban đầu là thực hiện điều này viewGroupbằng cách sử dụng RecyclerViewthay vì trong ScrollViewđó một trong số các kiểu xem là CardViewnhưng tôi đã nhận được kết quả chính xác như vớiScrollView

kiểm tra phương pháp này: stackoverflow.com/a/21878703/684582
Alécio Carvalho

15
một giải pháp đơn giản trong nhiều trường hợp này là sử dụng NestedScrollViewthay thế, vì nó xử lý rất nhiều vấn đề cuộn
Richard Le Mesurier 19/2/2016

Richard đã cho bạn câu trả lời vào tháng Hai. Sử dụng NestedScrollViewthay vì a ScrollView. Đó chính xác là những gì nó làm.
Mike M.

2
Không thay đổi một điều cho tôi.
Luke Allison

2
Để tham khảo trong tương lai, nếu bất kỳ ai gặp phải vấn đề tương tự chỉ có các thiết bị marshmallow / nougat (API 23, 24), hãy kiểm tra cách khắc phục của tôi tại stackoverflow.com/a/38995399/132121
Hossain Khan

Câu trả lời:


653

sử dụng NestedScrollViewthay vìScrollView

Vui lòng xem qua tài liệu tham khảo của NestedScrollView để biết thêm thông tin.

và thêm recyclerView.setNestedScrollingEnabled(false);vào của bạnRecyclerView


24
Hoạt động với: android.support.v4.widget.NestedScrollView
Cocorico

7
giữ android:layout_height="wrap_content"cho bố cục tăng cao cho ViewHolder
Sarath Babu

4
Trong một bố cục phức tạp độ NestedScrollViewtrễ cho tôi, không giống như ScrollView. Tìm kiếm giải pháp mà không cần sử dụngNestedScrollView
Roman Samoylenko

7
Ngoài ra, bạn có thể thêm android: NestedScrollingEnables = "false" vào XML thay vì recyclerView.setNestedScrollingEnabled (false);
kostyabakay

2
Nó làm việc cho tôi nhưng hãy nhớ rằng các mục bên trong recyclerView không được tái chế.
Mostafa Arian Nejad

102

Tôi biết tôi đến trễ trò chơi, nhưng vấn đề vẫn tồn tại ngay cả sau khi google đã khắc phục android.support.v7.widget.RecyclerView

Vấn đề tôi có được bây giờ là RecyclerViewvới layout_height=wrap_contentkhông dùng chiều cao của tất cả các vấn đề mục bên trong ScrollViewchỉ xảy ra trên các phiên bản Marshmallow và Nougat + (API 23, 24, 25).
(CẬP NHẬT: Thay thế ScrollViewbằng android.support.v4.widget.NestedScrollViewcác tác phẩm trên tất cả các phiên bản. Bằng cách nào đó tôi đã bỏ lỡ giải pháp được chấp nhận thử nghiệm . Đã thêm phần này vào dự án github của tôi dưới dạng bản demo.)

Sau khi thử những thứ khác nhau, tôi đã tìm thấy cách giải quyết khắc phục vấn đề này.

Đây là cấu trúc bố trí của tôi một cách ngắn gọn:

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

Cách giải quyết là bọc RecyclerViewvới RelativeLayout. Đừng hỏi tôi làm thế nào tôi tìm thấy cách giải quyết này !!!¯\_(ツ)_/¯

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

Ví dụ hoàn chỉnh có sẵn trong dự án GitHub - https://github.com/amardeshbd/android-recycler-view-wrap-content

Dưới đây là bản chụp màn hình demo cho thấy bản sửa lỗi đang hoạt động:

Screencast


6
Cảm ơn người đàn ông .. nó giúp ích rất nhiều .. nhưng việc di chuyển trở nên khó khăn sau giải pháp của bạn vì vậy tôi đã đặt recyclerview.setNestedScrollingEnables (false); và bây giờ công việc của nó như một nét duyên dáng.
Sagar Chavada

4
Là phương pháp này tái chế quan điểm? nếu chúng ta có khoảng hàng trăm đối tượng được tái chế. Đây là hack không phải là giải pháp.
androidXP

1
Đúng, @androidXP đã đúng, bản hack này không phải là giải pháp cho một danh sách dài. Trường hợp sử dụng của tôi là mục cố định trong chế độ xem danh sách nhỏ hơn 10. Và như cách tôi tìm thấy cách giải quyết khác, tôi đã thử những thứ ngẫu nhiên, đây là một trong số đó :-)
Hossain Khan

perfect.solve vấn đề của tôi trong adnroid marshmallow và trên. ;)
Amir Ziarati

2
nếu tôi sử dụng giải pháp này, thì onBindView sẽ được gọi cho tất cả các mục trong danh sách, đây không phải là usecase của recyclerview.
thedarkpasbah

54

Mặc dù khuyến nghị rằng

bạn không bao giờ nên đặt chế độ xem có thể cuộn trong chế độ xem có thể cuộn khác

Là một lời khuyên âm thanh, tuy nhiên nếu bạn đặt chiều cao cố định trên chế độ xem của trình tái chế thì nó sẽ hoạt động tốt.

Nếu bạn biết chiều cao của bố trí mục bộ điều hợp, bạn có thể tính chiều cao của RecyclerView.

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

10
Làm thế nào để có được adapterItemSize trong recyclerview bất kỳ ý tưởng nào?
Krutik

1
Nó hoạt động như một say mê! Cần chỉnh sửa một chút: int viewHeight = adapterItemSize * adapterData.size (); recyclerView.getLayoutParams (). height = viewHeight;
Vaibhav Jani

3
Làm thế nào để tìm ra adapterItemSize?
droidster.me

2
@JoakimEngstrom Biến là adapterItemSizegì?
IgorGanapolsky

1
Tránh chế độ xem tái chế bên trong Chế độ xem cuộn, vì chế độ xem cuộn cung cấp cho không gian con vô hạn. Điều này gây ra chế độ xem của trình tái chế có quấn_content là chiều cao để đo vô hạn theo hướng dọc (cho đến mục cuối cùng của chế độ xem của trình tái chế). Thay vì sử dụng chế độ xem tái chế bên trong chế độ xem cuộn, chỉ sử dụng chế độ xem tái chế với các loại mục khác nhau. Với cách triển khai này, trẻ em của chế độ xem cuộn sẽ hoạt động như kiểu xem. Xử lý các khung nhìn bên trong khung nhìn tái chế.
abhishesh

28

Trong trường hợp cài đặt chiều cao cố định cho RecyclerView không hoạt động với ai đó (như tôi), đây là những gì tôi đã thêm vào giải pháp chiều cao cố định:

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
Thật vậy, điều này làm việc tốt cho tôi. Với vị trí này, tôi có thể di chuyển chế độ xem cuộn lên và xuống, và khi tôi chọn recyclerview để cuộn, nó sẽ ưu tiên hơn chế độ xem cuộn. Cảm ơn vì tiền boa
PGMacDesign

1
nó cũng hiệu quả với tôi, chỉ cần chắc chắn sử dụng getParent cho một đứa trẻ trực tiếp của scrollview
BigBen3216

Phương pháp này cũng hoạt động trên các bản phân phối cyanogenmod. Giải pháp chiều cao cố định trên cyanogenmod hoạt động, nhưng chỉ khi chiều cao cố định là chiều cao tuyệt đối của tất cả các mục trong danh sách, bất chấp quan điểm sử dụng recyclerview ở vị trí đầu tiên. Nâng cao.
HappyKatz

Tôi cũng cần recyclerView.setNestedScrollingEnables (false);
Analizer

25

Mới Thư viện hỗ trợ Android 23,2 phá được rằng vấn đề, bây giờ bạn có thể thiết lập wrap_content như chiều cao của bạn RecyclerViewvà làm việc một cách chính xác.

Thư viện hỗ trợ Android 23.2


cũng không chạy đúng cách (23.4.0)
xử vào

1
@behelit 23.4.0 có một số vấn đề code.google.com/p/android/issues/detail?id=210085#makechanges , sử dụng 23.2.1 thay vì
Samad

1
thậm chí không bay đúng vào ngày 25.0.1
maruti060385

15

RecyclerViews vẫn ổn để đưa vào ScrollView miễn là chúng không tự cuộn. Trong trường hợp này, nó có ý nghĩa để làm cho nó một chiều cao cố định.

Giải pháp thích hợp là sử dụng wrap_content trên chiều cao RecyclerView và sau đó triển khai một linearLayoutManager tùy chỉnh có thể xử lý gói chính xác.

Sao chép tuyến tính nàyLayoutManager vào dự án của bạn: https://github.com/serso/android-linear-layout-manager/blob/master/lib/src/main/java/org/solovyev/android/view/llm/LinearLayoutManager

Sau đó bọc RecyclerView:

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

Và thiết lập nó như vậy:

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

Chỉnh sửa: Điều này có thể gây ra các biến chứng khi cuộn vì RecyclerView có thể đánh cắp các sự kiện chạm của ScrollView. Giải pháp của tôi chỉ là bỏ RecyclerView trong tất cả và đi với linearLayout, các chương trình phỏng vấn thổi phồng theo chương trình và thêm chúng vào bố cục.


4
Bạn không thể gọi setNestedScrollingEnables (false) trên recyclerview?
Eshaan

14

Đối với ScrollView, bạn có thể sử dụng fillViewport=truevà thực hiện layout_height="match_parent"như bên dưới và đặt chế độ xem tái chế bên trong:

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

Không cần điều chỉnh chiều cao thêm thông qua mã.


Đã thử nghiệm làm việc tốt bằng cách sử dụng v23.2.1. Đã sử dụng nó để thêm bố trí trên recyclerview.
winsontan520

chỉ cuộn RecyclerView và ScrollView không cuộn
Samad

13

Tính toán RecyclerViewchiều cao bằng tay là không tốt, tốt hơn là sử dụng một tùy chỉnh LayoutManager.

Lý do cho vấn đề trên là bất kỳ quan điểm trong đó có nó cuộn ( ListView, GridView, RecyclerView) thất bại trong việc tính toán nó của chiều cao khi add như một đứa trẻ trong quan điểm khác có cuộn. Vì vậy, ghi đè onMeasurephương pháp của nó sẽ giải quyết vấn đề.

Vui lòng thay thế trình quản lý bố cục mặc định bằng bên dưới:

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

Làm thế nào tôi có thể gọi lớp này khi chúng tôi đặt dữ liệu thành bộ chuyển đổi?
Milan Gajera

11

Thử cái này. Câu trả lời rất muộn. Nhưng chắc chắn sẽ giúp được ai trong tương lai.

Đặt Scrollview của bạn thành NestedScrollView

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

Trong Recyclerview của bạn

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

8
sử dụng RecyclerView bên trong NestedScrollView đang gọi onBindView cho mọi mục trong danh sách ngay cả khi mục đó không hiển thị. Bất kỳ giải pháp cho vấn đề đó?
thedarkpasbah

8

CẬP NHẬT: câu trả lời này đã hết hạn ngay bây giờ vì có các widget như NestedScrollView và RecyclerView hỗ trợ cuộn lồng nhau.

bạn không bao giờ nên đặt chế độ xem có thể cuộn trong chế độ xem có thể cuộn khác!

tôi đề nghị bạn thực hiện chế độ xem tái chế bố cục chính của bạn và đặt chế độ xem của bạn dưới dạng các mục của chế độ xem tái chế.

hãy xem ví dụ này cho thấy cách sử dụng nhiều khung nhìn bên trong bộ điều hợp khung nhìn tái chế. liên kết đến ví dụ


Tôi có một trang có nhiều hơn một Recycler , có cách nào khác để thuyết phục điều này không? một số thứ như instagram hoặc google play phần bình luận tải thêm bản ghi khi bạn nhấp vào thêm bình luận
Ashkan

làm cho nó trở thành một trình tái chế duy nhất và đặt các khung nhìn của bạn làm các mục cho trình tái chế đó
EC84B4

6
Điều đó là vô nghĩa. Bạn có thể có RecyclerViews lồng nhau tốt.
IgorGanapolsky

Câu trả lời này đã lỗi thời. Chúng tôi có những thứ như NestedScrollView cho phép các chế độ xem cuộn được lồng nhau. RecyclerViews lồng nhau bây giờ cũng hoạt động.
Corey Horn

7

Nếu bạn đặt RecyclerViewbên trong NestedScrollViewvà kích hoạt recyclerView.setNestedScrollingEnabled(false);, cuộn sẽ hoạt động tốt .
Tuy nhiên, có một vấn đề

RecyclerView không tái chế

Ví dụ: RecyclerView(bên trong NestedScrollViewhoặc ScrollView) của bạn có 100 mục.
Khi Activitykhởi chạy, 100 mục sẽ tạo ( onCreateViewHolderonBindViewHoldertrong số 100 mục sẽ được gọi cùng lúc).
Ví dụ: đối với mỗi mục, bạn sẽ tải một hình ảnh lớn từ API => hoạt động được tạo -> 100 hình ảnh sẽ tải.
Nó làm cho hoạt động bắt đầu chậm và chậm trễ.
Giải pháp có thể :
- Suy nghĩ về việc sử dụngRecyclerView với nhiều loại.

Tuy nhiên, nếu trong trường hợp của bạn, chỉ có một vài mục trong RecyclerViewtái chế hoặc không tái chế không ảnh hưởng đến hiệu suất rất nhiều, bạn có thể sử dụng RecyclerViewbên trong ScrollViewđơn giản


Chỉ ra cách tôi có thể tái chế RecyclerView ngay cả trong ScollView? Cảm ơn!
Kẻ nói dối

2
@Liar, hiện tại không có cách nào để RecyclerViewtái chế sau khi đưa nó vào ScrollView. Nếu bạn muốn tái chế, hãy nghĩ về một cách tiếp cận khác (như sử dụng RecyclerView với nhiều loại)
Phan Văn Linh

4

Bạn có thể sử dụng theo cách này:

Thêm dòng này vào chế độ xem xml recyclerView của bạn:

        android:nestedScrollingEnabled="false"

Hãy thử nó, recyclerview sẽ được cuộn trơn tru với chiều cao linh hoạt

hy vọng điều này sẽ giúp.


2

Có vẻ như điều NestedScrollViewđó giải quyết vấn đề. Tôi đã thử nghiệm sử dụng bố cục này:

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

Và nó hoạt động mà không có vấn đề


Bro, Vẫn có cùng một vấn đề Sau khi đổi thành NestedScrollview từ Scrollview.
MohanRaj S

mmm ... bạn có thể chia sẻ một số mã ... Tôi không có vấn đề gì nhưng bạn không bao giờ có thể biết với loại vấn đề này
royB

làm cách nào tôi có thể chia sẻ mã của mình qua email hoặc qua tràn ngăn xếp
MohanRaj S

2
sử dụng mã này gọi onBindView cho tất cả các mục trong danh sách ngay cả khi các mục đó không hiển thị trong danh sách. Điều này đánh bại mục đích của recyclerview.
thedarkpasbah

2

Tôi đã sử dụng CustomLayoutManager để tắt RecyclerView Scrolling. Đồng thời không sử dụng Recycler View dưới dạng WrapContent, sử dụng dưới dạng 0dp, Trọng lượng = 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();
    }
}

Sử dụng CustomLayoutManager trong RecyclerView:

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

UI XML:

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

Tôi đã có cùng một vấn đề. Đó là những gì tôi đã thử và nó hoạt động. Tôi đang chia sẻ mã xml và java của tôi. Hy vọng điều này sẽ giúp được ai đó.

Đây là 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 >

Đây là mã java liên quan. Nó hoạt động như một say mê.

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

Điều gì về bố trí bên dưới RecyclerView?
Tapa Lưu

nó cũng hoạt động Bạn chỉ cần sử dụng. NestedScrollViewthay vì xem cuộn
Zeeshan Shabbir

Có, chỉ NestedScrollView. Nhưng giải pháp với NestedScrollView + RecyclView gọi các quần thể RecyclerView rất chậm (tôi nghĩ cho calcutaion của vị trí Y của chế độ xem đầu tiên sau RecyclerView)
Tapa Lưu


0

Trên thực tế mục đích chính của RecyclerViewlà để bù đắp ListViewScrollView. Thay vì làm những gì bạn thực sự đang làm: Có RecyclerViewmột ScrollView, tôi sẽ đề nghị chỉ có một RecyclerViewthứ có thể xử lý nhiều loại trẻ em.


1
Điều này sẽ chỉ hoạt động với điều kiện là con cái của bạn có thể được thu thập ngay khi bạn cuộn chúng ra khỏi tầm nhìn. Nếu bạn có con là mapFragments hoặc streetview, sẽ không có ý nghĩa gì khi chúng buộc phải tải lại mỗi lần chúng cuộn ra khỏi recyclerview. Nhúng chúng vào một scrollview và sau đó tạo ra một recyclerview ở phía dưới có ý nghĩa hơn sau đó.
Simon

1
@Simon có setIsRecyclable () tiện dụng trong ViewHolder
ernazm

RecyclerView thay thế ListView, nó không có nghĩa là thay thế ScrollView.
cyroxis

Nhận xét này xứng đáng tốt hơn. Đó là một giải pháp, và thậm chí tốt hơn so với giải pháp được chấp nhận.
Kai Wang

0

Trước tiên, bạn nên sử dụng NestedScrollViewthay vì ScrollViewvà đặt RecyclerViewbên trongNestedScrollView .

Sử dụng lớp bố trí tùy chỉnh để đo chiều cao và chiều rộng của màn hình:

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

Và thực hiện mã dưới đây trong hoạt động / đoạn của 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

Nếu RecyclerView chỉ hiển thị một hàng trong ScrollView. Bạn chỉ cần đặt chiều cao của hàng của bạn thành android:layout_height="wrap_content".


0

bạn cũng có thể ghi đè tuyến tínhLayoutManager để cuộn recyclerview trơn tru

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

0

Xin lỗi vì đến bữa tiệc muộn, nhưng có vẻ như có một giải pháp khác hoạt động hoàn hảo cho trường hợp bạn đề cập.

Nếu bạn sử dụng chế độ xem tái chế bên trong chế độ xem tái chế, nó có vẻ hoạt động hoàn toàn tốt. Cá nhân tôi đã thử và sử dụng nó, và nó dường như không hề chậm chạp và không bị giật. Bây giờ tôi không chắc liệu đây có phải là một thực tiễn tốt hay không, nhưng lồng nhiều chế độ xem tái chế, thậm chí chế độ xem cuộn lồng nhau chậm lại. Nhưng điều này dường như làm việc độc đáo. Hãy thử xem. Tôi chắc chắn làm tổ sẽ hoàn toàn ổn với điều này.


0

Một cách tiếp cận khác để giải quyết vấn đề là sử dụng ConstraintLayoutbên trong ScrollView:

<ScrollView>
  <ConstraintLayout> (this is the only child of ScrollView)
    <...Some Views...>
    <RecyclerView> (layout_height=wrap_content)
    <...Some Other Views...>

Nhưng tôi vẫn sẽ theo androidx.core.widget.NestedScrollView cách tiếp cận, được đề xuất bởi Yang Peiyong .


-2

** Giải pháp hiệu quả với tôi
Sử dụng NestedScrollView với chiều cao là quấn_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"

// Nội dung hàng của bạn ở đây


Một số ý kiến ​​về một câu trả lời tương tự nói rằng việc vô hiệu hóa cuộn lồng nhau đánh bại mục đích sử dụng RecyclerView, nghĩa là nó sẽ không tái chế các chế độ xem. Không chắc chắn làm thế nào để xác nhận điều đó.
jk7

1
Đặt NestedScrollingEnables = "false" khiến RecyclerView KHÔNG tái chế các khung nhìn của nó, ít nhất là trong thiết lập của tôi (RecyclerView bên trong NestedScrollView). Xác nhận bằng cách thêm RecyclerListener vào RecyclerView và đặt điểm dừng bên trong phương thức onViewRecycl () của nó.
jk7
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.