Changing number of columns with GridLayoutManager and RecyclerView
Asked Answered
H

9

72

Inside my fragment I'm setting my GridLayout in the following way: mRecycler.setLayoutManager(new GridLayoutManager(rootView.getContext(), 2));

So, I just want to change that 2 for a 4 when the user rotates the phone/tablet. I've read about onConfigurationChanged and I tried to make it work for my case, but it isn't going in the right way. When I rotate my phone, the app crashes...

Could you tell me how to solve this issue?

Here is my approach to find the solution, which is not working correctly:

  @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        int orientation = newConfig.orientation;

        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            mRecycler.setLayoutManager(new GridLayoutManager(mContext, 2));
        } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            mRecycler.setLayoutManager(new GridLayoutManager(mContext, 4));
        }
    }
Hectometer answered 11/4, 2015 at 15:49 Comment(10)
please add crash logDisjunct
I've just taken a look to my logcat and this was not causing the crash. But it is still not working.Hectometer
I would not create a new manager, but instead use existing one. Use getLayoutManager() on your recyclerview, cast it to GridLayoutManager. On that manager call setSpanCount(orientation == portrait ? 2 : 4) And for the sake of redrawing the view call adaper.notifyDatasetChanged() This should work fine if your view is not redrawn each time.Disjunct
WOW! That's sound like I was looking for but I didn't knew how to achieve. I'll try it and tell you about.Hectometer
Note that it is always a good idea to check for the orientation upon creating the view itself. That you usually do in onCreate for Activities or onCreateView for fragments. Make sure you do that because your users may start the activity in landscape modeDisjunct
Wasn't working, I think it is because of the view being redrawn.Hectometer
Yes. The view by default is redrawn when you cange the orientation, so make sure that you handle it there.Disjunct
@Hectometer Your question worked great for me, I do not know why yours was crashing. Did you ever figure out why?Switchback
Look at @SobaDeveloper. I changed the from my onResume() to my onCreateView()Hectometer
I basically implemented both your answers. I am surprised this has not gotten more attention. I see a lot of applications do this.Switchback
T
83

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));
}
Tarsal answered 11/4, 2015 at 20:6 Comment(3)
I'll try it out and tell you about. Thanks!Hectometer
Is this the correct behaviour to do this task? ThanksLituus
@Lituus I think resource folders are a better practice than doing conditions on getConfiguration() or onConfigurationChanged. Also see my answer on this question.Optometrist
O
92

If you have more than one condition or use the value in multiple places this can go out of hand pretty fast. I suggest to create the following structure:

res
  - values
    - integers.xml
  - values-land
    - integers.xml

with res/values/integers.xml being:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="gallery_columns">2</integer>
</resources>

and res/values-land/integers.xml being:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="gallery_columns">4</integer>
</resources>

And the code then becomes (and forever stays) like this:

final int columns = getResources().getInteger(R.integer.gallery_columns);
mRecycler.setLayoutManager(new GridLayoutManager(mContext, columns));

You can easily see how easy it is to add new ways of determining the column count, for example using -w500dp/-w600dp/-w700dp resource folders instead of -land.

It's also quite easy to group these folders into separate resource folder in case you don't want to clutter your other (more relevant) resources:

android {
    sourceSets.main.res.srcDir 'src/main/res-overrides' // add alongside src/main/res
}
Optometrist answered 30/9, 2015 at 23:26 Comment(0)
T
83

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));
}
Tarsal answered 11/4, 2015 at 20:6 Comment(3)
I'll try it out and tell you about. Thanks!Hectometer
Is this the correct behaviour to do this task? ThanksLituus
@Lituus I think resource folders are a better practice than doing conditions on getConfiguration() or onConfigurationChanged. Also see my answer on this question.Optometrist
M
14

In addition to the answers. It can be also done using XML attributes only. Below is the code.

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/pax_seat_map_rv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:spanCount="3"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
Marathon answered 6/9, 2020 at 11:6 Comment(0)
M
10

The Recycle View supports AutofitRecycleView.

You need to add android:numColumns="auto_fit" in your xml file.

You can refer to this AutofitRecycleViewLink

Messenia answered 4/5, 2016 at 9:2 Comment(2)
Excellent implementation! rotate the screen to change the number of columns dynamically and maintains the position of scrollTrippet
This is probably the most perfect solution, but not if your designer decide something different. Sometimes the design just make us to the implementation by the hard waySorbian
E
9

A more robust way to determine the no. of columns would be to calculate it based on the screen width and at runtime. I normally use the following function for that.

public static int calculateNoOfColumns(Context context) {
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
    int scalingFactor = 200; // You can vary the value held by the scalingFactor
    // variable. The smaller it is the more no. of columns you can display, and the
    // larger the value the less no. of columns will be calculated. It is the scaling
    // factor to tweak to your needs.
    int columnCount = (int) (dpWidth / scalingFactor);
    return (columnCount>=2?columnCount:2); // if column no. is less than 2, we still display 2 columns
}

It is a more dynamic method to accurately calculate the no. of columns. This will be more adaptive for users of varying screen sizes without being resticted to only two possible values.

NB: You can vary the value held by the scalingFactor variable. The smaller it is the more no. of columns you can display, and the larger the value the less no. of columns will be calculated. It is the scaling factor to tweak to your needs.

Emilia answered 26/6, 2017 at 16:56 Comment(0)
T
4

In the onCreate () event you can use StaggeredGridLayoutManager

mRecyclerView = (RecyclerView) v.findViewById(R.id.recyclerView);      

mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(
       1, //number of grid columns
       GridLayoutManager.VERTICAL);      

mRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);

Then when the user rotates the screen capture the event, and change the number of columns automatically

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {           
            mStaggeredGridLayoutManager.setSpanCount(1);

    } else {           
            //show in two columns
            mStaggeredGridLayoutManager.setSpanCount(2);           
    }
}
Trippet answered 19/9, 2016 at 20:27 Comment(0)
E
2

I ended up handling this in the onCreate method.

private RecyclerView recyclerView = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    recyclerView.setHasFixedSize(true);
    Configuration orientation = new Configuration();
    if(this.recyclerView.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
    } else if (this.recyclerView.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
    }
            connectGetApiData();
}

It worked out perfectly for my app.

Elute answered 12/8, 2017 at 17:30 Comment(0)
T
0

You can implement the method in your recyclerView onMeasure. First, create the java class AutofitRecyclerView

public class AutofitRecyclerView extends RecyclerView {
//private GridLayoutManager manager;
private StaggeredGridLayoutManager manager;
private int columnWidth = -1;

public AutofitRecyclerView(Context context) {
    super(context);
    init(context, null);
}

public AutofitRecyclerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
}

public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    if (attrs != null) {
        int[] attrsArray = {
                android.R.attr.columnWidth
        };
        TypedArray array = context.obtainStyledAttributes(attrs, attrsArray);
        columnWidth = array.getDimensionPixelSize(0, -1);
        array.recycle();
    }

    manager = new StaggeredGridLayoutManager(1, GridLayoutManager.VERTICAL);
    setLayoutManager(manager);

}

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    super.onMeasure(widthSpec, heightSpec);
    if (columnWidth > 0) {
        int spanCount = Math.max(1, getMeasuredWidth() / columnWidth);
        manager.setSpanCount(spanCount);
    }
}}

In your xlm layout file activity_main.xml

<yourpackagename.AutofitRecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:columnWidth="@dimen/column_width"
            android:clipToPadding="false"/>

Then set the variable to the width of each item in the file size of the values folder values/dimens.xml

<resources>
  <dimen name="column_width">250dp</dimen>
</resources>

It can be for different screen resolutions values-xxhdpi/dimens.xml

<resources>
 <dimen name="column_width">280dp</dimen>
</resources>

In your activity in the onCreate event place the following code

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    recyclerView.addItemDecoration(new MarginDecoration(this));
    recyclerView.setHasFixedSize(true);
    recyclerView.setAdapter(new NumberedAdapter(50));
}
Trippet answered 7/9, 2016 at 19:59 Comment(0)
L
0

I will try to explain it step by step. And you can check integers.xml, integers.xml (land) and MainFragment files of github project that I shared. You will see that number of columns will change based on orientation or screen size(tablet vs phone)(I will explain only how to do it when orientation changed, since the question is only about it). https://github.com/b-basoglu/NewsApp/blob/master/app/src/main/java/com/news/newsapp/ui/main/MainFragment.kt

  1. Create a resources file called integers.xml. -> Open the res folder in the Project > Android pane, right-click on the values folder, and select New > Values resource file.

  2. Name the file integers.xml and click OK.

  3. Create an integer constant between the tags called grid_column_count and set it equal to 2:

    < integer name="grid_column_count">2</ integer >

  4. Create another values resource file, again called integers.xml; however, the name will be modified as you add resource qualifiers from the Available qualifiers pane. The resource qualifiers are used to label resource configurations for various situations.

  5. Select Orientation in the Available qualifiers pane, and press the >> symbol in the middle of the dialog to assign this qualifier.

  6. Change the Screen orientation menu to Landscape, and notice how the directory name values-land appears. This is the essence of resource qualifiers: the directory name tells Android when to use that specific layout file. In this case, that is when the phone is rotated to landscape mode.

  7. Click OK to generate the new layout file.

  8. Copy the integer constant you created into this new resource file, but change the value to 4.

    < integer name="grid_column_count">4</ integer >

[You have these files][1]

  1. Now all you have to do is re-assigning your span count when configuration changed as below:
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        gridLayoutManager?.let {
            it.spanCount = resources.getInteger(R.integer.grid_column_count)
        }
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        gridLayoutManager = GridLayoutManager(requireContext(), 
                                resources.getInteger(R.integer.grid_column_count))
        ${yourRecyclerView}.layoutManager = gridLayoutManager
        ...
  [1]: https://i.sstatic.net/3NZdq.png
Luxate answered 12/2, 2022 at 22:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.