Jak sprawić, aby WRAP_CONTENT działał na RecyclerView


181

Mam kartę DialogFragmentzawierającą RecyclerView(listę kart).

W tym RecyclerViewjest jeden lub więcej, CardViewsktóre mogą mieć dowolną wysokość.

Chcę nadać temu DialogFragmentprawidłową wysokość na podstawie tych, CardViewsktóre są w niej zawarte.

Zwykle byłoby to proste, ustawiłbym wrap_contentsię w RecyclerViewten sposób.

<android.support.v7.widget.RecyclerView ...
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"   
    android:clickable="true"   
    android:scrollbars="vertical" >

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

Ponieważ używam RecyclerViewto nie działa zobacz:

https://issuetracker.google.com/issues/37001674

i

Wysokość widoku zagnieżdżonego recyklingu nie zawija treści

Na obu tych stronach ludzie sugerują rozszerzenie LinearLayoutManageri zastąpienieonMeasure()

Najpierw użyłem LayoutManager, który ktoś podał w pierwszym łączu:

public static class WrappingLayoutManager extends LinearLayoutManager {

        public WrappingLayoutManager(Context context) {
            super(context);
        }

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

            measureScrapChild(recycler, 0,
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            int width = mMeasuredDimension[0];
            int height = mMeasuredDimension[1];

            switch (widthMode) {
                case View.MeasureSpec.EXACTLY:
                case View.MeasureSpec.AT_MOST:
                    width = widthSize;
                    break;
                case View.MeasureSpec.UNSPECIFIED:
            }

            switch (heightMode) {
                case View.MeasureSpec.EXACTLY:
                case View.MeasureSpec.AT_MOST:
                    height = heightSize;
                    break;
                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);
            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();
                measuredDimension[1] = view.getMeasuredHeight();
                recycler.recycleView(view);
            }
        }
    }

Jednak to nie zadziałało, ponieważ

heightSize = View.MeasureSpec.getSize(heightSpec);

zwraca bardzo dużą wartość, która wydaje się być powiązana match_parent.

Komentując height = heightSize;(w drugim przypadku przełącznika) udało mi się sprawić, by wysokość działała, ale tylko wtedy, gdy TextViewdziecko wewnątrz CardViewnie zawija własnego tekstu (długie zdanie).

Gdy tylko zostanie TextViewzawinięty własny tekst, wysokość MUSI wzrosnąć, ale nie wzrośnie. Obliczono wysokość tego długiego zdania jako pojedynczą linię, a nie linię zawiniętą (2 lub więcej).

Wszelkie porady na temat tego, jak powinienem to poprawić, LayoutManageraby moje RecyclerViewprace WRAP_CONTENT?

Edycja: ten menedżer układu może działać dla większości osób, ale nadal ma problemy z przewijaniem i obliczaniem wysokości zawijania widoków tekstu

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(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++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            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);
        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);
        }
    }
}

4
Wygląda na to, że Google w końcu udało się to naprawić :Jan 22, 2016: This has been merged into the internal tree, should be available with the next version of support library.
Marcin Orłowski

Odpowiedzi:


187

Z aktualizacji aktualizacji 23.2.1 Biblioteki obsługi systemu Android wszystkie WRAP_CONTENT powinny działać poprawnie.

Zaktualizuj wersję biblioteki w gradlepliku LUB, aby dodatkowo:

compile 'com.android.support:recyclerview-v7:23.2.1'

rozwiązano niektóre problemy, takie jak Naprawiono błędy związane z różnymi metodami specyfikacji

Sprawdź http://developer.android.com/tools/support-library/features.html#v7-recyclerview

możesz sprawdzić historię wersji Biblioteki pomocy technicznej


Korzystałem z narzędzia LayoutManager i natychmiast zaktualizowałem 23.2.0go, aby zawiesić aplikację. Więc podążyłem za twoją odpowiedzią i moje RecyclerViewopakowanie się nie zawija. Czemu?
Abubakar Oladeji

3
Naprawiony. Napisałem mojego niestandardowego menedżera LayoutManager, aby zawinąć zawartość elementów w widoku Recycler View, ale po aktualizacji zaczął się zawieszać z wyjątkiem opartym na indeksie. A kiedy go usunąłem, nie zawiesił się ponownie, ale mój widok nie był zawijany, dopóki nie zainicjowałem Recycler View za pomocą LinearLayoutManager.
Abubakar Oladeji

25
Nie zapomnij zadzwonić, w mRecyclerView.setNestedScrollingEnabled(false);przeciwnym razie widok recyklera nadal obsłuży sam przewijanie, aby przekazać zdarzenia rodzicom.
WindRider

4
Opakowanie zawartości RecyclerView nie jest jeszcze obsługiwane. Sprawdź medium.com/@elye.project/…
Elye

1
To nie jest rozwiązanie danego problemu. Nawet w wersji 27 wsparcia nie działa. Rozwiązanie zostało podane w następnej odpowiedzi od orange01, aby owinąć RecyclerView wewnątrz RelativeLayout.
Christian

65

AKTUALIZACJA 02.07.2020
Ta metoda może zapobiegać recyklingowi i nie powinna być stosowana w przypadku dużych zbiorów danych .

AKTUALIZACJA 05.07.2019

Jeśli używasz RecyclerViewwewnątrz ScrollView, po prostu zmień ScrollViewna androidx.core.widget.NestedScrollView. Wewnątrz tego widoku nie ma potrzeby spakowania RecyclerViewwewnątrz RelativeLayout.

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- other views -->

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!-- other views -->

    </LinearLayout>

</androidx.core.widget.NestedScrollView>

W końcu znalazłem rozwiązanie tego problemu.

Wszystko, co musisz zrobić, to owinąć RecyclerVieww sposób RelativeLayout. Może są też inne widoki, które również mogą działać.

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

4
nie wiem, dlaczego twoje rozwiązanie jest właściwe. Ale to działa dla mnie. Dziękuję bardzo <3
My Will

1
To samo .. próbowałem wielu rzeczy, ale jakoś to zadziałało. Nie mam pojęcia, dlaczego i jak. Dziękuję Ci bardzo! FrameLayout nie działało dla mnie, ale RelativeLayout działało.
sarora,

2
Działa to również dla mnie, gdy korzystałem z RecyclerView wewnątrz ConstraintLayout.
Allan Veloso

to nie działało dla mnie, dopóki nie ustawiłem zarówno wysokości ORAZ szerokości dla recyclinglerview na "match_parent"
darkrider1287

To zadziałało dla mnie, pamiętam też, kiedy to zobaczyłem. Użyłem go w przeszłości, dziękuję @ orange01 !!
Ivor

53

Oto wyrafinowana wersja klasy, która wydaje się działać i nie ma problemów, które mają inne rozwiązania:

package org.solovyev.android.views.llm;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

/**
 * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
 * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
 *
 * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
 * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
 * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
 * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
 * the measure pass.
 */
public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    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 int childSize = DEFAULT_CHILD_SIZE;
    private boolean hasChildSize;

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context) {
        super(context);
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    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 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, widthSpec, unspecified, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                height += childDimensions[CHILD_HEIGHT];
                if (i == 0) {
                    width = childDimensions[CHILD_WIDTH];
                }
                if (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, heightSpec, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                width += childDimensions[CHILD_WIDTH];
                if (i == 0) {
                    height = childDimensions[CHILD_HEIGHT];
                }
                if (width >= widthSize) {
                    break;
                }
            }
        }

        if ((vertical && height < heightSize) || (!vertical && width < widthSize)) {
            // we really should wrap the contents of the view, let's do it

            if (exactWidth) {
                width = widthSize;
            } else {
                width += getPaddingLeft() + getPaddingRight();
            }

            if (exactHeight) {
                height = heightSize;
            } else {
                height += getPaddingTop() + getPaddingBottom();
            }

            setMeasuredDimension(width, height);
        } else {
            // if calculated height/width exceeds requested height/width let's use default "onMeasure" implementation
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }

    private void logMeasureWarning(int child) {
        if (BuildConfig.DEBUG) {
            Log.w("LinearLayoutManager", "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 widthSpec, int heightSpec, int[] dimensions) {
        final View child = recycler.getViewForPosition(position);

        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;

        final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
        final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

        final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
        final int childHeightSpec = getChildMeasureSpec(heightSpec, 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;

        recycler.recycleView(child);
    }
}

Jest to również dostępne jako biblioteka . Link do odpowiedniej klasy .


Działa dla mnie bezbłędnie. Dziękuję Ci.
natario

10
Proszę używać aktualnej wersji github, ponieważ wiele się zmieniło od czasu, gdy opublikowałem odpowiedź.
se.solovyev

Dziękujemy za Twoją pracę. Mam problemy z pracą z dziećmi o różnej wysokości. Jeśli mam 10 dzieci 150dp, to działa; jeśli jeden z nich ma 300dp, ostatni zostanie ukryty. Dowolny pomysł?
natario,

3
Mówiąc dokładniej, wydaje mi się, że mierzy dziecko, zanim onBindViewHolder()zostanie nazwane. To źle, ponieważ w tym momencie dzwonię, na przykład, holder.textView.setText(longText)aby dziecko stało się wyższe, ale nie ma to odzwierciedlenia w wysokości recyklera. Jeśli masz jakiś pomysł (na przykład szybkie zmiany w adapterze), byłbym wdzięczny.
natario,

Dzięki. Twoja odpowiedź / biblioteka pomogła mi przezwyciężyć problem wysokości poziomej RecyclerViewwewnątrz pionowejRecyclerView .
Sufian,

16

AKTUALIZACJA

Dzięki aktualizacji Biblioteki pomocy Android 23.2 wszystkie WRAP_CONTENT powinny działać poprawnie.

Zaktualizuj wersję biblioteki w pliku stopniowym.

compile 'com.android.support:recyclerview-v7:23.2.0'

Oryginalna odpowiedź

Zgodnie z odpowiedzią na inne pytanie, musisz użyć oryginalnej metody onMeasure (), gdy wysokość widoku recyklera jest większa niż wysokość ekranu. Ten menedżer układu może obliczyć elementDekoracja i przewijać z większą liczbą.

    public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(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++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        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);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

oryginalna odpowiedź: https://stackoverflow.com/a/28510031/1577792


2
Jak możemy osiągnąć to samo za gridlayoutmanageri staggeredgridlayoutmanagerbiorąc pod uwagę liczbę rozpiętości w umyśle
Amrut Bidri

10

Oto wersja c # dla Androida mono

/* 
* Ported by Jagadeesh Govindaraj (@jaganjan)
 *Copyright 2015 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contact details
 *
 * Email: se.solovyev @gmail.com
 * Site:  http://se.solovyev.org
 */


using Android.Content;
using Android.Graphics;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Java.Lang;
using Java.Lang.Reflect;
using System;
using Math = Java.Lang.Math;

namespace Droid.Helper
{
    public class WrapLayoutManager : LinearLayoutManager
    {
        private const int DefaultChildSize = 100;
        private static readonly Rect TmpRect = new Rect();
        private int _childSize = DefaultChildSize;
        private static bool _canMakeInsetsDirty = true;
        private static readonly int[] ChildDimensions = new int[2];
        private const int ChildHeight = 1;
        private const int ChildWidth = 0;
        private static bool _hasChildSize;
        private static  Field InsetsDirtyField = null;
        private static int _overScrollMode = ViewCompat.OverScrollAlways;
        private static RecyclerView _view;

        public WrapLayoutManager(Context context, int orientation, bool reverseLayout)
            : base(context, orientation, reverseLayout)
        {
            _view = null;
        }

        public WrapLayoutManager(Context context) : base(context)
        {
            _view = null;
        }

        public WrapLayoutManager(RecyclerView view) : base(view.Context)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout)
            : base(view.Context, orientation, reverseLayout)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public void SetOverScrollMode(int overScrollMode)
        {
            if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever)
                throw new ArgumentException("Unknown overscroll mode: " + overScrollMode);
            if (_view == null) throw new ArgumentNullException(nameof(_view));
            _overScrollMode = overScrollMode;
            ViewCompat.SetOverScrollMode(_view, overScrollMode);
        }

        public static int MakeUnspecifiedSpec()
        {
            return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
        }

        public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
            int heightSpec)
        {
            var widthMode = View.MeasureSpec.GetMode(widthSpec);
            var heightMode = View.MeasureSpec.GetMode(heightSpec);

            var widthSize = View.MeasureSpec.GetSize(widthSpec);
            var heightSize = View.MeasureSpec.GetSize(heightSpec);

            var hasWidthSize = widthMode != MeasureSpecMode.Unspecified;
            var hasHeightSize = heightMode != MeasureSpecMode.Unspecified;

            var exactWidth = widthMode == MeasureSpecMode.Exactly;
            var exactHeight = heightMode == MeasureSpecMode.Exactly;

            var unspecified = MakeUnspecifiedSpec();

            if (exactWidth && exactHeight)
            {
                // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
                base.OnMeasure(recycler, state, widthSpec, heightSpec);
                return;
            }

            var vertical = Orientation == Vertical;

            InitChildDimensions(widthSize, heightSize, vertical);

            var width = 0;
            var 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();

            var stateItemCount = state.ItemCount;
            var adapterItemCount = ItemCount;
            // 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 (var 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[ChildHeight];
                    if (i == 0)
                    {
                        width = ChildDimensions[ChildWidth];
                    }
                    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[ChildWidth];
                    if (i == 0)
                    {
                        height = ChildDimensions[ChildHeight];
                    }
                    if (hasWidthSize && width >= widthSize)
                    {
                        break;
                    }
                }
            }

            if (exactWidth)
            {
                width = widthSize;
            }
            else
            {
                width += PaddingLeft + PaddingRight;
                if (hasWidthSize)
                {
                    width = Math.Min(width, widthSize);
                }
            }

            if (exactHeight)
            {
                height = heightSize;
            }
            else
            {
                height += PaddingTop + PaddingBottom;
                if (hasHeightSize)
                {
                    height = Math.Min(height, heightSize);
                }
            }

            SetMeasuredDimension(width, height);

            if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return;
            var fit = (vertical && (!hasHeightSize || height < heightSize))
                      || (!vertical && (!hasWidthSize || width < widthSize));

            ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways);
        }

        private void LogMeasureWarning(int child)
        {
#if DEBUG
            Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
                "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");
#endif
        }

        private void InitChildDimensions(int width, int height, bool vertical)
        {
            if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0)
            {
                // already initialized, skipping
                return;
            }
            if (vertical)
            {
                ChildDimensions[ChildWidth] = width;
                ChildDimensions[ChildHeight] = _childSize;
            }
            else
            {
                ChildDimensions[ChildWidth] = _childSize;
                ChildDimensions[ChildHeight] = height;
            }
        }

        public void ClearChildSize()
        {
            _hasChildSize = false;
            SetChildSize(DefaultChildSize);
        }

        public void SetChildSize(int size)
        {
            _hasChildSize = true;
            if (_childSize == size) return;
            _childSize = size;
            RequestLayout();
        }

        private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize,
            int[] dimensions)
        {
            View child = null;
            try
            {
                child = recycler.GetViewForPosition(position);
            }
            catch (IndexOutOfRangeException e)
            {
                Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
                    "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
            }

            if (child != null)
            {
                var p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()

                var hPadding = PaddingLeft + PaddingRight;
                var vPadding = PaddingTop + PaddingBottom;

                var hMargin = p.LeftMargin + p.RightMargin;
                var 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);

                var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child);
                var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child);

                var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width,
                    CanScrollHorizontally());
                var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height,
                    CanScrollVertically());

                child.Measure(childWidthSpec, childHeightSpec);

                dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin;
                dimensions[ChildHeight] = 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)
                {
                   var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams));
                    InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty");
                    InsetsDirtyField.Accessible = true;
                }
                InsetsDirtyField.Set(p, true);
            }
            catch (NoSuchFieldException e)
            {
                OnMakeInsertDirtyFailed();
            }
            catch (IllegalAccessException e)
            {
                OnMakeInsertDirtyFailed();
            }
        }

        private static void OnMakeInsertDirtyFailed()
        {
            _canMakeInsetsDirty = false;
#if DEBUG
            Log.Warn("LinearLayoutManager",
                "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
#endif
        }
    }
}

prawie ... zastąpić var p = (RecyclerView.LayoutParams) child.LayoutParameterszvar p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()
Roubachof

dlaczego deklarujesz statyczne zmienne składowe?
esskar

@esskar Myślę, że zmienna jest używana w metodach statycznych, spójrz na wersję Java, jeśli masz jakiekolwiek wątpliwości
Jagadeesh Govindaraj,

6

RecyclerViewdodano wsparcie dla, wrap_contentw 23.2.0którym był błąd, 23.2.1 było po prostu stabilne, więc możesz użyć:

compile 'com.android.support:recyclerview-v7:24.2.0'

Możesz zobaczyć historię zmian tutaj:

https://developer.android.com/topic/libraries/support-library/revisions.html

Uwaga:

Należy również pamiętać, że po aktualizacji biblioteki wsparcia RecyclerViewbędzie szanować, wrap_contenta także match_parent, jeśli masz widok elementu RecyclerViewzestawu, ponieważ match_parentpojedynczy widok wypełni cały ekran


1
@ Yvette ok, na pewno
ColdFusion


4

Problem z przewijaniem i zawijaniem tekstu polega na tym, że ten kod zakłada, że ​​zarówno szerokość, jak i wysokość są ustawione na wrap_content. Należy jednak LayoutManagerwiedzieć, że szerokość pozioma jest ograniczona. Zamiast tworzyć własne widthSpecdla każdego widoku potomnego, wystarczy użyć oryginału widthSpec:

@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++) {
        measureScrapChild(recycler, i,
                widthSpec,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            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);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(widthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}

3

Spróbuj tego (To paskudne rozwiązanie, ale może działać): W onCreatemetodzie twojego Activitylub w onViewCreatedmetodzie twojego fragmentu. Ustaw oddzwanianie gotowe do uruchomienia przy RecyclerViewpierwszym renderowaniu, na przykład:

vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                calculeRecyclerViewFullHeight();
            }
        });

W calculeRecyclerViewFullHeightobliczeniu RecyclerViewpełnej wysokości opartej na wysokości jego dzieci.

protected void calculateSwipeRefreshFullHeight() {
        int height = 0;
        for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) {
            View v = getRecyclerView().getChildAt(idx);
            height += v.getHeight();
        }
        SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams();
        params.height = height;
        getSwipeRefresh().setLayoutParams(params);
    }

W moim przypadku mój RecyclerViewjest SwipeRefreshLayoutz tego powodu ustawiam wysokość na, SwipeRefreshViewa nie na, RecyclerViewale jeśli nie masz SwipeRefreshView, możesz RecyclerViewzamiast tego ustawić wysokość .

Daj mi znać, czy to ci pomogło, czy nie.


Jak uzyskać metodę getRecyclerView ()?
asubanovsky

@asubanovsky Jest to metoda, która zwraca twoją RecyclerViewinstancję.
4gus71n

2
Pamiętaj, aby usunąć globalLayoutListener w onGlobalLayout ()
Matthew

3

Działa to teraz, ponieważ wydali wersję 23.2, jak stwierdzono w tym poście . Cytując oficjalny blog

Ta wersja wprowadza do interfejsu API LayoutManager nową ekscytującą funkcję: automatyczny pomiar! Pozwala to RecyclerView na sam rozmiar w oparciu o rozmiar jego zawartości. Oznacza to, że wcześniej niedostępne scenariusze, takie jak użycie WRAP_CONTENT dla wymiaru RecyclerView, są teraz możliwe. Przekonasz się, że wszystkie wbudowane menedżery układów obsługują teraz automatyczny pomiar.


3

Wystarczy umieścić RecyclerView w NestedScrollView. Działa świetnie

<android.support.v4.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:layout_marginBottom="25dp">
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/kliste"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.widget.NestedScrollView>

2

Użyłem niektórych z powyższych rozwiązań, ale to działało, widthale height.

  1. Jeśli Określony compileSdkVersionwiększa niż 23 , można korzystać bezpośrednio RecyclerView przewidziane w swoich bibliotekach podtrzymujących recyklera widzenia, jak na 23 to będzie 'com.android.support:recyclerview-v7:23.2.1'. Te biblioteki pomocnicze obsługują atrybuty wrap_contentdla szerokości i wysokości.

Musisz dodać to do swoich zależności

compile 'com.android.support:recyclerview-v7:23.2.1'
  1. Jeśli masz compileSdkVersionmniej niż 23 lata , możesz skorzystać z niżej wymienionego rozwiązania.

Znalazłem ten wątek Google dotyczący tego problemu. W tym wątku jest jeden wkład, który prowadzi do implementacji LinearLayoutManager .

Przetestowałem go zarówno pod względem wysokości, jak i szerokości . W obu przypadkach działał dobrze.

/*
 * Copyright 2015 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contact details
 *
 * Email: se.solovyev@gmail.com
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.views.llm;

import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

import java.lang.reflect.Field;

/**
 * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
 * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
 * <p/>
 * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
 * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
 * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
 * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
 * the measure pass.
 */
public class LinearLayoutManager 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 LinearLayoutManager(Context context) {
        super(context);
        this.view = null;
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        this.view = null;
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(RecyclerView view) {
        super(view.getContext());
        this.view = view;
        this.overScrollMode = ViewCompat.getOverScrollMode(view);
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(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("LinearLayoutManager", "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("LinearLayoutManager", "LinearLayoutManager 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("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
        }
    }
}


1

sugeruję, aby umieścić widok recyklera w dowolnym innym układzie (preferowany jest układ względny). Następnie zmień wysokość / szerokość recyclinglerview jako dopasuj element nadrzędny do tego układu i ustaw wysokość / szerokość układu nadrzędnego jako zawartość zawijania. mi to pasuje


0

Zamień, measureScrapChildaby śledzić kod:

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
        int heightSpec, int[] measuredDimension)
    {
        View view = recycler.GetViewForPosition(position);
        if (view != null)
        {
            MeasureChildWithMargins(view, widthSpec, heightSpec);
            measuredDimension[0] = view.MeasuredWidth;
            measuredDimension[1] = view.MeasuredHeight;
            recycler.RecycleView(view);
        }
    }

Używam xamarin, więc jest to kod c #. Myślę, że można to łatwo „przetłumaczyć” na Javę.


0

Zaktualizuj widok za pomocą wartości null zamiast nadrzędnej grupy widoków w module wyświetlającym adaptera w metodzie onCreateViewHolder.

@Override
public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View view = inflator.inflate(R.layout.layout_item, null, false);
    return new MyViewHolder(view);
}

0

Musisz umieścić FrameLayout jako Widok główny, a następnie umieścić w RelativeLayout za pomocą ScrollView i przynajmniej RecyclerView, to działa dla mnie.

Prawdziwa sztuczka to RelativeLayout ...

Chętnie pomoże.


-1

Nie pracowałem nad moją odpowiedzią, ale tak, jak ją znam StaggridLayoutManager bez. siatki 1 może rozwiązać Twój problem, ponieważ StaggridLayout automatycznie dostosuje swoją wysokość i szerokość do rozmiaru zawartości. jeśli to działa, nie zapomnij sprawdzić poprawnej odpowiedzi.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.