Recyclerview Gridlayoutmanager: How to Auto-Detect Span Count

RecyclerView GridLayoutManager: how to auto-detect span count?

Personaly I don't like to subclass RecyclerView for this, because for me it seems that there is GridLayoutManager's responsibility to detect span count. So after some android source code digging for RecyclerView and GridLayoutManager I wrote my own class extended GridLayoutManager that do the job:

public class GridAutofitLayoutManager extends GridLayoutManager
{
private int columnWidth;
private boolean isColumnWidthChanged = true;
private int lastWidth;
private int lastHeight;

public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}

public GridAutofitLayoutManager(
@NonNull final Context context,
final int columnWidth,
final int orientation,
final boolean reverseLayout) {

/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1, orientation, reverseLayout);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}

private int checkedColumnWidth(@NonNull final Context context, final int columnWidth) {
if (columnWidth <= 0) {
/* Set default columnWidth value (48dp here). It is better to move this constant
to static constant on top, but we need context to convert it to dp, so can't really
do so. */
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
context.getResources().getDisplayMetrics());
}
return columnWidth;
}

public void setColumnWidth(final int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
isColumnWidthChanged = true;
}
}

@Override
public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
final int width = getWidth();
final int height = getHeight();
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
final int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = width - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
final int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
isColumnWidthChanged = false;
}
lastWidth = width;
lastHeight = height;
super.onLayoutChildren(recycler, state);
}
}

I don't actually remember why I choosed to set span count in onLayoutChildren, I wrote this class some time ago. But the point is we need to do so after view get measured. so we can get it's height and width.

EDIT 1: Fix error in code caused to incorrectly setting span count. Thanks user @Elyees Abouda for reporting and suggesting solution.

EDIT 2: Some small refactoring and fix edge case with manual orientation changes handling. Thanks user @tatarize for reporting and suggesting solution.

Android GridLayout different span count based on row width

You might be better off with a different Layout manager like FlexboxLayoutManager https://github.com/google/flexbox-layout

It has lots of flexibility of on controlling when the wrap the row (automatic or manual) and you can specify lots of controls on how individual cells can grow/shrink to fill a line.

There are lots of examples on the github page.

But as a starting point you could manually wrap before cells 3 and 7 to give you the 3 on the first row and 4 on the second.

Or setting FlexWrap will do it automatically based on size

RecyclerView's GridLayoutManager dynamic span count

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

StaggeredGridLayoutManager with auto-fit span count

Had a closer look at the cause of the exception, and this should prevent the error:

import android.content.Context
import android.os.Handler
import android.util.DisplayMetrics
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlin.math.max
import kotlin.math.min

class GridAutoFitStaggeredLayoutManager : StaggeredGridLayoutManager {

companion object {
private fun setInitialSpanCount(context: Context?, columnWidthDp: Int) : Int {
val displayMetrics: DisplayMetrics? = context?.resources?.displayMetrics
return ((displayMetrics?.widthPixels ?: columnWidthDp) / columnWidthDp)
}
}

private var mContext: Context?
private var mColumnWidth = 0
private var mMaximumColumns: Int
private var mLastCalculatedWidth = -1

@JvmOverloads
constructor(
context: Context?,
columnWidthDp: Int,
maxColumns: Int = 99
) : super(setInitialSpanCount(context, columnWidthDp), VERTICAL)
{
mContext = context
mMaximumColumns = maxColumns
mColumnWidth = columnWidthDp
}

override fun onLayoutChildren(
recycler: Recycler,
state: RecyclerView.State
) {
if (width != mLastCalculatedWidth && width > 0) {
recalculateSpanCount()
}
super.onLayoutChildren(recycler, state)
}

private fun recalculateSpanCount() {
val totalSpace: Int = if (orientation == RecyclerView.VERTICAL) {
width - paddingRight - paddingLeft
} else {
height - paddingTop - paddingBottom
}
val newSpanCount = min(
mMaximumColumns,
max(1, totalSpace / mColumnWidth)
)
queueSetSpanCountUpdate(newSpanCount)
mLastCalculatedWidth = width
}

private fun queueSetSpanCountUpdate(newSpanCount: Int) {
if(mContext != null) {
Handler(mContext!!.mainLooper).post { spanCount = newSpanCount }
}
}
}

How to make span count and icon size automatic

You can calculate number of columns. Define a static function to calculate the number of columns as:

public class Utility {
public static int calculateNoOfColumns(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (dpWidth / 180);
// Where 180 is the width of your grid item. You can change it as per your convention.
return noOfColumns;
}
}

And then, when using the GridLayoutManager in the activity or fragment you can do like this:

int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext());

// Some code...

mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);
mRecyclerView.setLayoutManager(mGridLayoutManager);

// Some code...

I hope, this helps you.

RecyclerView GridLayoutManager: span count is defaulted to 1

use the code and set Adapter for recyclerview and just inflate that gridlayout.xml in Adapter.

RecyclerView.LayoutManager layoutmanager = new GridLayoutManager(this, 2);
recyclerview.setLayoutManager(layoutmanager);

and for adapter use your layout

    @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.GridLayout.xml, parent, false);
return new Adapter.ViewHolder(view);
}

Recycler view with auto span count and item width with wrap content

You can use FlexboxLayoutManager from google:

Sample Image

https://github.com/google/flexbox-layout

Set span for items in GridLayoutManager using SpanSizeLookup

The problem was that header should have span size of 2, and regular item should have span size of 1.
So correct implementations is:

mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch(mAdapter.getItemViewType(position)){
case MyAdapter.TYPE_HEADER:
return 2;
case MyAdapter.TYPE_ITEM:
return 1;
default:
return -1;
}
}
});


Related Topics



Leave a reply



Submit