GridLayoutManager - how to auto fit columns?
You can calculate available number of columns, given a desired column width, and load the image as calculated. Define a static funtion to calculate as:
public class Utility {
public static int calculateNoOfColumns(Context context, float columnWidthDp) { // For example columnWidthdp=180
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (screenWidthDp / columnWidthDp + 0.5); // +0.5 for correct rounding to int.
return noOfColumns;
}
}
And then when using it in the activity or fragment you can do like this :
int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext());
............
mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);
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.
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 }
}
}
}
RecyclerView autofit LayoutManager
I recently saw FlexboxLayoutManager
at FlexBox Layout Github.
If you want to achieve same effect as Google Keep application you can use it.
GridLayoutManager - column width wrap its own largest child
You don't need to put your RecyclerView in HorizontalScrollView. See the code below.
public class MainActivity extends AppCompatActivity {
String[] list = new String[]{"Some text goes here", "Some small", "text", "goes here", "Some", "very large text", "goes here",
"Some text goes here", "Some small", "text", "goes here", "Some", "very large text", "goes here"};
RecyclerView grid;
GridAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
grid = (RecyclerView)findViewById(R.id.grid);
grid.setLayoutManager(new GridLayoutManager(this, 2, LinearLayoutManager.HORIZONTAL, false));
grid.setHasFixedSize(true);
adapter = new GridAdapter(list);
grid.setAdapter(adapter);
}
}
Adapter class
public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder>{
String[] mList;
public GridAdapter(String[] list) {
mList = list;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(mList[position]);
}
@Override
public int getItemCount() {
return mList.length;
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(View itemView) {
super(itemView);
textView = (TextView)itemView.findViewById(R.id.text);
}
public void bind(String s) {
textView.setText(s);
}
}
}
row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp">
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="cab.suresh.gridlayoutexample.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Edit
Place your RecyclerView inside NestedScrollView like this
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<android.support.v7.widget.RecyclerView
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.NestedScrollView>
and set your number of spanCount like this
spanCount = 8;
grid.setLayoutManager(new GridLayoutManager(this, spanCount, LinearLayoutManager.HORIZONTAL, false));
Changing number of columns in RecyclerView gridlayout
public class VarColumnGridLayoutManager extends GridLayoutManager {
private int minItemWidth;
public VarColumnGridLayoutManager(Context context, int minItemWidth) {
super(context, 1);
this.minItemWidth = minItemWidth;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler,
RecyclerView.State state) {
updateSpanCount();
super.onLayoutChildren(recycler, state);
}
private void updateSpanCount() {
int spanCount = getWidth() / minItemWidth;
if (spanCount < 1) {
spanCount = 1;
}
this.setSpanCount(spanCount);
}}
Changing number of columns with GridLayoutManager and RecyclerView
Try handling this inside your onCreateView method instead since it will be called each time there's an orientation change:
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 2));
}
else{
mRecycler.setLayoutManager(new GridLayoutManager(mContext, 4));
}
Related Topics
Layout Problem with Button Margin
Android: Starting an Activity for a Different Third Party App
How to Discover Zeroconf (Bonjour) Services on Android? I'M Having Trouble with Jmdns
React Native Android Duplicate File Error When Generating APK
Kotiln: Pass Data from Adapter to Activity
How to Set The Title Color for The New Toolbar
Create Free/Paid Versions of Application from Same Code
Open Google Maps Through Intent for Specific Location in Android
Android Webview HTML5 Video Autoplay Not Working on Android 4.0.3
Insufficent_Storage_Memory Message While Install 50 Mb APK in Emulator
Supportmapfragment Does Not Support Androidx Fragment
Android Browser Textarea Scrolls All Over The Place, Is Unusable
How Open New Activity Clicking an Item in Listview