RecyclerView, StaggeredGridLayoutManager Refresh Bug

user3316557 picture user3316557 · Nov 11, 2014 · Viewed 8.4k times · Source

I used support library v7-21 and the RecyclerView isn't showing correctly. GridLayoutManager and LinearLayoutManager is Ok. Problem only occurs when in StaggeredGridLayoutManager I Load my DataSet and then refresh the data. Data refresh is working fine but the RecyclerView's view exist out of the screen.

does anyone knows how to fix it?

Answer

MojoTosh picture MojoTosh · Jan 3, 2015

Updated 2015-01-04 (at bottom)

I have an example project to demonstrate this at https://github.com/dbleicher/recyclerview-grid-quickreturn . But here are a few more details that may help you out.

When you add/remove an item to the RecyclerView, you should call notifyItemInserted/notifyItemRemoved to have the adapter tell the layout manager to re-layout just the affected views. For example, in the adapter:

public void addItemAtPosition(int position, String item) {
    myDataset.add(position, item);
    mAdapter.notifyItemInserted(position);        
}

If you call this method to add a view, and the view is on-screen, SGLM seems to work as expected inserting and adjusting the layout. If, however, you are viewing the top of the list and you add the item at position zero, the view is created off-screen (and you won't see it). You can scroll to this view with this following code:

public void addItemAtPosition(int position, String item) {
    myDataset.add(position, item);
    mAdapter.notifyItemInserted(position);
    mSGLM.scrollToPosition(position);
}

There is (IMHO) a bug in StaggeredGridLayoutManager that is revealed with adding items "off-screen". According to comments from yiğit boyar in this thread https://plus.google.com/u/1/111532428576115387787/posts/6xxayUBz2iV

"...if an item is added out of bounds, layout manager does not care"

And here's where the bug appears. With SGLM, there is a timing issue with when the re-layout occurs. In my example code (link above) I have an ItemDecorator that adds margin to the top-most item(s) so they aren't obscured by the toolbar. When using the code above, the layout incorrectly retains this margin on the item that is moved "down" the screen when the new item(s) are inserted. Bummer.

Here's the layout before adding at the top: Layout before adding at top

Here's the layout demonstrating the bug after adding an item at the top: Bad layout

There is a workaround, but it somewhat defeats the purpose of using a RecyclerView. Basically, if you just call notifyDataSetChanged after adds/removes, this will case SGLM to invalidate it's entire layout. This is not optimal from an efficiency perspective, but it does result in a proper layout. Using the following code:

public void addItemAtPosition(int position, String item) {
    myDataset.add(position, item);
    mAdapter.notifyDataSetChanged();  // Should NOT do this, but it works!
    mSGLM.scrollToPosition(position);
}

Will result in the proper, post-addition layout: Proper layout result

Hope this helps.

Update: 2014-01-04

As noted in the comments, another workaround is to call invalidateItemDecorations() on the recyclerview after performing the insert. Right now, it appears that doing so immediately after the insert will ignore this call (possibly because a layout pass is already running). If one defers the call briefly, it does seem to work:

public void addItemAtPosition(int position, String item) {
    myDataset.add(position, item);
    mAdapter.notifyItemInserted(position);
    mSGLM.scrollToPosition(position);

    // Items added to the top row? Better invalidate the decorator.
    // Delay to ensure that the previous layout pass has completed.
    if (position < columnCount) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mRecycler.invalidateItemDecorations();
            }
        }, 300);
    }
}