RecyclerView внутри ScrollView не работает

Я пытаюсь реализовать макет, который содержит RecyclerView и ScrollView в том же макете.

Макет:

  ...    

Проблемы: я могу прокручивать до последнего элемента ScrollView

Вещи, которые я пробовал:

  1. вид карты внутри ScrollView (теперь ScrollView содержит RecyclerView ) – может видеть карту до тех пор, пока RecyclerView
  2. Первоначальная мысль заключалась в том, чтобы реализовать эту viewGroup с использованием RecyclerView вместо ScrollView где одним из видов этого вида является CardView но я получил те же результаты, что и в ScrollView

используйте NestedScrollView вместо ScrollView

Для получения дополнительной информации перейдите к справочному документу NestedScrollView .

и добавьте recyclerView.setNestedScrollingEnabled(false); к вашему RecyclerView

Я знаю, что я опоздал на игру, но проблема все еще существует даже после того, как Google установил исправление в android.support.v7.widget.RecyclerView

Проблема, которую я получаю сейчас, это RecyclerView с layout_height=wrap_content не принимая высоту всех элементов, выпущенных внутри ScrollView что происходит только в ScrollView Marshmallow и Nougat + (API 23, 24, 25).
(UPDATE: замена ScrollView на android.support.v4.widget.NestedScrollView работает со всеми версиями. Я как-то пропустил тестирование принятого решения . Добавил это в мой проект github в качестве демонстрации.)

Попытавшись по-разному, я нашел обходное решение, которое устраняет эту проблему.

Вот моя структура макета в двух словах:

   (vertical - this is the only child of scrollview)   (layout_height=wrap_content)  

Обходной путь – обертка RecyclerView с RelativeLayout . Не спрашивайте меня, как я нашел это обходное решение! ¯\_(ツ)_/¯

    

Полный пример доступен в проекте GitHubhttps://github.com/amardeshbd/android-recycler-view-wrap-content

Вот демонстрационный демонстрационный пример, показывающий действие в действии:

Screencast

Хотя рекомендация о том, что

вы никогда не должны ставить прокручиваемый вид внутри другого прокручиваемого вида

Хороший совет, однако, если вы установите фиксированную высоту на просмотр ресайклера, он должен работать нормально.

Если вы знаете высоту макета элемента адаптера, вы можете просто вычислить высоту RecyclerView.

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

Новая библиотека поддержки Android 23.2 решает эту проблему, теперь вы можете установить wrap_content как высоту вашего RecyclerView и работать правильно.

Библиотека поддержки Android 23.2

В случае, если установка фиксированной высоты для 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) { } }); 

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:

  

И настройте его так:

  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, программно раздуть подзаголовки и добавить их в макет.

Для ScrollView вы можете использовать fillViewport=true и make layout_height="match_parent" как layout_height="match_parent" ниже, и включить просмотр recycler внутри:

    

Никакой дополнительной регулировки высоты не требуется с помощью кода.

Вычисление высоты 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 (fe 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"); } } } 

Попробуй это. Очень поздний ответ. Но, безусловно, помогите кому-нибудь в будущем.

Установите Scrollview в NestedScrollView

     

В вашем Recyclerview

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

UPDATE: этот ответ уже устарел, поскольку есть такие виджеты, как NestedScrollView и RecyclerView, которые поддерживают вложенную прокрутку.

вы никогда не должны ставить прокручиваемое представление в другое прокручиваемое представление!

я предлагаю вам сделать ваш основной вид ресайклера макета и поместить ваши представления в качестве элементов просмотра ресайклеров.

взгляните на этот пример, он показывает, как использовать несколько видов в адаптере просмотра recycler. ссылка на пример

Вы можете использовать этот способ:

Добавьте эту строку в свой вид recyclerView xml:

  android:nestedScrollingEnabled="false" 

попробуйте, recyclerview будет плавно прокручиваться с гибкой высотой

надеюсь, это помогло.

Кажется, что NestedScrollView действительно решает проблему. Я тестировал этот макет:

        

И это работает без проблем

У меня была такая же проблема. Это то, что я пытался, и это работает. Я использую свой xml и java-код. Надеюсь, это поможет кому-то.

Вот xml

  

           

Вот связанный Java-код. Отлично работает.

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

Это делает трюк:

 recyclerView.setNestedScrollingEnabled(false); 

На самом деле главной задачей RecyclerView является компенсация ListView и ScrollView . Вместо того, чтобы делать то, что вы на самом деле делаете: имея RecyclerView в ScrollView , я бы предложил иметь только RecyclerView который может обрабатывать многие типы детей.

Сначала вы должны использовать NestedScrollView вместо ScrollView и поместить RecyclerView внутри NestedScrollView .

Используйте собственный class макета для измерения высоты и ширины экрана:

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

И реализуйте ниже код в активности / fragmentе 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!"); } } }); 

Если RecyclerView показывает только одну строку внутри ScrollView. Вам просто нужно установить высоту своей строки в android:layout_height="wrap_content" .

вы также можете переопределить LinearLayoutManager, чтобы сделать recyclerview roll гладко

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

Извините, что опаздывал на вечеринку, но похоже, что есть еще одно решение, которое отлично подходит для случая, о котором вы упомянули.

Если вы используете просмотр recycler внутри вида ресайклера, он работает отлично. Я лично пробовал и использовал его, и, похоже, он не медлил и не вялость. Теперь я не уверен, что это хорошая практика или нет, но вложенные множественные просмотры recycler, даже вложенный просмотр прокрутки замедляется. Но это, похоже, работает хорошо. Пожалуйста, попробуйте. Я уверен, что вложение будет прекрасно в этом.

** Решение, которое работало для меня
Использовать NestedScrollView с высотой как wrap_content

 
RecyclerView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" tools:targetApi="lollipop"

and view holder layout
android:layout_width="match_parent"
android:layout_height="wrap_content"

// Содержимое вашей строки идет здесь

  • Добавление действия кнопки в пользовательское уведомление
  • Как я могу выровнять меню / значки панели инструментов Android слева, как в приложении «Карты Google»?
  • Скрыть / показать bottomNavigationView на прокрутке
  • Как изменить значки меню навигации и переполнения панели инструментов (appcompat v7)?
  • Как сделать новый эффект параллакса PlayStore
  • android: windowSoftInputMode = "adjustResize" не имеет никакого значения?
  • Как изменить заголовок и значок панели задач Android
  • удалите прописку вокруг значка действия слева на Android 4.0+
  • Кнопка Click Listeners в Android
  • Одна активность и все остальные fragmentы
  • Всплывающее окно по входящему вызову
  • Давайте будем гением компьютера.