使用RecyclerView GridLayoutManager通过ItemDecoration创建列间距时,项目的宽度不同

我正在尝试使用RecyclerViewGridLayoutManager来制作一个3列网格,我使用ItemDecoration来制作列间距,现在问题是第三列中项目的宽度小于第一列和第二列中的项目! 请参阅下面的屏幕截图。

在此处输入图像描述

如果我没有将自定义ItemDecoration添加到RecyclerView ,一切正常。

这是我的代码:

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 { 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中

    

item.xml

     

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

如果有人能解释这个问题,我将非常感激。

Related of "使用RecyclerView GridLayoutManager通过ItemDecoration创建列间距时,项目的宽度不同"

我自己find了问题的原因。 ItemDecoration的偏移量被视为项目尺寸(宽度和高度)的一部分!

我们来看看上面问题中的示例代码和屏幕截图。 屏幕截图的宽度为480像素,这里是3列,每个项目的宽度为480/3 = 160像素。 在SpacingDecoration ,我在第一列和第二列上添加左偏移(20像素),因此第一列和第二列的内容宽度为160-20 = 140,然后我在第3列项上添加左右偏移,所以第3列项目的内容宽度为160-20-20 = 120。

现在我们要让每个项目的内容(彩色矩形)具有相同的宽度,我们必须计算每个列项目划分一行的总间距多少,但我写的详细分析很差,所以在这里我写一个粗略的计算过程中,你可以通过它并跳到结论。

 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) 

我们可以得出结论:

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

colunmnIndex大于0且小于columnCount。


这是我的自定义ItemDecoration的间距,它适用于LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager ,所有项目的宽度相同。 您可以直接在代码中使用它。

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

我根据@AvatarQing的答案编写了一个更健壮的ItemDecoration: SCommonItemDecoration

您可以在项目之间设置相同的垂直或水平空间,此外您可以为不同types的项目设置不同的空间。

在此处输入图像描述