Tuesday, December 23, 2014

RecyclerView: Autofit grid

After I made a RecyclerView grid with a header, I realized that I want to make it auto fit as well. I want to define the size of each item and let the system compute the spanCount automatically.

To determine the number of spans, I need two things: the width of the RecyclerView, and the width of each item.

AutofitRecyclerView

We extend RecyclerView to get access to its width. As for the width of each item, when GridView has android:numColumns="auto_fit", it uses android:columnWidth to compute the number of columns. Let's reuse that attribute.

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 GridLayoutManager(getContext(), 1);
  setLayoutManager(manager);
}

In the constructor, we read the value of android:columnWidth and save it in a member variable. Later in onMeasure will will use it to determine the span count.

Even though we will be setting the span count in onMeasure, the app will crash if we wait until then to define a GridLayoutManager, so we create one here as well, with a span count of 1.

In onMeasure, we ask the super class to perform the measurement, then take the value from getMeasuredWidth to compute the span count.

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

Notice the Math.max call? This makes sure that we will have at least a span count of 1, even if the column width is defined to be larger than the width of the RecyclerView.

With that, the span count changes with the width of the RecyclerView.

One thing to note: we are using the column width the compute the span count, and once that is given to the RecyclerView it will turn around and compute the width of each item. So for instance if your RecyclerView is 320dp wide and you use a column width of 72dp, a span count of 4 (320dp / 72dp = 4.4444) will be given to the RecyclerView, which will make your items 320dp / 4 = 80dp wide, not 72dp. Make sure your item layout takes that into account.

Source: https://github.com/chiuki/android-recyclerview

Why bother?

Since GridView already supports auto fit, why bother reimplementing it using RecyclerView? RecyclerView has a lot of other functionalities like built-in animations for inserting and removing items, reordering etc, so you may want to use that instead of GridView.

10 comments:

Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.

  1. Great article! I've been looking at implementing a custom gridview to set consistent row heights, but now I'm thinking it might be time to jump ship to the RecyclerView. Thanks for sharing.

    ReplyDelete
  2. Thank you so much. Awesome tricky work!

    ReplyDelete
  3. Thank you so much. Awesome tricky work!

    ReplyDelete
  4. Thank you. A very efficent way to solve an important problem.

    ReplyDelete
  5. Does your AutoFitRecyclerView work well when being used with a LinearLayoutManager rather than a GridLayoutManager? I’m currently using it with a LinearLayoutManager and there aren’t any issues. But I’m now wondering if are there any performance issues that I may be over looking.

    Great block post by the way!

    ReplyDelete
    Replies
    1. I'm surprised that you are using AutoFitRecyclerView with LinearLayoutManager because it doesn't have span count so the code shouldn't compile.

      Delete