Home » Android » android – RecyclerView Multiple Layout View(s) in an Adapter class

android – RecyclerView Multiple Layout View(s) in an Adapter class

Posted by: admin June 15, 2020 Leave a comment

Questions:

Here is what I have achieved ? 3 different sections, 10 different items in each section.

Here is the tutorial link I am following and below is the Screenshot:

enter image description here

Trying to show different Views for each and every Section. Like:

For Section 1 (layout_1.xml)

For Section 2 (layout_2.xml)

For Section 3 (layout_3.xml)

But showing layout view of layout_1.xml in every Section… (Section 1, 2, 3)

May I know where I am doing mistake in my code, what I have missed ?

public class SectionListDataAdapter extends RecyclerView.Adapter<SectionListDataAdapter.SingleItemRowHolder> {

    private ArrayList<SingleItemModel> itemsList;
    private Context mContext;

    public SectionListDataAdapter(Context context, ArrayList<SingleItemModel> itemsList) {
        this.itemsList = itemsList;
        this.mContext = context;
    }

    @Override
    public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

        switch (i) {

            case 0:
                View viewONE = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_1, null, false);
                SingleItemRowHolder rowONE = new SingleItemRowHolder(viewONE);
                return rowONE;

            case 1:
                View viewTWO = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_2, null, false);
                SingleItemRowHolder rowTWO = new SingleItemRowHolder(viewTWO);
                return rowTWO;

            case 2:
                View viewTHREE = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_3, null, false);
                SingleItemRowHolder rowTHREE = new SingleItemRowHolder(viewTHREE);
                return rowTHREE;

        }

        return null;

    }

    @Override
    public void onBindViewHolder(SingleItemRowHolder holder, int i) {

        SingleItemModel singleItem = itemsList.get(i);
        holder.tvTitle.setText(singleItem.getName());
    }

    @Override
    public int getItemCount() {
        return (null != itemsList ? itemsList.size() : 0);
    }

    public class SingleItemRowHolder extends RecyclerView.ViewHolder {

        protected TextView tvTitle;

        protected ImageView itemImage;


        public SingleItemRowHolder(View view) {
            super(view);

            this.tvTitle = (TextView) view.findViewById(R.id.tvTitle);
            this.itemImage = (ImageView) view.findViewById(R.id.itemImage);

            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    Toast.makeText(v.getContext(), tvTitle.getText(), Toast.LENGTH_SHORT).show();

                }
            });

        }

    }

}
How to&Answers:

Use this inside adapter’s getItemViewType:

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return 0;
            } else if(position == 1) {
                return 1;
            } else {
              return 2;
            }
        }

Answer:

to use multiple layouts according to position in recyclerview you have to override the getItemViewType(int position) method inside the adapter :-

 @Override
    public int getItemViewType(int position) {
        if(position==0)
            return  0;
        else if(position==1)
            return  1;
        else
            return 2;
    }

Answer:

FYI

RecyclerView can also be used to inflate multiple view types .

  • It will be easiest for you that create different Holder.
  • Create Different Adapter is best Solutions

Try with

  @Override
        public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

            switch (i) {

                case 0:
                    View viewONE = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_1, null, false);
                    SingleItemRowHolder rowONE = new SingleItemRowHolder(viewONE);
                    return rowONE;

                case 1:
                    View viewTWO = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_2, null, false);
                    SingleItemRowHolderTwo rowTWO = new SingleItemRowHolderTwo (viewTWO);
                    return rowTWO;

                case 2:
                    View viewTHREE = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_3, null, false);
                    SingleItemRowHolderThree rowTHREE = new SingleItemRowHolderThree(viewTHREE);
                    return rowTHREE;

            }

            return null;

        }

Read RecyclerView can also be used to inflate multiple view types

Answer:

As it was mention already, in order to getItemViewType method of RecyclerView.Adapter class, because if you will see in implementation of that method, you will see that it just return 0 all the time.

    public int getItemViewType(int position) {
        return 0;
    }

And here adjusted code of your adapter which should solve your problem.

public class SectionListDataAdapter extends RecyclerView.Adapter<SectionListDataAdapter.SingleItemRowHolder> {
    private static final int ITEM_TYPE_ROW_1 = 0;
    private static final int ITEM_TYPE_ROW_2 = 1;
    private static final int ITEM_TYPE_ROW_3 = 2;

    private ArrayList<SingleItemModel> itemsList;
    private Context context;

    public SectionListDataAdapter(Context context, ArrayList<SingleItemModel> itemsList) {
        this.itemsList = itemsList;
        this.context = context;
    }

    @Override
    public int getItemViewType(int position) {
        switch (position) {
            case 0:
                return ITEM_TYPE_ROW_1;
            case 1:
                return ITEM_TYPE_ROW_2;
            case 2:
                return ITEM_TYPE_ROW_3;
        }
        throw new RuntimeException(String.format("unexpected position - %d", position));
    }

    @Override
    public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        switch (viewType) {
            case ITEM_TYPE_ROW_1:
                View viewOne = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_1, null, false);
                return new SingleItemRowHolder(viewOne);
            case ITEM_TYPE_ROW_2:
                View viewTwo = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_2, null, false);
                return new SingleItemRowHolder(viewTwo);
            case ITEM_TYPE_ROW_3:
                View viewThree = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_3, null, false);
                return new SingleItemRowHolder(viewThree);
        }

        throw new RuntimeException(String.format("unexpected viewType - %d", viewType));
    }

    @Override
    public void onBindViewHolder(SingleItemRowHolder holder, int i) {
        SingleItemModel singleItem = itemsList.get(i);
        holder.tvTitle.setText(singleItem.getName());
    }

    @Override
    public int getItemCount() {
        return (null != itemsList ? itemsList.size() : 0);
    }

    class SingleItemRowHolder extends RecyclerView.ViewHolder {
        TextView tvTitle;
        ImageView itemImage;

        public SingleItemRowHolder(View view) {
            super(view);

            this.tvTitle = (TextView) view.findViewById(R.id.tvTitle);
            this.itemImage = (ImageView) view.findViewById(R.id.itemImage);

            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(v.getContext(), tvTitle.getText(), Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

}

Answer:

You can do it in a more simple way. Pass any flag while initializing adapter.

public class SectionListDataAdapter extends RecyclerView.Adapter<SectionListDataAdapter.SingleItemRowHolder> {
    private ArrayList<SingleItemModel> itemsList;
    private Context context;
    private int view;

    public SectionListDataAdapter(Context context, ArrayList<SingleItemModel> itemsList, int layoutFlag) {
        this.itemsList = itemsList;
        this.context = context;
        switch(layoutFlag) {
           case 0:
               view = R.layout.layout_1;
               break;
           case 1:
               view = R.layout.layout_2;
               break;
           case 2:
               view = R.layout.layout_3;
               break;
        }
    }
...
...
...
}

Use this view for layout reference. You just have to tell which layout to be inflated at the time of setting adapter.

Answer:

You need to override the method

int getItemViewType (int position)

It receives the row number and you need to return the “type” of row i.e. 1 2 or 3.

The result will then be passed to onCreateViewHolder.

Answer:

If for example you want to show this list of views:

type1
type2
type3
type1
type2
type3

then that should do the work:

public class SectionListDataAdapter extends RecyclerView.Adapter<SectionListDataAdapter.SingleItemRowHolder> {
    private static final int ITEM_TYPE_ROW_1 = 0;
    private static final int ITEM_TYPE_ROW_2 = 1;
    private static final int ITEM_TYPE_ROW_3 = 2;

    private ArrayList<SingleItemModel> itemsList;
    private Context context;
    private ArrayList<Integer> viewTypes = new ArrayList<>();

    public SectionListDataAdapter(Context context, ArrayList<SingleItemModel> itemsList) {
        this.itemsList = itemsList;
        this.context = context;

        viewTypes.add(ITEM_TYPE_ROW_1);
        viewTypes.add(ITEM_TYPE_ROW_2);
        viewTypes.add(ITEM_TYPE_ROW_3);
        viewTypes.add(ITEM_TYPE_ROW_1);
        viewTypes.add(ITEM_TYPE_ROW_2);
        viewTypes.add(ITEM_TYPE_ROW_3);

    }

    @Override
    public int getItemViewType(int position) {
       return viewTypes.get(position);            
    }

    @Override
    public int getItemCount() {
        return viewTypes.size();
    }


    .......
    ........

If you want to add/remove rows then it can be done inserting/removing viewTypes in the viewTypes Array and then calling RecyclerView notifyItemInserted or notifyItemRemoved methods the list will be updated with the new order and type of views.

Answer:

Simply use frame layout with your fragment and add this fragment in your framelayout that will add like you want for that. So its also easy to handle. hope this will help you

Answer:

Yes you need to override getItemViewType(int position) method which helps to inflate different views in recyclerview.

I am posting a sample code which may help you.

public class TransactionHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final int TYPE_HEADER = 1;
private final int TYPE_CHILD = 2;
private final Context mContext;
private final List<TransactionResultEntity> mTransactionList;


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case TYPE_HEADER:
            View headerView = LayoutInflater.from(mContext)
                    .inflate(R.layout.row_transaction_header, parent, false);
            return new ParentTypeDataObjectHolder(headerView);
        case TYPE_CHILD:
            View childView = LayoutInflater.from(mContext)
                    .inflate(R.layout.row_transaction_child, parent, false);
            return new ChildTypeDataObjectHolder(childView);
    }
    return null;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    switch (holder.getItemViewType()) {
        case TYPE_HEADER:
            ParentTypeDataObjectHolder parentTypeDataObjectHolder = (ParentTypeDataObjectHolder) holder;
            parentTypeDataObjectHolder.headerYearMonthTv.setText(mTransactionList.get(holder.getAdapterPosition()).getRowLabel());
            break;

        case TYPE_CHILD:
            ChildTypeDataObjectHolder childTypeDataObjectHolder = (ChildTypeDataObjectHolder) holder;
           childTypeDataObjectHolder.txnAmountTv.setText(mTransactionList.get(holder.getAdapterPosition()).getTransactionAmount());
            break;
    }
}


@Override
public int getItemCount() {
    return mTransactionList.size();
}

@Override
public int getItemViewType(int position) {
    if (mTransactionList.get(position).getDataType() == TYPE_HEADER)
        return TYPE_HEADER;
    else
        return TYPE_CHILD;
}

class ParentTypeDataObjectHolder extends RecyclerView.ViewHolder {

    private final TextView headerYearMonthTv;

    public ParentTypeDataObjectHolder(View itemView) {
        super(itemView);
        headerYearMonthTv = (TextView) itemView.findViewById(R.id.row_transaction_header_tv);
    }
}

class ChildTypeDataObjectHolder extends RecyclerView.ViewHolder {
    TextView txnAmountTv;

    public ChildTypeDataObjectHolder(View itemView) {
        super(itemView);
        txnAmountTv = (TextView) itemView.findViewById(R.id.transaction_child_txn_amount_tv);
                }
}

}

Answer:

All you need to do is override the method getItemViewType() inside your adapter.

You can write it as:

 @Override
    public int getItemViewType(int position) {
        if (position < 0) {
            return 0;
        } else if(position < 20) {
            return 1;
        } else {
          return 2;
        }
    }

Now the above logic works if your itemsList ArrayList have first 10 items of section 1, next 10 items of section 2, and last 10 items of section 3.

If that is not the case then you can have an integer field sectionNumber in your SingleItemModel class which specifies the section number in which that model belongs to. Now you can modify the method getItemViewType() as

@Override
public int getItemViewType(int position) {
    SingleItemModel singleItemModel = itemsList.get(position);
    if (singleItemModel.getSection() == 1) {
        return 0;
    } else if(singleItemModel.getSection() == 2) {
        return 1;
    } else {
      return 2;
    }
}

Answer:

Okay, If I got it right, you want to make the second adapter, the one providing the row lists, variable, so it supports different layouts, based not on its position, but on some data from the main adapter (the one providing the sections). Therefore, overriding getItemViewType won’t work, because the section data is contained in the main adapter, it does not even get there. So, the best, and cleanest course, would be to use… abstraction. Forget about multiple viewholders. Use one, and into it, bind a custom view. The custom views will provide both the specific layout files and will set the controls included in them. The holder will do just what it is intended to do: save ram by reusing views. the adavantage of this, is that you can have a clean hierarchy, which can grow in complexity in time, instead of a big, fat adapter which will become too hard to maintain.Here it is:

Since this is a lot of code to put it in here, I took your example project and modified to provide what I understood you were trying to do. Here it is:

https://github.com/fcopardo/exampleCustomViewsInHolder/tree/master

The highlights:

public class SectionListDataAdapter extends RecyclerView.Adapter<SectionListDataAdapter.SingleItemRowHolder> {

    private ArrayList<SingleItemModel> itemsList;
    private Context context;
    private String section;

    public SectionListDataAdapter(Context context, ArrayList<SingleItemModel> itemsList, String sectionName) {
        this.itemsList = itemsList;
        this.context = context;
        this.section = sectionName;
    }

    @Override
    public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        return new SingleItemRowHolder(RowFactory.getRow(context, section));
    }

    @Override
    public void onBindViewHolder(SingleItemRowHolder holder, int i) {
        holder.setData(itemsList.get(i));
    }

    @Override
    public int getItemCount() {
        return (null != itemsList ? itemsList.size() : 0);
    }

    public class SingleItemRowHolder extends RecyclerView.ViewHolder {

        protected AbstractRowElement rowElement;

        public SingleItemRowHolder(AbstractRowElement view) {
            super(view);
            this.rowElement = view;
        }

        public void setData(SingleItemModel singleItemModel){
            rowElement.setItem(singleItemModel);
        }

    }



}

this is the variable layout adapter. As you can see, it uses only one ViewHolder, and a factory to provide the view instances you need.

public class RowFactory {

    public static AbstractRowElement getRow(Context context, String name){
        switch (name){
            case "Section 1": return new FullRowElement(context);
            case "Section 2": return new TextRowElement(context);
            case "Section 3": return new ImageRowElement(context);
            default:
                Log.e("inflate", name);
                return new FullRowElement(context);
        }
    }
}

this provides the custom views, each one using a different layout, but working with the same dataset, based on the section title.

public abstract class AbstractRowElement extends CardView{

    protected int layout = 0;
    protected SingleItemModel singleItemModel;

    public AbstractRowElement(Context context) {
        super(context);
        inflateBaseLayout();
    }

    public AbstractRowElement(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflateBaseLayout();
    }

    public AbstractRowElement(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inflateBaseLayout();
    }

    protected void inflateBaseLayout() {
        this.setContainer();
        if(this.layout != 0) {
            LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            inflater.inflate(layout, this, true);
            this.inflateComponents();
        }
    }

    protected abstract void setContainer();
    protected abstract void inflateComponents();

    public void setItem(SingleItemModel itemModel){
        this.singleItemModel = itemModel;
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), singleItemModel.getName()+"\n"+singleItemModel.getDescription(), Toast.LENGTH_SHORT).show();
            }
        });
        setData(singleItemModel);
    }
    public abstract void setData(SingleItemModel itemModel);

}

Finally, this is the base view class for the adapter. Subclasses define the layout file to use, and put the desired data in the controls. The rest is pretty straightforward.

It would be totally possible to make this without custom views. You could just make something like :

int layoutFile = getLayoutForSection(section);
View v = LayoutInflater.from(viewGroup.getContext()).inflate(layoutFile, null);

But since I don’t know how complex is the view you intend to create, it is best to keep things nicely separated. Have fun!