ScrollView中的RecyclerView无法正常工作

roy*_*oyB 240 android android-layout android-scrollview android-cardview android-recyclerview

我正在尝试在相同的布局中实现包含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>
Run Code Online (Sandbox Code Playgroud)

问题:我可以滚动直到最后一个元素 ScrollView

我试过的事情:

  1. 里面的卡片视图ScrollView(现在ScrollView包含RecyclerView) - 可以看到卡片直到RecyclerView
  2. 最初的想法是实现这个viewGroup,RecyclerView而不是ScrollView其中一个视图类型是,CardView但我得到了与完全相同的结果ScrollView

Yan*_*ong 591

NestedScrollView而不是ScrollView

有关更多信息,请参阅NestedScrollView参考文档.

并添加recyclerView.setNestedScrollingEnabled(false);到您的RecyclerView

  • 适用于:android.support.v4.widget.NestedScrollView (22认同)
  • 保持``android:layout_height ="wrap_content"``为ViewHolder充气的布局 (7认同)
  • 它对我有用,但请记住,recyclerView 中的项目没有被回收。 (7认同)
  • 您也可以将android:nestedScrollingEnabled =“ false”添加到XML而不是recyclerView.setNestedScrollingEnabled(false);。 (5认同)
  • 在一个复杂的布局中,`NestedScrollView`对我来说不像"ScrollView".在不使用`NestedScrollView`的情况下搜索解决方案 (3认同)
  • 现在使用的正确包是“androidx.core.widget.NestedScrollView” (3认同)

Hos*_*han 92

我知道我在游戏中已经迟到了,但即使谷歌已经修复了这个问题,这个问题仍然存在 android.support.v7.widget.RecyclerView

我现在得到的问题是RecyclerViewlayout_height=wrap_content没有把所有的项目问题的高度内ScrollView发生在棉花糖和牛轧糖+(API 23,24,25)版本.
(UPDATE:更换ScrollViewandroid.support.v4.widget.NestedScrollView.在所有版本的作品我种种原因错过了测试接受的解决方案在我的GitHub项目作为演示添加这个.)

尝试不同的东西后,我找到了修复此问题的解决方法.

简而言之,这是我的布局结构:

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

解决办法是在包装的RecyclerViewRelativeLayout.不要问我怎么找到这个解决方法!¯\_(?)_/¯

<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>
Run Code Online (Sandbox Code Playgroud)

GitHub项目提供了完整的示例- https://github.com/amardeshbd/android-recycler-view-wrap-content

这是一个演示截屏视频,显示了修复操作:

截屏

  • 谢谢man ..它的帮助很多..但是在你的解决方案后滚动变得很难,所以我设置了recyclerview.setNestedScrollingEnabled(false); 现在它的工作就像一个魅力. (6认同)
  • 这种方法是回收视图吗?如果我们有大约数百个对象要回收.这是黑客无法解决的问题. (4认同)
  • 是的,@androidXP 是对的,这个 hack 并不是一个长列表的解决方案。我的用例是列表视图中的固定项目,少于 10 个。至于我如何找到其他解决方法,我正在尝试随机的事情,这就是其中之一:-) (2认同)
  • 如果我使用这个解决方案,那么onBindView将被调用列表中的所有项目,这不是recyclerview的用例. (2认同)

Joa*_*rom 53

虽然建议说

你永远不应该在另一个可滚动的视图中放置一个可滚动的视图

这是一个合理的建议,但是如果你在Recycler视图上设置一个固定的高度,它应该可以正常工作.

如果您知道适配器项目布局的高度,则可以计算RecyclerView的高度.

int viewHeight = adapterItemSize * adapterData.size();
recyclerView.getLayoutParams().height = viewHeight;
Run Code Online (Sandbox Code Playgroud)

  • 如何在recyclerview中获取adapterItemSize的想法? (10认同)
  • 如何找到adapterItemSize? (3认同)
  • @JoakimEngstrom什么是`adapterItemSize`变量? (2认同)
  • 避免在滚动视图中使用回收器视图,因为滚动视图为其子视图提供了无限的空间。这导致以wrap_content为高度的回收器视图在垂直方向上无限测量(直到回收器视图的最后一项)。不要在滚动视图中使用回收器视图,而应仅使用具有不同项目类型的回收器视图。通过此实现,滚动视图的子级将表现为视图类型。在回收器视图中处理这些视图类型。 (2认同)

小智 27

如果为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) {

    }
});
Run Code Online (Sandbox Code Playgroud)


kev*_*s14 23

新的Android支持库23.2解决了这个问题,您现在可以将wrap_content设置为您的高度RecyclerView并正常工作.

Android支持库23.2


jca*_*ady 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"/>
Run Code Online (Sandbox Code Playgroud)

并设置如下:

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

编辑:这可能导致滚动的复杂化,因为RecyclerView可以窃取ScrollView的触摸事件.我的解决方案就是放弃所有的RecyclerView并使用LinearLayout,以编程方式夸大子视图,并将它们添加到布局中.

  • 你不能在recyclerview上调用setNestedScrollingEnabled(false)吗? (4认同)

mus*_*taq 14

因为ScrollView,您可以使用fillViewport=true和制作layout_height="match_parent"如下,并将Recycler视图放入其中:

<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>
Run Code Online (Sandbox Code Playgroud)

无需通过代码进一步调整高度.


Aru*_*ney 13

RecyclerView手动计算高度不好,更好的是使用自定义LayoutManager.

原因上面的问题是它有它的滚动(任何观点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");
    }
}
}
Run Code Online (Sandbox Code Playgroud)


Ran*_*mar 11

试试这个.答案很晚.但未来肯定会帮助任何人.

将Scrollview设置为NestedScrollView

<android.support.v4.widget.NestedScrollView>
 <android.support.v7.widget.RecyclerView>
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.NestedScrollView>
Run Code Online (Sandbox Code Playgroud)

在您的Recyclerview中

recyclerView.setNestedScrollingEnabled(false); 
recyclerView.setHasFixedSize(false);
Run Code Online (Sandbox Code Playgroud)

  • 在NestedScrollView中使用RecyclerView即使项目不可见,也会为列表中的每个项目调用onBindView.该问题的解决方案是什么? (7认同)

EC8*_*4B4 8

更新:这个答案已经过时了,因为有像NestedScrollView和RecyclerView这样的小部件支持嵌套滚动.

你永远不应该在另一个可滚动的视图中放置一个可滚动的视图!

我建议你制作主要布局回收者视图,并将你的视图作为回收者视图的项目.

看一下这个例子,它展示了如何在recycler视图适配器中使用多个视图. 链接到示例

  • 那是胡说八道.您可以使用嵌套的RecyclerViews. (6认同)

iDe*_*per 6

将此行添加到您的RecyclerViewxml 视图中:

android:nestedScrollingEnabled="false"
Run Code Online (Sandbox Code Playgroud)

并且您的RecyclerView将以灵活的高度平滑滚动。

希望能帮助到你。


Pha*_*inh 6

如果你把RecyclerView里面NestedScrollView并启用recyclerView.setNestedScrollingEnabled(false);,滚动将运作良好.
但是,有一个问题

RecyclerView 不回收

例如,您的RecyclerView(内部NestedScrollViewScrollView)有100项.
Activity启动,100项将创建(onCreateViewHolderonBindViewHolder100项目都将在同一时间调用).
例如,对于每个项目,您将从API =>活动创建加载大图像 - >将加载100个图像.
它使启动活动缓慢和滞后.
可能的解决方案:
- 考虑使用RecyclerView多种类型.

但是,如果在你的情况下,只有少数项目RecyclerView回收不回收不会影响性能很多,你可以使用RecyclerView内部ScrollView简单

  • @Liar,目前尚无办法将`RecyclerView`回收到ScrollView之后。如果要回收,请考虑另一种方法(例如,将RecyclerView与多种类型一起使用) (2认同)

roy*_*oyB 5

看来确实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>

</android.support.v4.widget.NestedScrollView>
Run Code Online (Sandbox Code Playgroud)

而且它的工作没有问题。

  • 使用此代码为列表中的所有项目调用 onBindView,即使这些项目在列表中不可见。这违背了 recyclerview 的目的。 (4认同)

归档时间:

查看次数:

165711 次

最近记录:

6 年,5 月 前