Items are not the same width when using RecyclerView GridLayoutManager to make column spacing by ItemDecoration
Solution 1
I found the reason of the problem by myself. The offset that made in ItemDecoration
is regarded as a part of item's dimensions(width and height)!
Let's take a look at the sample code and screen capture in the question above. The width of the screen capture is 480 pixels, and here is 3 columns, each item's width is 480/3 = 160 pixels. In SpacingDecoration
, I add a left offset (20 pixels) on the first and second column, so the content's width of first and second column item is 160-20=140, then I add both left and right offset on the 3rd column item, so the content's width of 3rd column item is 160-20-20=120.
Now we want to make each item's content(the colored rectangle) has the same width, we must calculate how much each column item divide the total spacing of one row, but I am poor to write detailed analysis, so here I write a rough calculating process, you can pass it and jump to the 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)
We can conclude :
itemLeftOffset = spacing * ((columnCount - colunmnIndex) / columnCount)
itemRightOffset = spacing * ((colunmnIndex + 1) / columnCount)
colunmnIndex is greater than 0 and less than columnCount.
Here is my custom ItemDecoration
for spacing, it works well with LinearLayoutManager
,GridLayoutManager
and StaggeredGridLayoutManager
, all items are the same width. You can use it directly in your 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;
}
}
}
}
Solution 2
I've wrote a more robust ItemDecoration based on @AvatarQing 's answer: SCommonItemDecoration
You can set same vertical or horizontal space between items, besides you can set different space to different type of items.
Riki
Updated on June 14, 2022Comments
-
Riki about 2 years
I'm trying to use
RecyclerView
andGridLayoutManager
to make a 3 columns grid, and I useItemDecoration
to make column spacing, now the problem is the item's width in third column is smaller than the item in first and second column! See the screen capture below.If I don't add the custom
ItemDecoration
toRecyclerView
, everything is OK.Here is my 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()); } } }
It will be much appreciate if someone can explain this problem.