RecyclerView inside ScrollView is not working

前端 未结 26 1511
梦如初夏
梦如初夏 2020-11-22 05:37

I\'m trying to implement a layout which contains RecyclerView and ScrollView at the same layout.

Layout template:


    

        
相关标签:
26条回答
  • 2020-11-22 06:12

    The best solution is to keep multiple Views in a Single View / View Group and then keep that one view in the SrcollView. ie.

    Format -

    <ScrollView> 
      <Another View>
           <RecyclerView>
           <TextView>
           <And Other Views>
      </Another View>
    </ScrollView>
    

    Eg.

    <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView           
                  android:text="any text"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"/>
    
    
            <TextView           
                  android:text="any text"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"/>
     </ScrollView>
    

    Another Eg. of ScrollView with multiple Views

    <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:orientation="vertical"
                android:layout_weight="1">
    
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/imageView"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#FFFFFF"
                    />
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingHorizontal="10dp"
                    android:orientation="vertical">
    
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/CategoryItem"
                        android:textSize="20sp"
                        android:textColor="#000000"
                        />
    
                    <TextView
                        android:textColor="#000000"
                        android:text="₹1000"
                        android:textSize="18sp"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"/>
                    <TextView
                        android:textColor="#000000"
                        android:text="so\nugh\nos\nghs\nrgh\n
                        sghs\noug\nhro\nghreo\nhgor\ngheroh\ngr\neoh\n
                        og\nhrf\ndhog\n
                        so\nugh\nos\nghs\nrgh\nsghs\noug\nhro\n
                        ghreo\nhgor\ngheroh\ngr\neoh\nog\nhrf\ndhog"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"/>
    
                 </LinearLayout>
    
            </LinearLayout>
    
    </ScrollView>
    
    0 讨论(0)
  • 2020-11-22 06:14

    This does the trick:

    recyclerView.setNestedScrollingEnabled(false);
    
    0 讨论(0)
  • 2020-11-22 06:15

    Although the recommendation that

    you should never put a scrollable view inside another scrollable view

    Is a sound advice, however if you set a fixed height on the recycler view it should work fine.

    If you know the height of the adapter item layout you could just calculate the height of the RecyclerView.

    int viewHeight = adapterItemSize * adapterData.size();
    recyclerView.getLayoutParams().height = viewHeight;
    
    0 讨论(0)
  • 2020-11-22 06:15

    Sorry being late to the party, but it seems like there is another solution which works perfectly for the case you mentioned.

    If you use a recycler view inside a recycler view, it seems to work perfectly fine. I have personally tried and used it, and it seems to give no slowness and no jerkyness at all. Now I am not sure if this is a good practice or not, but nesting multiple recycler views , even nested scroll view slows down. But this seems to work nicely. Please give it a try. I am sure nesting is going to be perfectly fine with this.

    0 讨论(0)
  • 2020-11-22 06:17

    Calculating RecyclerView's height manually is not good, better is to use a custom LayoutManager.

    The reason for above issue is any view which has it's scroll(ListView, GridView, RecyclerView) failed to calculate it's height when add as a child in another view has scroll. So overriding its onMeasure method will solve the issue.

    Please replace the default layout manager with the below:

    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");
        }
    }
    }
    
    0 讨论(0)
  • 2020-11-22 06:17

    You can use this way either :

    Add this line to your recyclerView xml view :

            android:nestedScrollingEnabled="false"
    

    try it ,recyclerview will be smoothly scrolled with flexible height

    hope this helped .

    0 讨论(0)
提交回复
热议问题