11

I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.

I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this

layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
                return 1;
            else
                return columnCount; //show one item per row
        }
    });

@Override
public void onClick(View v) {
    if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
        showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
    else
        showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;


    int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
    adapter = new ProductAdapter(getActivity(), productList, showType);
    recyclerView.setAdapter(adapter);
    layoutManager.scrollToPosition(firstVisibleItem);
}

The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?

Any solution that doesn't uses multiple adapters is good enough!

Bugs Happen
  • 2,169
  • 4
  • 33
  • 59
Alireza Ahmadi
  • 5,122
  • 7
  • 40
  • 67

2 Answers2

19

What you need to do is:

  1. Modify your Adapter:

  • Specify two types of Views that your Adapter can inflate:

private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
  • Create a field that can store current type mCurrentType

  • Use your Adapter's getItemViewType. For example like this:

@Override
public int getItemViewType (int position) {
    return mCurrentType;
}
  • In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.

public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
    if (viewType == LARGE_GRID_ITEM) {
        //return large grid view holder
    } else {
        //return small grid view holder
    }
}
  • Additionally you can create methods:

public void toggleItemViewType () {
    if (mCurrentType == LARGE_GRID_ITEM){
        mCurrentType = SMALL_GRID_ITEM;
    } else {
        mCurrentType = LARGE_GRID_ITEM;
    }
}

public boolean displaysLargeGrid(){
    return mCurrentType == LARGE_GRID_ITEM;
}
  1. Modify the code you posted:

layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (adapter.displaysLargeGrid()) {
                return 1;
            } else {
                return columnCount; //show one item per row
            }
        }
    });

@Override
public void onClick(View v) {
    adapter.toggleItemViewType();
    adapter.notifyDataSetChanged();
}
Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
  • 3
    I think you didn't get my problem. your code is fine but when you call toggle mCurrentType changes but createViewHolder will not be get called! notifyDataSetChanged() only force onBindView to be called not onCreateViewHolder(). So problem remains, how can also force onCreateViewHolder() to be recalled so the right item get inflated? – Alireza Ahmadi Oct 26 '15 at 10:33
  • 1
    I did get your problem. You didn't get my answer. It takes your problem and gives you the proper approach. Did you even try my code? It will call `onCreateViewHolder` if there is no holder for the large grid. My code changes what `getItemViewType` returns. When you call `notifyDataSetChanged`, adapter will check itemViewTypes for positions and decide if it needs to call `onCreateViewHolder` (if there is no available holder for this type of View) or just `onBindViewHolder` (if it already has some holder left). You need to have two types of holders. – Bartek Lipinski Oct 26 '15 at 10:39
  • 1
    His code would work because he has purged the entire list by calling notifyDataSetChanged() this is bad for performance, when you should be calling notifyItemChanged(). calling notifyDataSetChanged() is like creating the list all over again. For a small list you can do that, but with a large number of items or heavy single row layout you shouldn't. – Mayank Feb 23 '16 at 17:25
  • This is not an answer! you changed the title completely – mahdi azarm Jul 20 '20 at 17:45
  • @mahdiazarm the SO question is more than just a title. It has a description as well. This is the correct answer for the problem that was presented in the question. – Bartek Lipinski Jul 20 '20 at 18:08
3

Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
box
  • 4,450
  • 2
  • 38
  • 38