webentwicklung-frage-antwort-db.com.de

RecyclerView GridLayoutManager: Wie erkennt man die Spannenanzahl automatisch?

Verwenden des neuen GridLayoutManager: https://developer.Android.com/reference/Android/support/v7/widget/GridLayoutManager.html

Es ist eine explizite Spannenzahl erforderlich, daher wird das Problem jetzt: Woher wissen Sie, wie viele "Spannweiten" pro Zeile passen? Dies ist immerhin ein Raster. Je nach gemessener Breite sollte es so viele Spannweiten geben, wie der RecyclerView passen kann.

Mit der alten GridView setzen Sie einfach die Eigenschaft "columnWidth" und erkennen automatisch, wie viele Spalten passen. Im Grunde möchte ich für die RecyclerView replizieren:

  • füge OnLayoutChangeListener zur RecyclerView hinzu
  • blasen Sie in diesem Callback ein einzelnes "Gitterelement" auf und messen Sie es
  • spanCount = recyclerViewWidth/singleItemWidth;

Dies scheint ziemlich üblich zu sein. Gibt es einen einfacheren Weg, den ich nicht sehe?

92
foo64

Ich persönlich mag es nicht, RecyclerView dafür zu subclassieren, weil es für mich so aussieht, als ob GridLayoutManager dafür verantwortlich ist, die Spannweitenanzahl zu ermitteln. Nach einigem Quellcode für Android-Quellcode für RecyclerView und GridLayoutManager schrieb ich meine eigene Klasse mit erweitertem GridLayoutManager, die die Aufgabe erfüllt:

public class GridAutofitLayoutManager extends GridLayoutManager
{
    private int mColumnWidth;
    private boolean mColumnWidthChanged = true;

    public GridAutofitLayoutManager(Context context, int columnWidth)
    {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout)
    {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1, orientation, reverseLayout);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    private int checkedColumnWidth(Context context, int columnWidth)
    {
        if (columnWidth <= 0)
        {
            /* Set default columnWidth value (48dp here). It is better to move this constant
            to static constant on top, but we need context to convert it to dp, so can't really
            do so. */
            columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                    context.getResources().getDisplayMetrics());
        }
        return columnWidth;
    }

    public void setColumnWidth(int newColumnWidth)
    {
        if (newColumnWidth > 0 && newColumnWidth != mColumnWidth)
        {
            mColumnWidth = newColumnWidth;
            mColumnWidthChanged = true;
        }
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
    {
        int width = getWidth();
        int height = getHeight();
        if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0)
        {
            int totalSpace;
            if (getOrientation() == VERTICAL)
            {
                totalSpace = width - getPaddingRight() - getPaddingLeft();
            }
            else
            {
                totalSpace = height - getPaddingTop() - getPaddingBottom();
            }
            int spanCount = Math.max(1, totalSpace / mColumnWidth);
            setSpanCount(spanCount);
            mColumnWidthChanged = false;
        }
        super.onLayoutChildren(recycler, state);
    }
}

Ich kann mich nicht erinnern, warum ich mich für onLayoutChildren entschieden habe. Ich habe diese Klasse vor einiger Zeit geschrieben. Aber der Punkt ist, dass wir dies tun müssen, nachdem die Sicht gemessen wurde. so können wir Höhe und Breite bekommen.

EDIT: Fehler im Code behoben, der dazu geführt hat, dass die Spannenzahl falsch eingestellt wurde. Vielen Dank Benutzer @Elyees Abouda für das Melden und Vorschlagen von Lösung .

102
s.maks

Ich habe dies mit einem Ansichtsbaumbeobachter erreicht, um die Breite des Recylcerview nach dem Rendern abzurufen und dann die festen Abmessungen meiner Kartenansicht aus Ressourcen zu ermitteln und dann die Spannweite nach den Berechnungen festzulegen. Dies gilt nur, wenn die angezeigten Elemente eine feste Breite haben. Dies half mir, das Raster unabhängig von der Bildschirmgröße oder Ausrichtung automatisch auszufüllen.

mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    mRecyclerView.getViewTreeObserver().removeOnGLobalLayoutListener(this);
                    int viewWidth = mRecyclerView.getMeasuredWidth();
                    float cardViewWidth = getActivity().getResources().getDimension(R.dimen.cardview_layout_width);
                    int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth);
                    mLayoutManager.setSpanCount(newSpanCount);
                    mLayoutManager.requestLayout();
                }
            });
25
speedynomads

Nun, das ist was ich verwendet habe, ziemlich einfach, aber erledigt die Arbeit für mich. Dieser Code ruft im Wesentlichen die Bildschirmbreite in Dips auf und teilt sich dann durch 300 (oder die Breite, die Sie für das Layout Ihres Adapters verwenden). So zeigen kleinere Telefone mit einer Breite von 300-500 nur eine Spalte, Tablets 2-3 Spalten usw. Einfach, unkompliziert und ohne Nachteile, soweit ich sehen kann.

Display display = getActivity().getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);

float density  = getResources().getDisplayMetrics().density;
float dpWidth  = outMetrics.widthPixels / density;
int columns = Math.round(dpWidth/300);
mLayoutManager = new GridLayoutManager(getActivity(),columns);
mRecyclerView.setLayoutManager(mLayoutManager);
13
Ribs

Ich habe die RecyclerView erweitert und die onMeasure-Methode überschrieben.

Ich setze eine Elementbreite (Elementvariable) so früh wie möglich mit einem Standardwert von 1. Dies ändert sich auch bei der Konfiguration. Dies wird jetzt so viele Reihen haben, wie in Hochformat, Querformat, Telefon/Tablet usw. passen kann.

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    super.onMeasure(widthSpec, heightSpec);
    int width = MeasureSpec.getSize(widthSpec);
    if(width != 0){
        int spans = width / mItemWidth;
        if(spans > 0){
            mLayoutManager.setSpanCount(spans);
        }
    }
}
13
andrew_s

Ich poste dies nur für den Fall, dass jemand eine seltsame Spaltenbreite bekommt wie in meinem Fall.

Ich kann die Antwort von @ s-marks aufgrund meines schlechten Rufes nicht kommentieren. Ich habe seine Lösung Lösung angewendet, aber ich bekam eine seltsame Spaltenbreite, also änderte ich die checkedColumnWidth -Funktion wie folgt:

private int checkedColumnWidth(Context context, int columnWidth)
{
    if (columnWidth <= 0)
    {
        /* Set default columnWidth value (48dp here). It is better to move this constant
        to static constant on top, but we need context to convert it to dp, so can't really
        do so. */
        columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                context.getResources().getDisplayMetrics());
    }

    else
    {
        columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth,
                context.getResources().getDisplayMetrics());
    }
    return columnWidth;
}

Durch die Umwandlung der angegebenen Spaltenbreite in DP wurde das Problem behoben.

7
Ariq

Die aufgerufene Lösung ist in Ordnung, behandelt die eingehenden Werte jedoch als Pixel. Dies kann Sie auslösen, wenn Sie Werte für das Testen und die Annahme von dp hart codieren. Am einfachsten ist es wahrscheinlich, die Spaltenbreite in eine Dimension zu setzen und sie zu lesen, wenn der GridAutofitLayoutManager konfiguriert wird, der dp automatisch in den korrekten Pixelwert konvertiert: 

new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width))
2
toncek

Ich schlussfolgere die Antworten hier

2
Omid Raha

Um die Orientierungsänderung der Antwort von s-marks zu berücksichtigen, habe ich die Breitenänderung (Breite von getWidth (), keine Spaltenbreite) überprüft.

private boolean mWidthChanged = true;
private int mWidth;


@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
{
    int width = getWidth();
    int height = getHeight();

    if (width != mWidth) {
        mWidthChanged = true;
        mWidth = width;
    }

    if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0
            || mWidthChanged)
    {
        int totalSpace;
        if (getOrientation() == VERTICAL)
        {
            totalSpace = width - getPaddingRight() - getPaddingLeft();
        }
        else
        {
            totalSpace = height - getPaddingTop() - getPaddingBottom();
        }
        int spanCount = Math.max(1, totalSpace / mColumnWidth);
        setSpanCount(spanCount);
        mColumnWidthChanged = false;
        mWidthChanged = false;
    }
    super.onLayoutChildren(recycler, state);
}
1
Andreas

Dies ist s.maks 'Klasse mit einem kleinen Fix für die Änderung der Größe des Recyclerview. Wenn Sie sich beispielsweise mit der Ausrichtung befassen, ändern Sie sich selbst (im Manifest Android:configChanges="orientation|screenSize|keyboardHidden"), oder aus einem anderen Grund kann die Größe des Recyclings die Größe ändern, ohne dass sich mColumnWidth ändert. Ich habe auch den int-Wert geändert, den es benötigt, um die Ressource der Größe zu sein, und habe einem Konstruktor ohne Ressource dann setColumnWidth erlaubt, dies selbst zu tun.

public class GridAutofitLayoutManager extends GridLayoutManager {
    private Context context;
    private float mColumnWidth;

    private float currentColumnWidth = -1;
    private int currentWidth = -1;
    private int currentHeight = -1;


    public GridAutofitLayoutManager(Context context) {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1);
        this.context = context;
        setColumnWidthByResource(-1);
    }

    public GridAutofitLayoutManager(Context context, int resource) {
        this(context);
        this.context = context;
        setColumnWidthByResource(resource);
    }

    public GridAutofitLayoutManager(Context context, int resource, int orientation, boolean reverseLayout) {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1, orientation, reverseLayout);
        this.context = context;
        setColumnWidthByResource(resource);
    }

    public void setColumnWidthByResource(int resource) {
        if (resource >= 0) {
            mColumnWidth = context.getResources().getDimension(resource);
        } else {
            /* Set default columnWidth value (48dp here). It is better to move this constant
            to static constant on top, but we need context to convert it to dp, so can't really
            do so. */
            mColumnWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                    context.getResources().getDisplayMetrics());
        }
    }

    public void setColumnWidth(float newColumnWidth) {
        mColumnWidth = newColumnWidth;
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        recalculateSpanCount();
        super.onLayoutChildren(recycler, state);
    }

    public void recalculateSpanCount() {
        int width = getWidth();
        if (width <= 0) return;
        int height = getHeight();
        if (height <= 0) return;
        if (mColumnWidth <= 0) return;
        if ((width != currentWidth) || (height != currentHeight) || (mColumnWidth != currentColumnWidth)) {
            int totalSpace;
            if (getOrientation() == VERTICAL) {
                totalSpace = width - getPaddingRight() - getPaddingLeft();
            } else {
                totalSpace = height - getPaddingTop() - getPaddingBottom();
            }
            int spanCount = (int) Math.max(1, Math.floor(totalSpace / mColumnWidth));
            setSpanCount(spanCount);
            currentColumnWidth = mColumnWidth;
            currentWidth = width;
            currentHeight = height;
        }
    }
}
0
Tatarize
  1. Festlegen der minimalen festen Breite von imageView (zum Beispiel 144dp x 144dp)
  2. Wenn Sie GridLayoutManager erstellen, müssen Sie wissen, wie viele Spalten minimale Größe von imageView haben:

    WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); //Получаем размер экрана
    Display display = wm.getDefaultDisplay();
    
    Point point = new Point();
    display.getSize(point);
    int screenWidth = point.x; //Ширина экрана
    
    int photoWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 144, this.getResources().getDisplayMetrics()); //Переводим в точки
    
    int columnsCount = screenWidth/photoWidth; //Число столбцов
    
    GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columnsCount);
    recyclerView.setLayoutManager(gridLayoutManager);
    
  3. Danach müssen Sie die Größe von imageView im Adapter ändern, wenn in der Spalte Platz vorhanden ist. Sie können newImageViewSize und dann den inisilize-Adapter aus der Aktivität senden. 

    @Override //Заполнение нашей плитки
    public void onBindViewHolder(PhotoHolder holder, int position) {
       ...
       ViewGroup.LayoutParams photoParams = holder.photo.getLayoutParams(); //Параметры нашей фотографии
    
       int newImageViewSize = screenWidth/columnsCount; //Новый размер фотографии
    
       photoParams.width = newImageViewSize; //Установка нового размера
       photoParams.height = newImageViewSize;
       holder.photo.setLayoutParams(photoParams); //Установка параметров
       ...
    }
    

Es funktioniert in beiden Richtungen. In der Vertikalen habe ich 2 Spalten und in der Horizontalen - 4 Spalten. Das Ergebnis: https://i.stack.imgur.com/WHvyD.jpg

0
Serjant.arbuz