LayoutManager for RecyclerView Grid with different cell width
Solution 1
You can use StaggeredGridLayoutManager.LayoutParams
to change width and height of cells, What you expect to design follows a simple pattern, If you assign every cell an index (in the order that StaggeredGridLayoutManager
arranges them by default) you will have this:
index % 4 == 3 ---> full span cell
index % 8 == 0, 5 ---> half span cell
index % 8 == 1, 2, 4, 6 ---> quarter span cell
first declare some constants in adapter to define span types:
private static final int TYPE_FULL = 0;
private static final int TYPE_HALF = 1;
private static final int TYPE_QUARTER = 2;
Then override getItemViewType
method in your adapter like this:
@Override
public int getItemViewType(int position) {
final int modeEight = position % 8;
switch (modeEight) {
case 0:
case 5:
return TYPE_HALF;
case 1:
case 2:
case 4:
case 6:
return TYPE_QUARTER;
}
return TYPE_FULL;
}
All you need to do is change layoutparams considering viewType
of the holder
:
@Override
public MyHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View itemView =
LayoutInflater.from(mContext).inflate(R.layout.items, parent, false);
itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
final int type = viewType;
final ViewGroup.LayoutParams lp = itemView.getLayoutParams();
if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams sglp =
(StaggeredGridLayoutManager.LayoutParams) lp;
switch (type) {
case TYPE_FULL:
sglp.setFullSpan(true);
break;
case TYPE_HALF:
sglp.setFullSpan(false);
sglp.width = itemView.getWidth() / 2;
break;
case TYPE_QUARTER:
sglp.setFullSpan(false);
sglp.width = itemView.getWidth() / 2;
sglp.height = itemView.getHeight() / 2;
break;
}
itemView.setLayoutParams(sglp);
final StaggeredGridLayoutManager lm =
(StaggeredGridLayoutManager) ((RecyclerView) parent).getLayoutManager();
lm.invalidateSpanAssignments();
}
itemView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
MyHolder holder = new MyHolder(itemView);
return holder;
}
My adapter always returns 50 for items count(just a test) and I used a simple layout file for items, It contains a LinearLayout
and a TextView
to show the position of holder, Remember you should pass 2 for spanCount
(int the StaggeredGridLayoutManager
constructor) because of your design.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
StaggeredGridLayoutManager lm =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
rv.setLayoutManager(lm);
rv.setAdapter(new MyAdapter(this));
}
PS:
For lazy people like me maybe it's a simpler way rather than extending my custom LayoutManager
, But I'm sure there are better ways to achieve this.
Update:
Updated part of your question is more simpler, You can use GridLayoutManager
:
GridLayoutManager glm = new GridLayoutManager(this, 3);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position % 3 == 2) {
return 3;
}
switch (position % 4) {
case 1:
case 3:
return 1;
case 0:
case 2:
return 2;
default:
//never gonna happen
return -1 ;
}
}
});
rv.setLayoutManager(glm);
In this way you set 3 for default span size, Then considering the position of your span, Each item occupies 1, 2 or 3 spans, Something like this:
Item0.width == 2 X Item1.width
Item2.width == 3 X Item1.width
Item0.width == 2/3 X Item1.width
Solution 2
For everyone else, looking for a solution without simplifying your layout, you can have a look to my answer here, or you can read more.
Big thanks to Nick Butcher! His layout manager called SpannableGridLayoutManager
is capable of doing this.
-
Download SpannableGridLayoutManager from here
For some reason I had to change this line:
while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) {
to
while (lastVisiblePosition < lastItemPosition) {
to got the manager working.
Set SpannableGridLayoutManger to your RecyclerView
In your case:
SpannedGridLayoutManager manager = new SpannedGridLayoutManager(
new SpannedGridLayoutManager.GridSpanLookup() {
@Override
public SpannedGridLayoutManager.SpanInfo getSpanInfo(int position) {
switch (position % 8) {
case 0:
case 5:
return new SpannedGridLayoutManager.SpanInfo(2, 2);
case 3:
case 7:
return new SpannedGridLayoutManager.SpanInfo(3, 2);
default:
return new SpannedGridLayoutManager.SpanInfo(1, 1);
}
}
},
3, // number of columns
1f // default size of item
);
Which will give you exactly what is in the first picture of the question.
Solution 3
Finally did it, it will help everyone who is trying to achieve any kind of patterns
I was looking for the same result but with one header and lazy loading footer. I tried @Kristyna answers of Nick Butcher Library too. But it has many issues like fast scrolling view disappear problem with the extra space or fixed cell height issue.
I found another library of Arasthel SpannedGridLayoutManager and tried to implement it. But it also has many issues like fixed height and footers extra space issue
Finally, after 5hr of digging Arasthel's SpannedGridLayoutManager, I fixed the issues and found my result.
My final edited forked library download from here SpannedGridLayoutManager.
In my case
val adapter = GridItemAdapter()//This is your adapter
val spannedGridLayoutManager = SpannedGridLayoutManager(orientation = VERTICAL, spans = 3)
spannedGridLayoutManager.itemOrderIsStable = true
recyclerview.layoutManager = spannedGridLayoutManager
For the first image result
spannedGridLayoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookup { position ->
when {
position == 0 -> {
/**
* 150f is now static
* should calculate programmatically in runtime
* for to manage header hight for different resolution devices
*/
SpanSize(3, 1, 150f)
}
position % 7 == 1 ->
SpanSize(2, 2)
else ->
SpanSize(1, 1)
}
}
recyclerview.adapter = adapter
And the second image result
spannedGridLayoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookup { position ->
when (position % 8) {
0, 5 ->
SpanSize(2, 2)
3, 7 ->
SpanSize(3, 2)
else ->
SpanSize(1, 1)
}
}
recyclerview.adapter = adapter
This spanSizeLookup logic may vary according to the requirement, for me I had set it randomly for testing purposes.
random
Updated on March 17, 2021Comments
-
random over 3 years
StaggeredGridLayoutManager
doesn't seem to allow customising a cell width or span multiple columns (except full span) for vertical orientation.What is a preferred
LayoutManager
for organising cells as shown above?P.S. I just want to know how to customise cell width and not height with
StaggeredGridLayoutManager
. I know height can be customised as implemented in this sample.public class VerticalStaggeredGridFragment extends RecyclerFragment { public static VerticalStaggeredGridFragment newInstance() { VerticalStaggeredGridFragment fragment = new VerticalStaggeredGridFragment(); Bundle args = new Bundle(); fragment.setArguments(args); return fragment; } @Override protected RecyclerView.LayoutManager getLayoutManager() { return new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); } @Override protected RecyclerView.ItemDecoration getItemDecoration() { return new InsetDecoration(getActivity()); } @Override protected int getDefaultItemCount() { return 100; } @Override protected SimpleAdapter getAdapter() { return new SimpleStaggeredAdapter(); } }
Adapter
public class SimpleStaggeredAdapter extends SimpleAdapter { @Override public void onBindViewHolder(VerticalItemHolder itemHolder, int position) { super.onBindViewHolder(itemHolder, position); final View itemView = itemHolder.itemView; if (position % 4 == 0) { int height = itemView.getContext().getResources() .getDimensionPixelSize(R.dimen.card_staggered_height); itemView.setMinimumHeight(height); } else { itemView.setMinimumHeight(0); } } }
itemView.setMinimumWidth(customWidth)
doesn't affect cell width.To simplify, I update my grid layout to
How to increase cell 0 width as compared to cell 1 in a
StaggeredGridLayoutManager
or any other layout manager? -
random about 8 yearsAre you able to set cell width to 1/4 or 3/4 or say specific size in dp for a grid with span size 2? I was able to set custom height but not width with staggeredgridlayoutmanager. You'll notice cells in my image are not of equal width in the 1st row. I will try your solution too in some time and get back.
-
Farshad Tahmasbi about 8 yearsThat's exactly what I did if you check onCreateViewHolder method.
-
random about 8 yearsCan you update code in your answer for the grid image i provided? I wont be able to try your solution until tomorrow.
-
random about 8 yearsNaN I tried your solution. Unfortunately I cannot change cell width in the first row with the approach. I updated my question to simplify layout. Thanks a lot for your interest. +1 for the effort.
-
Vinay Vissh over 7 yearsThanks a lot!! The last part about gridlayout solved my problem :)
-
Vivek Solanki almost 7 yearsIt was super great help. Shout out to open communities :D
-
blueware about 6 yearsI downloaded the file you mentioned, included it in my project, made the change you mentioned but when scroll through items, the app crashes with this:
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getTop()' on a null object reference
-
kristyna about 6 years@blueware Where Can you tell me where? Which file on what line?
-
blueware about 6 yearsfile:
SpannableGridLayoutManger
, no exact line. If you want I can attach my stacktrace for that exception. -
kristyna about 6 years@blueware you can put your stacktrace to Pastebin and share the url
-
Taufik Nur Rahmanda over 5 yearscan I have
getSpanSize
insideonBindViewHolder
so I know which item from my data should have different span size? -
Farshad Tahmasbi over 5 years@TaufikNurRahmanda
GridLayoutManager.SpanSizeLookup
is an abstract class and YOU are telling it what it should return, So you undoubtedly know the pattern, In case that you insist on usinggetSpanSize
method, just pass an instance to the adapter! -
Vishal Yadav about 5 years@kristyna ... recyclerView takes lots of space from top please help me out
-
kristyna about 5 years@VishalYadav I would have to see your code. Any padding you forgot to delete in the code or xml? Maybe the image on its own has some blank space?
-
Yudi karma almost 5 years@VishalYadav i have same isue, recyclerView takes lots of space from top, you are already fix it ? please help me too
-
Yudi karma almost 5 years@kristyna you found the problem about this issue ? "recyclerView takes lots of space from top"
-
Yudi karma almost 5 years
-
Tgo1014 almost 4 yearsJust wanted to talk you. This lib is amazing and exactly what I needed. I spend some days before finding something like this.
-
Nabin almost 4 yearsThanks @Tgo1014. If you want to talk to me, please send me an email.
-
Tgo1014 almost 4 yearsops, I meant thank you instead of talk you. And thanks too for making yourself available for talking on email :)
-
sejn over 3 years@FarshadTahmasbi Can you have a look into this stackoverflow.com/questions/64607773/…
-
neo about 3 yearsthat was help me alot