web-dev-qa-db-fra.com

RecyclerView GridLayoutManager: comment détecter automatiquement le nombre d'étapes?

Utilisation du nouveau GridLayoutManager: https://developer.Android.com/reference/Android/support/v7/widget/GridLayoutManager.html

Il faut un nombre d’étapes explicite, le problème est donc devenu: comment savoir combien d’espaces sont ajustés par ligne? Ceci est une grille, après tout. Il doit y avoir autant de portées que le RecyclerView peut adapter, en fonction de la largeur mesurée.

En utilisant l’ancienne GridView, il vous suffirait de définir la propriété "columnWidth" pour détecter automatiquement le nombre de colonnes qui conviennent. C’est essentiellement ce que je veux reproduire pour RecyclerView:

  • ajouter OnLayoutChangeListener sur la RecyclerView
  • dans ce rappel, gonflez un seul "élément de grille" et mesurez-le
  • spanCount = recyclerViewWidth/singleItemWidth;

Cela semble être un comportement assez courant, y a-t-il un moyen plus simple que je ne vois pas?

92
foo64

Personnellement, je n'aime pas sous-classer RecyclerView pour cela, car pour moi, il semble qu'il incombe à GridLayoutManager de détecter le nombre d'intervalles. Donc, après avoir fouillé dans le code source Android pour RecyclerView et GridLayoutManager, j'ai écrit ma propre classe étendue GridLayoutManager qui fait le travail:

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

Je ne me souviens pas vraiment pourquoi j'ai choisi de définir le nombre d'étapes dans onLayoutChildren, j'ai écrit ce cours il y a quelque temps. Mais le fait est que nous devons le faire après avoir mesuré la vue. afin que nous puissions obtenir sa hauteur et sa largeur.

EDIT: Correction d'une erreur dans le code qui entraînait un paramétrage incorrect du nombre d'étapes. Merci utilisateur @Elyees Abouda pour avoir signalé et suggéré/ solution .

102
s.maks

Pour ce faire, j'ai utilisé un observateur d'arborescence de vues pour obtenir la largeur de la vue d'ensemble une fois rendue, puis obtenir les dimensions fixes de la vue de ma carte à partir des ressources, puis définir le nombre d'étapes après avoir effectué mes calculs. Cela ne s'applique vraiment que si les éléments que vous affichez ont une largeur fixe. Cela m'a aidé à remplir automatiquement la grille, quelle que soit la taille ou l'orientation de l'écran.

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

Eh bien, c’est ce que j’ai utilisé, assez basique, mais le travail est fait pour moi. Ce code obtient essentiellement la largeur de l'écran en creux et se divise ensuite par 300 (ou la largeur que vous utilisez pour la présentation de votre adaptateur). Donc, les téléphones plus petits avec une largeur de 300 à 500 mm n’affichent qu’une colonne, des tablettes de 2 à 3 colonnes, etc. Simple, sans chichi, sans inconvénient, à ma connaissance.

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

J'ai étendu le RecyclerView et remplacé la méthode onMeasure.

J'ai défini une largeur d'élément (variable membre) dès que possible, avec une valeur par défaut de 1. Cela met également à jour la configuration modifiée. Cela aura maintenant autant de lignes que possible dans les modes portrait, paysage, téléphone/tablette, etc.

@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

_ {Je publie ceci juste au cas où quelqu'un aurait une largeur de colonne bizarre, comme dans mon cas.} _

Je ne peux pas commenter la réponse de @ s-marks en raison de ma mauvaise réputation. J'ai appliqué sa solution solution mais j'ai eu une largeur de colonne étrange, donc j'ai modifié la fonction vérifiéColumnWidth comme suit:

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

En convertissant la largeur de colonne donnée en DP, le problème a été résolu.

7
Ariq

La solution à vote positif convient, mais gère les valeurs entrantes en pixels, ce qui peut vous tromper si vous codez en dur des valeurs pour tester et supposer dp. Le moyen le plus simple est probablement de mettre la largeur de la colonne dans une dimension et de la lire lors de la configuration de GridAutofitLayoutManager, qui convertira automatiquement dp en valeur de pixel correcte: 

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

Je conclusion ci-dessus répond ici

2
Omid Raha

Pour prendre en compte le changement d'orientation sur la réponse de s-marks , j'ai ajouté un contrôle sur le changement de largeur (width from getWidth (), pas la largeur de colonne).

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

C'est la classe de s.maks avec un correctif mineur pour le moment où le recyclerview change de taille. Par exemple, lorsque vous traitez vous-même avec les changements d'orientation (dans le manifeste Android:configChanges="orientation|screenSize|keyboardHidden"), ou pour toute autre raison, la vue de recyclage peut changer de taille sans changer mColumnWidth. J'ai également changé la valeur int nécessaire pour être la ressource de la taille et permis à un constructeur sans aucune ressource, puis setColumnWidth de le faire vous-même.

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. Définir la largeur fixe minimale de imageView (144dp x 144dp par exemple)
  2. Lorsque vous créez GridLayoutManager, vous devez savoir combien de colonnes auront la taille minimale de imageView:

    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. Après cela, vous devez redimensionner imageView dans l'adaptateur si vous avez un espace dans la colonne. Vous pouvez envoyer newImageViewSize puis inisilize adapter à partir de l’activité dans laquelle vous calculez le nombre d’écrans et de colonnes: 

    @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); //Установка параметров
       ...
    }
    

Cela fonctionne dans les deux orientations. En vertical, j'ai 2 colonnes et en horizontal - 4 colonnes. Le résultat: https://i.stack.imgur.com/WHvyD.jpg

0
Serjant.arbuz