web-dev-qa-db-fra.com

Les éléments n'ont pas la même largeur lors de l'utilisation de RecyclerView GridLayoutManager pour définir l'espacement des colonnes par ItemDecoration.

J'essaie d'utiliser RecyclerView et GridLayoutManager pour créer une grille à 3 colonnes, et ItemDecoration pour espacer les colonnes, maintenant le problème est que la largeur de l'élément dans la troisième colonne est plus petite que l'élément dans les première et deuxième colonnes! Voir la capture d'écran ci-dessous. 

enter image description here

Si je n'ajoute pas la coutume ItemDecoration à RecyclerView, tout va bien.

Voici mon code:

MainActivity.Java:

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private MyAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mAdapter = new MyAdapter();
        mRecyclerView.setAdapter(mAdapter);

        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        mRecyclerView.setLayoutManager(gridLayoutManager);

        int horizontalSpacing = 20;
        int verticalSpacing = 10;
        SpacingDecoration decoration = new SpacingDecoration(horizontalSpacing, verticalSpacing, true);
        mRecyclerView.addItemDecoration(decoration);
    }


    private static class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private int[] mColors = new int[]{Color.RED, Color.BLUE, Color.Magenta};

        private static class ItemHolder extends RecyclerView.ViewHolder {

            public MyTextView title;

            public ItemHolder(View itemView) {
                super(itemView);
                title = (MyTextView) itemView.findViewById(Android.R.id.text1);
                title.setTextColor(Color.WHITE);
            }
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            ItemHolder holder = new ItemHolder(itemView);
            holder.itemView.setOnClickListener(itemClickListener);
            return holder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder rHolder, int position) {
            ItemHolder holder = (ItemHolder) rHolder;

            holder.title.setText(String.format("[%d]width:%d", position, holder.itemView.getWidth()));
            holder.itemView.setBackgroundColor(mColors[position % mColors.length]);
            holder.itemView.setTag(position);
            holder.title.setTag(position);
        }

        @Override
        public int getItemCount() {
            return 50;
        }

        private View.OnClickListener itemClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = (int) v.getTag();
                showText(v.getContext(), String.format("[%d]->width:%d", position, v.getWidth()));
            }
        };

    }

    public static class SpacingDecoration extends RecyclerView.ItemDecoration {

        private int mHorizontalSpacing = 5;
        private int mVerticalSpacing = 5;
        private boolean isSetMargin = true;

        public SpacingDecoration(int hSpacing, int vSpacing, boolean setMargin) {
            isSetMargin = setMargin;
            mHorizontalSpacing = hSpacing;
            mVerticalSpacing = vSpacing;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            boolean isSetMarginLeftAndRight = this.isSetMargin;
            int bottomOffset = mVerticalSpacing;
            int leftOffset = 0;
            int rightOffset = 0;

            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
            if (parent.getLayoutManager() instanceof GridLayoutManager) {
                GridLayoutManager lm = (GridLayoutManager) parent.getLayoutManager();
                GridLayoutManager.LayoutParams gridLp = (GridLayoutManager.LayoutParams) lp;

                if (gridLp.getSpanSize() == lm.getSpanCount()) {
                    // Current item is occupied the whole row
                    // We just need to care about margin left and right now
                    if (isSetMarginLeftAndRight) {
                        leftOffset = mHorizontalSpacing;
                        rightOffset = mHorizontalSpacing;
                    }
                } else {
                    // Current item isn't occupied the whole row
                    if (gridLp.getSpanIndex() > 0) {
                        // Set space between items in one row
                        leftOffset = mHorizontalSpacing;
                    } else if (gridLp.getSpanIndex() == 0 && isSetMarginLeftAndRight) {
                        // Set left margin of a row
                        leftOffset = mHorizontalSpacing;
                    }
                    if (gridLp.getSpanIndex() == lm.getSpanCount() - gridLp.getSpanSize() && isSetMarginLeftAndRight) {
                        // Set right margin of a row
                        rightOffset = mHorizontalSpacing;
                    }
                }
            }
            outRect.set(leftOffset, 0, rightOffset, bottomOffset);
        }
    }


    private static Toast sToast;

    public static void showText(Context context, String text) {
        if (sToast != null) {
            sToast.cancel();
        }
        sToast = Toast.makeText(context, text, Toast.LENGTH_LONG);
        sToast.show();
    }
}

activity_main.xml

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
                Android:layout_width="match_parent"
                Android:layout_height="match_parent">

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/recycler_view"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"/>

</RelativeLayout>

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
              Android:layout_width="match_parent"
              Android:layout_height="match_parent"
              Android:orientation="vertical">

    <com.example.liuqing.rvgldemo.MyTextView
        Android:id="@Android:id/text1"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:padding="5dp"
        Android:textColor="#ffffff"
        Android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>

MyTextView.Java

public class MyTextView extends TextView {

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

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
            setText("[" + getTag() + "]width:" + getWidth());
        }
    }
}

Ce serait très apprécié si quelqu'un pouvait expliquer ce problème.

17
AvatarQing

J'ai trouvé la raison du problème par moi-même. Le décalage qui est fait dans ItemDecoration est considéré comme faisant partie des dimensions de l'article (largeur et hauteur)! 

Jetons un coup d'œil à l'exemple de code et à la capture d'écran de la question ci-dessus. La largeur de la capture d'écran est de 480 pixels. Ici, vous avez 3 colonnes. La largeur de chaque élément est de 480/3 = 160 pixels. Dans SpacingDecoration, j'ajoute un décalage à gauche (20 pixels) sur les première et deuxième colonnes, de sorte que la largeur du contenu des premier et deuxième éléments de colonne est 160-20 = 140, puis que j'ajoute les décalages gauche et droit sur le troisième élément de colonne, la largeur du contenu de la 3ème colonne est donc 160-20-20 = 120.

Maintenant, nous voulons que le contenu de chaque élément (le rectangle coloré) ait la même largeur, nous devons calculer combien chaque élément de colonne divise l'espacement total d'une ligne, mais je suis pauvre pour écrire une analyse détaillée, alors voici un calcul approximatif processus, vous pouvez le passer et sauter à la conclusion.

spacing = 20
columnCount = 3
rowWidth = 480
itemWidth = rowWidth / columnCount
itemOccupiedSpacing = (spacing * (columnCount + 1)) / columnCount = spacing + spacing * (1/columnCount)
itemContentWidth = itemWidth - itemOccupiedSpacing

firstItemLeftOffset = spacing = spacing * (3/columnCount)
firstItemRightOffset = itemOccupiedSpacing - spacing = spacing * (1/columnCount)
secondItemLeftOffset = spacing - firstRightOffset = spacing * (2/columnCount)
secondItemRightOffset = itemOccupiedSpacing - secondLeftOffset = spacing * (2/columnCount)
thirdItemLeftOffset = itemOccupiedSpacing - secondLeftOffset = spacing * (1/columnCount)
thirdItemRightOffset = spacing = spacing * (3/columnCount)

On peut conclure :

itemLeftOffset = spacing * ((columnCount - colunmnIndex) / columnCount)
itemRightOffset = spacing * ((colunmnIndex + 1) / columnCount)

colunmnIndex est supérieur à 0 et inférieur à columnCount.


Voici ma coutume ItemDecoration pour l'espacement, cela fonctionne bien avec LinearLayoutManager, GridLayoutManager et StaggeredGridLayoutManager, tous les éléments ont la même largeur. Vous pouvez l'utiliser directement dans votre code.

public class SpacingDecoration extends ItemDecoration {

    private int mHorizontalSpacing = 0;
    private int mVerticalSpacing = 0;
    private boolean mIncludeEdge = false;

    public SpacingDecoration(int hSpacing, int vSpacing, boolean includeEdge) {
        mHorizontalSpacing = hSpacing;
        mVerticalSpacing = vSpacing;
        mIncludeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        // Only handle the vertical situation
        int position = parent.getChildAdapterPosition(view);
        if (parent.getLayoutManager() instanceof GridLayoutManager) {
            GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
            int spanCount = layoutManager.getSpanCount();
            int column = position % spanCount;
            getGridItemOffsets(outRect, position, column, spanCount);
        } else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) {
            StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) parent.getLayoutManager();
            int spanCount = layoutManager.getSpanCount();
            StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
            int column = lp.getSpanIndex();
            getGridItemOffsets(outRect, position, column, spanCount);
        } else if (parent.getLayoutManager() instanceof LinearLayoutManager) {
            outRect.left = mHorizontalSpacing;
            outRect.right = mHorizontalSpacing;
            if (mIncludeEdge) {
                if (position == 0) {
                    outRect.top = mVerticalSpacing;
                }
                outRect.bottom = mVerticalSpacing;
            } else {
                if (position > 0) {
                    outRect.top = mVerticalSpacing;
                }
            }
        }
    }

    private void getGridItemOffsets(Rect outRect, int position, int column, int spanCount) {
        if (mIncludeEdge) {
            outRect.left = mHorizontalSpacing * (spanCount - column) / spanCount;
            outRect.right = mHorizontalSpacing * (column + 1) / spanCount;
            if (position < spanCount) {
                outRect.top = mVerticalSpacing;
            }
            outRect.bottom = mVerticalSpacing;
        } else {
            outRect.left = mHorizontalSpacing * column / spanCount;
            outRect.right = mHorizontalSpacing * (spanCount - 1 - column) / spanCount;
            if (position >= spanCount) {
                outRect.top = mVerticalSpacing;
            }
        }
    }
}
12
AvatarQing

J'ai écrit un ItemDecoration plus robuste basé sur la réponse de @AvatarQing: SCommonItemDecoration

Vous pouvez définir le même espace vertical ou horizontal entre les éléments, mais vous pouvez également définir un espace différent pour différents types d'éléments.

 enter image description here

2
Bos