RecyclerView ภายใน ScrollView ไม่ทำงาน


276

ฉันพยายามใช้เค้าโครงซึ่งมี RecyclerView และ ScrollView ที่เค้าโครงเดียวกัน

แม่แบบเค้าโครง:

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

ปัญหา: ฉันสามารถเลื่อนไปจนถึงองค์ประกอบสุดท้ายของ ScrollView

สิ่งที่ฉันพยายาม:

  1. มุมมองการ์ดภายในScrollView(ตอนนี้ScrollViewมีRecyclerView) - สามารถดูการ์ดได้จนถึงRecyclerView
  2. ความคิดเริ่มแรกคือการใช้สิ่งนี้viewGroupโดยใช้RecyclerViewแทนที่จะScrollViewเป็นประเภทการดูแบบใดแบบหนึ่งCardViewแต่ฉันได้ผลลัพธ์เดียวกันกับScrollView

ชำระเงินวิธีนี้: stackoverflow.com/a/21878703/684582
Alécio Carvalho

15
วิธีง่ายๆในหลายกรณีเหล่านี้คือการใช้NestedScrollViewแทนเพราะมันจัดการกับปัญหาการเลื่อนจำนวนมาก
Richard Le Mesurier

Richard ตอบคำถามของคุณในเดือนกุมภาพันธ์ ใช้แทนNestedScrollView ScrollViewนั่นคือสิ่งที่มันมีไว้เพื่อ
Mike M.

2
ไม่เปลี่ยนอะไรสำหรับฉัน
ลุคอัลลิสัน

2
สำหรับการอ้างอิงในอนาคตหากใครประสบปัญหาที่คล้ายกันเฉพาะอุปกรณ์ marshmallow / nougat (API 23, 24) ให้ตรวจสอบวิธีแก้ปัญหาของฉันที่stackoverflow.com/a/38995399/132121
Hossain Khan

คำตอบ:


653

ใช้NestedScrollViewแทนScrollView

โปรดอ่านเอกสารอ้างอิง NestedScrollViewสำหรับข้อมูลเพิ่มเติม

และเพิ่มrecyclerView.setNestedScrollingEnabled(false);ของคุณRecyclerView


24
ทำงานร่วมกับ: android.support.v4.widget.NestedScrollView
Cocorico

7
เก็บandroid:layout_height="wrap_content"เลย์เอาท์ที่สูงเกินจริงสำหรับ ViewHolder
Sarath Babu

4
ในรูปแบบที่ซับซ้อนล่าช้าสำหรับผมที่แตกต่างจากNestedScrollView ScrollViewค้นหาวิธีแก้ปัญหาโดยไม่ใช้NestedScrollView
Roman Samoylenko

7
นอกจากนี้คุณสามารถเพิ่ม Android: nestedScrollingEnabled = "false" ไปยัง XML แทน recyclerView.setNestedScrollingEnabled (false);
kostyabakay

2
มันใช้งานได้สำหรับฉัน แต่โปรดจำไว้ว่ารายการภายใน recyclerView ไม่ได้รับการรีไซเคิล
Mostafa Arian Nejad

102

ฉันรู้ว่าฉันมาช้าเกม แต่ปัญหายังคงมีอยู่แม้หลังจากที่ Google ได้ทำการแก้ไข android.support.v7.widget.RecyclerView

ปัญหาที่ฉันได้รับในขณะนี้คือRecyclerViewมีlayout_height=wrap_contentไม่ได้ใช้ความสูงของรายการทั้งหมดออกภายในScrollViewที่เดียวที่เกิดขึ้นในขนมหวานและตังเม + (API 23, 24, 25) รุ่น
(อัปเดต: แทนที่ScrollViewด้วยandroid.support.v4.widget.NestedScrollViewผลงานในทุกรุ่นฉันไม่ได้รับการยอมรับการแก้ปัญหาการทดสอบเพิ่มสิ่งนี้ในโครงการ Github ของฉันเป็นตัวอย่าง

หลังจากลองสิ่งที่แตกต่างฉันพบวิธีแก้ปัญหาที่แก้ไขปัญหานี้

นี่คือโครงสร้างรูปแบบของฉันสั้น:

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

วิธีแก้ปัญหาคือห่อด้วยRecyclerView RelativeLayoutอย่าถามฉันว่าฉันพบวิธีแก้ปัญหานี้ !!!¯\_(ツ)_/¯

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

ตัวอย่างที่สมบูรณ์มีอยู่ในโครงการGitHub - https://github.com/amardeshbd/android-recycler-view-wrap-content

นี่คือหน้าจอสาธิตที่แสดงการแก้ไขในขณะใช้งาน:

screencast


6
ขอบคุณผู้ชาย .. ช่วยได้มาก .. แต่การเลื่อนเป็นเรื่องยากหลังจากที่โซลูชันของคุณดังนั้นฉันจึงตั้งค่า recyclerview.setNestedScrollingEnabled (false); และตอนนี้มันทำงานเหมือนมีเสน่ห์
Sagar Chavada

4
วิธีนี้รีไซเคิลมุมมองหรือไม่ ถ้าเรามีวัตถุหลายร้อยชิ้นที่จะนำไปรีไซเคิล แฮ็คนี้ไม่ได้แก้ปัญหา
androidXP

1
ใช่ @androidXP ถูกต้องแฮ็คนี้ไม่ใช่คำตอบสำหรับรายชื่อที่ยาว กรณีการใช้งานของฉันคือรายการคงที่ในมุมมองรายการซึ่งน้อยกว่า 10 และสำหรับวิธีการที่ฉันพบวิธีแก้ปัญหาอื่นฉันพยายามสุ่มสิ่งนี่เป็นหนึ่งในนั้น :-)
Hossain Khan

perfect.solved ปัญหาของฉันใน adnroid marshmallow และด้านบน ;)
Amir Ziarati

2
ถ้าฉันใช้โซลูชันนี้ onBindView จะได้รับการเรียกสำหรับรายการทั้งหมดในรายการซึ่งไม่ใช่ usecase ของ recyclerview
thedarkpassenger

54

แม้ว่าข้อเสนอแนะนั้น

คุณไม่ควรใส่มุมมองที่เลื่อนได้ในมุมมองเลื่อนอื่น

เป็นคำแนะนำเสียง แต่ถ้าคุณตั้งค่าความสูงคงที่ในมุมมองรีไซเคิลควรใช้งานได้ดี

หากคุณทราบความสูงของโครงร่างรายการอะแดปเตอร์คุณก็สามารถคำนวณความสูงของ RecyclerView

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

10
วิธีการรับ adapterItemSize ในมุมมองแนวคิดรีไซเคิล
Krutik

1
มันใช้งานได้เหมือนมีเสน่ห์! การแก้ไขเล็กน้อยควรจะเป็น: int viewHeight = adapterItemSize * adapterData.size (); recyclerView.getLayoutParams (). height = viewHeight;
Vaibhav Jani

3
จะหาอะแดปเตอร์ItemSizeได้อย่างไร?
droidster.me

2
@JoakimEngstrom adapterItemSizeตัวแปรคืออะไร
IgorGanapolsky

1
หลีกเลี่ยงมุมมองรีไซเลอร์ภายในมุมมองเลื่อนเนื่องจากมุมมองเลื่อนให้พื้นที่ไม่มีที่สิ้นสุดของลูก สิ่งนี้ทำให้มุมมองรีไซเลอร์มี wrap_content เป็นความสูงในการวัดอนันต์ในแนวตั้ง (จนถึงรายการสุดท้ายของมุมมองรีไซเลอร์) แทนที่จะใช้มุมมองรีไซเลอร์ภายในมุมมองเลื่อนใช้เฉพาะมุมมองรีไซเลอร์ที่มีประเภทรายการต่าง ๆ ด้วยการใช้งานนี้ลูก ๆ ของมุมมองเลื่อนจะทำงานเหมือนกับชนิดมุมมอง จัดการ viewtypes เหล่านั้นในมุมมองรีไซเคิล
abhishesh

28

ในกรณีที่การตั้งค่าความสูงคงที่สำหรับ RecyclerView ไม่ทำงานสำหรับบางคน (เช่นฉัน) นี่คือสิ่งที่ฉันได้เพิ่มลงในโซลูชันความสูงคงที่:

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
แน่นอนว่ามันใช้ได้ดีสำหรับฉัน เมื่อใช้สิ่งนี้ฉันสามารถเลื่อนมุมมองเลื่อนขึ้นและลงและเมื่อฉันเลือก recyclerview สำหรับการเลื่อนจะมีความสำคัญมากกว่ามุมมองเลื่อน ขอบคุณสำหรับเคล็ดลับ
PGMacDesign

1
มันก็ใช้งานได้สำหรับฉันด้วยอย่าลืมใช้ getParent กับลูกโดยตรงของ scrollview
BigBen3216

วิธีนี้ใช้ได้กับการกระจายตัวของไซยาโนเจนโมดเช่นกัน โซลูชันความสูงคงที่ใน cyanogenmod ใช้งานได้เฉพาะในกรณีที่ความสูงคงที่คือความสูงแน่นอนของรายการทั้งหมดในรายการซึ่งท้าทายจุดที่ใช้ recyclerview ในตอนแรก upvoted
HappyKatz

ฉันยังต้องการ recyclerView.setNestedScrollingEnabled (false);
วิเคราะห์

25

ไลบรารีการสนับสนุน Androidใหม่23.2แก้ปัญหานั้นตอนนี้คุณสามารถตั้งค่าwrap_contentเป็นความสูงของคุณRecyclerViewและทำงานอย่างถูกต้อง

ห้องสมุดสนับสนุน Android 23.2


ไม่พุ่งอย่างถูกต้องเช่นกัน (23.4.0)
behelit

1
@behelit 23.4.0 มีปัญหาบางอย่างcode.google.com/p/android/issues/detail?id=210085#makechangesให้ใช้ 23.2.1 แทน
Samad

1
ไม่กระโจนอย่างถูกต้องในวันที่ 25.0.1
maruti060385

15

RecyclerViews นั้นใช้ได้ใน ScrollViews ตราบใดที่พวกเขาไม่ได้เลื่อนตัวเอง ในกรณีนี้มันสมเหตุสมผลที่จะทำให้ความสูงคงที่

ทางออกที่เหมาะสมคือการใช้wrap_contentกับความสูงของ RecyclerView จากนั้นใช้ LinearLayoutManager แบบกำหนดเองที่สามารถจัดการการห่อได้อย่างเหมาะสม

คัดลอก LinearLayoutManager นี้ลงในโครงการของคุณ: https://github.com/serso/android-linear-layout-manager/blob/master/lib/src/main/java/org/solovyev/android/views/llm/LinearLayoutManager.java

จากนั้นให้ปิด RecyclerView:

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

และตั้งค่าให้เป็นเช่นนั้น:

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

แก้ไข: สิ่งนี้อาจทำให้เกิดความยุ่งยากในการเลื่อนเนื่องจาก RecyclerView สามารถขโมยเหตุการณ์การสัมผัสของ ScrollView โซลูชันของฉันเป็นเพียงการทิ้ง RecyclerView ไว้ทั้งหมดแล้วไปกับ LinearLayout ขยายตัวอย่างย่อยของโปรแกรมและเพิ่มลงในเค้าโครง


4
คุณไม่สามารถเรียก setNestedScrollingEnabled (false) บน recyclerview ได้หรือไม่
Eshaan

14

สำหรับScrollView, คุณสามารถใช้fillViewport=trueและทำlayout_height="match_parent"ตามด้านล่างและใส่มุมมองรีไซเคิลใน:

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

ไม่จำเป็นต้องปรับความสูงเพิ่มเติมผ่านรหัส


ผ่านการทดสอบการทำงานดีโดยใช้ v23.2.1 ใช้เพื่อเพิ่มเลย์เอาต์เหนือ recyclerview
winsontan520

เพียงแค่เลื่อน RecyclerView และ ScrollView ไม่เลื่อน
Samad

13

การคำนวณของความสูงของตนเองไม่ดีดีคือการใช้ที่กำหนดเองRecyclerViewLayoutManager

เหตุผลสำหรับปัญหาดังกล่าวคือมุมมองใด ๆ ที่มีมันเลื่อน ( ListView, GridView, RecyclerView) ล้มเหลวในการคำนวณความสูงของมันเมื่อเพิ่มเป็นเด็กในอีกมุมมองหนึ่งที่มีการเลื่อน ดังนั้นการเอาชนะonMeasureวิธีนี้จะช่วยแก้ปัญหาได้

โปรดแทนที่ผู้จัดการเค้าโครงเริ่มต้นด้วยด้านล่าง:

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

ฉันจะเรียกคลาสนี้ได้อย่างไรเมื่อเราตั้งค่าข้อมูลเป็นอะแดปเตอร์
มิลาน Gajera

11

ลองสิ่งนี้ คำตอบที่ล่าช้ามาก แต่แน่นอนว่าจะช่วยทุกคนในอนาคต

ตั้งค่า Scrollview ของคุณเป็น NestedScrollView

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

ใน Recyclerview ของคุณ

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

8
การใช้ RecyclerView ภายใน NestedScrollView กำลังเรียกใช้ BindView สำหรับทุกรายการในรายการแม้ว่ารายการนั้นจะมองไม่เห็นก็ตาม ทางออกสำหรับปัญหานั้นคืออะไร?
ผู้โดยสารที่ใช้เส้นทาง

8

UPDATE: คำตอบนี้ล้าสมัยไปแล้วในขณะนี้เนื่องจากมีวิดเจ็ตเช่น NestedScrollView และ RecyclerView ที่รองรับการเลื่อนแบบซ้อน

คุณไม่ควรใส่มุมมองที่เลื่อนได้ในมุมมองเลื่อนอื่น!

ฉันขอแนะนำให้คุณสร้างมุมมองหลักของนักรีไซเคิลและวางมุมมองของคุณเป็นรายการในมุมมองของนักรีไซเคิล

ลองดูตัวอย่างนี้มันแสดงวิธีใช้หลายมุมมองภายในอะแดปเตอร์มุมมองรีไซเคิล ลิงก์ไปยังตัวอย่าง


ฉันมีเพจที่มีRecyclerมากกว่าหนึ่งหน้ามีวิธีอื่นในการโน้มน้าวสิ่งนี้หรือไม่? บางสิ่งเช่นอินสตาแกรมหรือgoogle playส่วนความคิดเห็นที่โหลดเรกคอร์ดเพิ่มเติมเมื่อคุณคลิกที่ความคิดเห็นเพิ่มเติม
Ashkan

ทำให้ recyclerView เป็นมุมมองเดียวและทำให้มุมมองของคุณเป็นรายการสำหรับ recycler นั้น
EC84B4

6
นั่นเป็นเรื่องไร้สาระ คุณสามารถซ้อน RecyclerViews ได้ดี
IgorGanapolsky

คำตอบนี้ล้าสมัยแล้ว เรามีหลายอย่างเช่น NestedScrollView ที่อนุญาตให้ใช้มุมมองแบบเลื่อนได้แบบซ้อน RecyclerViews ที่ซ้อนกันก็ทำงานเช่นกัน
คอเรย์ฮอร์น

7

ถ้าคุณใส่RecyclerViewข้างในNestedScrollViewและเปิดใช้recyclerView.setNestedScrollingEnabled(false);งานการเลื่อนจะทำงานได้ดี
อย่างไรก็ตามมีปัญหา

RecyclerView อย่ารีไซเคิล

ตัวอย่างเช่นRecyclerView(ภายในNestedScrollViewหรือScrollView) มี 100 รายการ
เมื่อActivityเปิดตัว 100 รายการจะสร้าง ( onCreateViewHolderและonBindViewHolder100 รายการจะถูกเรียกพร้อมกัน)
ตัวอย่างสำหรับแต่ละรายการคุณจะโหลดภาพขนาดใหญ่จาก API => กิจกรรมที่สร้าง -> ภาพ 100 ภาพจะโหลด
มันทำให้กิจกรรมเริ่มช้าและล้าหลัง
ทางออกที่เป็นไปได้ :
- คิดเกี่ยวกับการใช้RecyclerViewกับหลายประเภท

แต่ถ้าในกรณีของคุณมีเพียงไม่กี่รายการในRecyclerViewและรีไซเคิลหรือไม่รีไซเคิลไม่ได้ส่งผลกระทบต่อผลการดำเนินงานเป็นจำนวนมากคุณสามารถใช้RecyclerViewภายในScrollViewสำหรับง่าย


แสดงว่าฉันจะทำให้ RecyclerView รีไซเคิลได้อย่างไรภายใน ScollView ขอบคุณ!
คนโกหก

2
@Liar ปัจจุบันไม่มีวิธีRecyclerViewรีไซเคิลหลังจากนำไปScrollViewใช้ หากคุณต้องการรีไซเคิลให้คิดถึงวิธีการอื่น (เช่นการใช้ RecyclerView ที่มีหลายประเภท)
Phan Van Linh

4

คุณสามารถใช้วิธีนี้:

เพิ่มบรรทัดนี้ในมุมมอง xml recycler ของคุณ:

        android:nestedScrollingEnabled="false"

ลอง recyclerview จะเลื่อนอย่างราบรื่นด้วยความสูงที่ยืดหยุ่น

หวังว่าสิ่งนี้จะช่วยได้


2

ดูเหมือนว่าNestedScrollViewจะแก้ปัญหาได้ ฉันทดสอบโดยใช้เค้าโครงนี้:

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

และมันทำงานได้โดยไม่มีปัญหา


พี่ชายยังมีปัญหาเดียวกันหลังจากเปลี่ยนเป็น NestedScrollview จาก Scrollview
MohanRaj S

mmm ... คุณแชร์รหัสได้ไหม ... ฉันไม่มีปัญหา แต่คุณไม่มีทางรู้ปัญหาเหล่านี้ได้เลย
royB

ฉันจะแบ่งปันรหัสของฉันทางอีเมลหรือผ่านการล้นสแต็คได้อย่างไร
MohanRaj S

2
การใช้รหัสนี้จะเรียกใช้ BindView สำหรับรายการทั้งหมดในรายการแม้ว่ารายการเหล่านั้นจะไม่ปรากฏในรายการ สิ่งนี้เอาชนะวัตถุประสงค์ของการรีไซเคิล
thedarkpassenger

2

ฉันใช้ CustomLayoutManager เพื่อปิดการใช้งาน RecyclerView Scrolling และอย่าใช้ Recycler View เป็น WrapContent ใช้เป็น 0dp, Weight = 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();
    }
}

ใช้ CustomLayoutManager ใน 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

ฉันมีปัญหาเดียวกัน นั่นคือสิ่งที่ฉันพยายามและใช้งานได้ ฉันแบ่งปันรหัส XML และจาวาของฉัน หวังว่านี่จะช่วยใครซักคน

นี่คือ 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 >

นี่คือรหัสจาวาที่เกี่ยวข้อง มันทำงานเหมือนจับใจ

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

สิ่งที่เกี่ยวกับรูปแบบด้านล่าง RecyclerView
Tapa บันทึก

ที่ใช้งานได้ดีเช่นกัน คุณเพียงแค่ต้องใช้ NestedScrollViewแทนการเลื่อนดู
Zeeshan Shabbir

ใช่เพียง NestedScrollView แต่การแก้ปัญหาด้วย NestedScrollView + RecycleView เรียกประชากรที่ช้ามากของ RecyclerView (ฉันคิดว่าการคำนวณตำแหน่ง Y ของมุมมองแรกหลังจาก RecyclerView)
Tapa บันทึก


0

ที่จริงวัตถุประสงค์หลักของRecyclerViewคือการชดเชยและListView ScrollViewแทนที่จะทำในสิ่งที่คุณทำจริง ๆ : การมีกRecyclerView. ScrollViewฉันขอแนะนำให้มีเพียงอันเดียวRecyclerViewที่สามารถจัดการกับเด็กได้หลายประเภท


1
สิ่งนี้จะทำงานได้ก็ต่อเมื่อลูก ๆ ของคุณสามารถเก็บเป็น Garbaged ทันทีที่คุณเลื่อนพวกมันออก หากคุณมีลูกที่เป็น mapFragments หรือสตรีทวิวมันไม่เข้าท่าเพราะพวกเขาถูกบังคับให้โหลดซ้ำทุกครั้งที่เลื่อนออกจากมุมมองรีไซเคิล ฝังไว้ใน scrollview แล้วสร้าง recyclerview ที่ด้านล่างเหมาะสมกว่า
Simon

1
@Simon มีประโยชน์ setIsRecyclable () ใน ViewHolder
ernazm

RecyclerView แทนที่ ListView มันไม่ได้มีไว้เพื่อแทนที่ ScrollView
cyroxis

ความคิดเห็นนี้สมควรได้รับดีกว่า มันเป็นวิธีการแก้ปัญหาและดีกว่าที่ยอมรับได้
ไก่วัง

0

ก่อนอื่นคุณควรใช้NestedScrollViewแทนScrollViewแล้วใส่RecyclerViewข้างในNestedScrollViewภายใน

ใช้คลาสเลย์เอาต์ที่กำหนดเองเพื่อวัดความสูงและความกว้างของหน้าจอ:

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

และใช้โค้ดด้านล่างในกิจกรรม / ส่วนของ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

หาก RecyclerView แสดงเพียงหนึ่งแถวภายใน ScrollView android:layout_height="wrap_content"คุณเพียงแค่ต้องมีความสูงของแถวชุดของคุณไป


0

นอกจากนี้คุณยังสามารถแทนที่ LinearLayoutManager เพื่อให้การหมุนภาพรีไซเคิลได้อย่างราบรื่น

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

0

ขออภัยที่มาสายไปงานเลี้ยง แต่ดูเหมือนว่าจะมีวิธีแก้ไขปัญหาอื่นซึ่งทำงานได้อย่างสมบูรณ์แบบสำหรับกรณีที่คุณกล่าวถึง

หากคุณใช้มุมมองรีไซเลอร์ภายในมุมมองรีไซเลอร์ดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบ ฉันได้ลองและใช้มันเป็นการส่วนตัวและดูเหมือนว่าจะไม่มีความช้าและไม่มีอาการกระตุกใด ๆ เลย ตอนนี้ฉันไม่แน่ใจว่านี่เป็นวิธีปฏิบัติที่ดีหรือไม่ แต่การซ้อนมุมมองรีไซเคิลหลาย ๆ ตัวแม้แต่การเลื่อนดูแบบซ้อนจะทำให้ช้าลง แต่ดูเหมือนว่าจะทำงานได้ดี โปรดลองดู ฉันแน่ใจว่าการทำรังจะดีอย่างสมบูรณ์แบบด้วยสิ่งนี้


0

อีกวิธีในการแก้ไขปัญหาคือใช้ConstraintLayoutภายในScrollView:

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

แต่ฉันยังคงติดอยู่กับandroidx.core.widget.NestedScrollView วิธีการที่เสนอโดยยาง Peiyong


-2

** โซลูชันที่ใช้งานได้สำหรับฉัน
ใช้ NestedScrollView ที่มีความสูงเป็น 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"

// เนื้อหาแถวของคุณอยู่ที่นี่


ความคิดเห็นบางคำตอบที่คล้ายกันบอกว่าการปิดใช้งานการเลื่อนแบบซ้อนจะเอาชนะวัตถุประสงค์ของการใช้ RecyclerView นั่นคือมันจะไม่รีไซเคิลมุมมอง ไม่แน่ใจว่าจะยืนยันได้อย่างไร
jk7

1
การตั้งค่า nestedScrollingEnabled = "false" ทำให้ RecyclerView ไม่รีไซเคิลมุมมองอย่างน้อยที่สุดในการตั้งค่าของฉัน (RecyclerView ภายใน NestedScrollView) ยืนยันโดยการเพิ่ม RecyclerListener ไปที่ RecyclerView และตั้งค่าเบรกพอยต์ภายในเมธอด onViewRecycled ()
jk7
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.