GridLayoutManager with different column count per row - android

I'm trying to build a RecyclerView with a GridLayoutManager which has a variable column count per row, something like this:
The sum of the width of all items in the same row will always be the screen width.
I tried to re-organize the list of items, grouping them by list of rows, and then inflating a LinearLayout per row. It didn't work quite well.
So I'm stuck and out of ideas. Any help would be really appreciated

You can use GridLayoutManager. To have different column count in row you have to override setSpanSizeLookup.
Example:
//spanCount = 3 (just for example)
GridLayoutManager gridLayoutManager = new GridLayoutManager(getAppContext(), spanCount);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
//define span size for this position
//some example for your first three items
if(position == item1) {
return 1; //item will take 1/3 space of row
} else if(position == item2) {
return 2; //you will have 2/3 space of row
} else if(position == item3) {
return 3; //you will have full row size item
}
}
});
I code sample above I just show have you can change item size. Pay attention that spanSize <= spanCount.

I have similar situation and think it is a good choice to use kotlin: sealed class. You may set any spanSizeLookup for every item in your adapter list.
Example of setup spanSizeLookup for GridLayoutManager:
val spanCount = 2
val layoutManager = GridLayoutManager(context, spanCount)
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val item = adapter.getItemList().getOrNull(position) ?: return spanCount
return when (item) {
is ProfileItem.Header -> spanCount
is ProfileItem.Status -> spanCount
is ProfileItem.Progress -> spanCount
is ProfileItem.Empty -> spanCount
is ProfileItem.Item -> spanCount / 2
is ProfileItem.Load -> spanCount / 2
}
}
}
My sealed class:
sealed class ProfileItem {
object Header : ProfileItem()
data class Status(var content: UserItem.User?) : ProfileItem()
object Progress : ProfileItem()
object Empty : ProfileItem()
data class Item(val content: RecordItem.Record) : ProfileItem()
object Load : ProfileItem()
}

Related

Recycler View shows identical items

I have an arrayList<> of strings and I added 10 strings to it.
private val names: ArrayList<String> = arrayListOf()
These are the strings added
[G.I. Joe: The Rise of Cobra, Creed 2, The Equalizer 2, Ride Along 2, Mission Impossible, Mission Impossible II, Mission Impossible III, Mission Impossible: Ghost Protocol, Mission Impossible: Fallout, Suicide Squad]
I have a recycler view with its adapter as follows:
class MovieSeriesAdapter(
private val movie: MoviesInSeries,
private val movieNameList: ArrayList<String>?,
private val restMoviesPosition: Int,
): RecyclerView.Adapter<MovieSeriesAdapter.ViewHolder>() {
class ViewHolder(binding: SeriesMoviesItemBinding): RecyclerView.ViewHolder(binding.root) {
val mainThing = binding.mainThing
val tvMovieNameAndYear = binding.seriesMovieNameAndYear
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(SeriesMoviesItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (position % 2 != 0) {
holder.mainThing.setBackgroundColor(Color.parseColor("#E8E8E8"))
}
val RestMoviesList = Constants.getRestOfSeriesMovies()
var targetPosition: Int = 0
when (movie.originalMovieName) {
"G.I. Joe: Retaliation" -> targetPosition = 0
"Creed" -> targetPosition = 1
"The Equalizer" -> targetPosition = 2
"Ride Along" -> targetPosition = 3
"Mission Impossible" -> targetPosition = 4
"Suicide Squad" -> targetPosition = 9
"Venom" -> targetPosition = 10
}
val targetMovieName = movieNameList!![targetPosition]
holder.tvMovieNameAndYear.text = "$targetMovieName ($targetMovieDate)"
}
override fun getItemCount(): Int {
return 5
}
The thing is that sometimes I need the recycler view to show 5 items, as in the case below. For Example, I have 5 movies of mission impossible and I want to show them. But the targetPosition is an integer variable of only one number.
I tried creating an array list of only the mission impossible movies but it showed identical 5 items with all of the data in one item.
How do I make it that when I need 5 items to be displayed, each item should get a diffent value from the name array list.
I'll recommend you to directly use the value of position for targetValue, inside onBindViewHolder while setting the value of text.
You should just give the adapter a list of the 5 you want to show.
Also, a lot of the code you wrote doesn't make a lot of sense.
For example
when (movie.originalMovieName) {
You decide here something based on movie.originalMovieName, but this movie is passed in the constructor of the adapter, which already doesn't make sense, but it also means that it will be the same movie for every item.

Is it posible to change layoutmanager for a different viewtype?

Actually I have two viewtypes, a recyclerview is showing the elements in a gridLayout fashion.
Problem is that one of those elements in the grid should be show horizontally with a linearlayout aspect and the others as a grid.
How can I manage this ?
override fun getItemViewType(position: Int): Int {
return if(position %5 == 0) 1 else 0
}
This is my recyclerview
binding.rv_mylist.adapter = TestAdapter()
binding.rv_mylist.layoutManager = GridLayoutManager(requireContext(), 3)
in conclusion, what I need is that viewtype 1 shows with a LinearLayoutManager and viewtype 0 with a GridLayoutManager
You can achieve this by modify the spanSizeLookup of the GridLayoutManager, i.e.
layoutManager = GridLayoutManager(activity, 2).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when(adapter.getItemViewType(position)) {
Type.Linear -> 1
Type.Grid -> 2
else -> 1
}
}
})
}
recyclerView.layoutManager = layoutManager

Android RecyclerView Make fixed grid layout with combined rows

I would like to make a layout like this:
I tried with this code, but the second and third item's height are same as the first item.
val lm = GridLayoutManager(requireContext(), 2, RecyclerView.HORIZONTAL, false)
lm.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (position == 0) 2
else 1
}
}

RecyclerView's GridLayoutManager dynamic span count

I am using following code dynamically change span count.
val layoutManager = GridLayoutManager(this, 3)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
when (position) {
0, 1, 2 -> return 1
3, 4 -> return 2
5 -> return 3
else -> return 1
}
}
}
And I got following output.
But I want D and E should horizontally equally aligned. I don't how to do it.
Actually I have 3 types in adapter, HEADER, TYPE_A, TYPE_B.
HEADER should have only one row, and TYPE_A is 3 rows, TYPE_B is 2 rows.
So may I get help to make some columns should have one 1 row, and some have only 2 rows(horizontally equally aligned) and some have 3 rows.
In that case you should make your grid layout have more than 3 cells. You'd need to pick a number that works for all three types of cells, a 6 is good because to have 3 cells by row you'd return a 2. To have 2 cells by row you'd return a 3 and to have 1 cell by row you'd return a 6:
val layoutManager = GridLayoutManager(this, 6)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
when (position) {
0, 1, 2 -> return 2
3, 4 -> return 3
5 -> return 6
else -> return 2
}
}
}
Here is my solution for dinamic span in this case the maximun span is 4 and 12 is multiple of 4, 3 and 2.
//NUMBER OF THE SPAN THAT YOU WANT is the size of your array
val mutipleSpan = 12 //least common multiple
val maxSpan = 4//max span count
val mLayoutManager = object : GridLayoutManager(itemView.context, mutipleSpan){}
mLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val actualSize = //NUMBER OF THE SPAN THAT YOU WANT
if(actualSize< maxSpan){
return mutipleSpan/actualSize
}
return mutipleSpan/maxSpan
}
}
recycler.layoutManager = mLayoutManager
Use FlexBoxLayoutManager of library by Google.
Document describes this is a valid solution.

Make the Last RecyclerView Item Fill Spaces in RecyclerView (If the data count is odd numbered)

I have been trying to create a recyclerView so far, but now I am faced with a problem.
I need to make it to look like this
I need to make it Grid like list, however I am unable to place them side by side.
Secondly, I need the last item to fill both spaces if the last item is "alone".
Any ideas?
I need to make it Grid like list
You can acheive it using RecyclerView with GridLayoutManager. For example,
// Initialize the view
recyclerView=(RecyclerView)findViewById(R.id.recyclerView);
// Here 2 is the number of columns
GridLayoutManager llm = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(llm);
recyclerView.setHasFixedSize(true);
I need the last item to fill both spaces if the last item is "alone"
To customize the grid items, we can use ItemDecoration. And in your case, the last item if it is alone should have the parent width. We can acheive this by checking the position of the last item.
Now, the code:
In Activity
recyclerView=(RecyclerView)findViewById(R.id.recyclerView);
GridLayoutManager llm = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(llm);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new GridItemDecoration());
// And set adapter
GridItemDecoration.java
public class GridItemDecoration extends RecyclerView.ItemDecoration
{
private int mHorizontalSpacing = 10;
private int mVerticalSpacing = 10;
#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.getChildPosition(view);
if (parent.getLayoutManager() instanceof GridLayoutManager)
{
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount, column;
// Check the last item and is alone. Then set the parent's width
if (position == parent.getAdapter().getItemCount() - 1 && position % 2 == 0)
{
spanCount = 1;
outRect.left = mHorizontalSpacing;
outRect.right = parent.getWidth() - mHorizontalSpacing;
}
else
{
spanCount = layoutManager.getSpanCount();
column = position % spanCount;
outRect.left = mHorizontalSpacing * (spanCount - column) / spanCount;
outRect.right = mHorizontalSpacing * (column + 1) / spanCount;
}
if (position < spanCount)
{
outRect.top = mVerticalSpacing;
}
outRect.bottom = mVerticalSpacing;
}
}
}
For set different span count you may use GridLayoutManager standard function setSpanSizeLookup.
This realization in Kotlin for fill screen width on last item:
// Count of columns (I set it in integer resources for screen optimisation)
val spanCount = resources.getInteger(R.integer.column_count)
val layoutManager = GridLayoutManager(context, spanCount)
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val isLastItem = position == adapter.getItemList().lastIndex
return if (isLastItem) {
/**
* Return this for full width of screen.
* It will be always equal 1, dividing only to avoid numbers in code.
*/
spanCount / spanCount
} else {
// Return this for place item in your column.
spanCount
}
}
}
Also if you have complicated adapter with plenty items, you may use this realization, for example:
val spanCount = resources.getInteger(R.integer.column_count_profile)
val layoutManager = GridLayoutManager(context, spanCount)
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val item = adapter.getItemList().getOrNull(position) ?: return spanCount
return when (item) {
is ScreenItem.Header -> spanCount
is ScreenItem.Status -> spanCount
is ScreenItem.Progress -> spanCount
is ScreenItem.Empty -> spanCount
is ScreenItem.Item -> spanCount / spanCount
is ScreenItem.Load -> spanCount / spanCount
}
}
}
Where ScreenItem is sealed class, inside which something like this:
sealed class ScreenItem{
object Header : ScreenItem()
data class Status(var content: ContentItem) : ScreenItem()
object Progress : ScreenItem()
object Empty : ScreenItem()
data class Item(val content: AnotherContentItem) : ScreenItem()
object Load : ScreenItem()
}
P.S. check out this article on medium about different items in list and different column count. May be it will be helpful for someone.

Categories

Resources