Draw empty cells in RecyclerView - android

In my case I have recycler view with Grid Layout Manager and custom Decorator. In every row(which have 3 column maximum) can be from 1 to 3 items.
The question is how to draw empty cells in case when count of items in the line are one or two? Warning!!! It’s not a lastRow!
Example:
Span count in row 3, elements from collection to draw = 2 how to draw empty cell in 3rd column?
Span count in row 3, elements from collection to draw = 1 how to draw empty cell in 2nd and 3rd columns?

Adapter for Recyclerview has to know about the view. For this purpose we use getItemViewType.By getting view type we can add any view to the recyclerview.
public class MainRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static int ADD_VIEW_HOLDER = 0;
private final static int EMPTY_VIEW_HOLDER = 1;
public List<DataList> mDrinkItems;
#Override
public int getItemViewType(int position) {
if (position== mDrinkItems.size()-1)
return EMPTY_VIEW_HOLDER;
else
return ADD_VIEW_HOLDER;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
switch (viewType){
case ADD_VIEW_HOLDER:
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.li_main_content,parent,false);
return new MainItemHolder(view);
case EMPTY_VIEW_HOLDER:
View EMPTYView= LayoutInflater.from(parent.getContext()).inflate(R.layout.li_EMPTY_action,parent,false);
return new EMPTYViewItemHolder(EMPTYView);
default:
return null;
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
int size= mDrinkItems.size();
switch (getItemViewType(position)){
case ADD_VIEW_HOLDER:
((MainItemHolder)holder).bindData(mDrinkItems.get(position),position,size);
break;
case EMPTY_VIEW_HOLDER:
((EMPTYViewItemHolder)holder).bindData(mDrinkItems.get(position),position,size);
break;
}
}
#Override
public int getItemCount() {
return mDrinkItems.size();
}
public void setData(List<DailyConsumption> mlist) {
this.mDrinkItems = mlist;
notifyDataSetChanged();
}
When adding data to list to set adapter you get the last index of list to set empty view.
List<DataList> mList = new ArrayList<>();
mList.add(mList.size(),new DataList(// whatever you want to add));

You can achieve it by defining different view types.
Just Override the getItemViewType()
#Override
public int getItemViewType(int position) {
if (position < mElementsList.size()) {
return ITEM_TYPE_NORMAL;
} else if (position >= mElementsList.size()) {
return ITEM_TYPE_EMPTY;
}
}
}
Then Override onCreateViewHolder
onCreateViewHolder(Viewgroup parent, int viewType) {
if(viewType == ITEM_TYPE_NORMAL) {
//Use layout for normal type
} else if (viewType == ITEM_TYPE_EMPTY) {
//Use layout for emptyType
}
}

Related

chatting recyclerview's type2 don't display

i want it
left display is Data class and view type ==0
right display is MeData class and view type ==2
Currently I would like to use two multiview types using recyclerview.
Data and MeData are two types class.
If return value of getItemViewType (int position) is 0, ui for data class works normally, but if return value 2, Me data class does not work and error occurs.
+
There is one function defined differently for the two view types, which does not define the getItemCount function for MeData.
private ArrayList<Data> Data = new ArrayList<Data>();
private ArrayList<MeData> MeData = new ArrayList<MeData>();
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if(viewType == 0) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chatitem, parent, false);
return new ViewHolder0(view);
}
else{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chatitem2, parent, false);
return new ViewHolder2(view);
}
}
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
//return position 0; <- not error
return position % 2 * 2; <- error
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
if(holder.getItemViewType() == 0){
((ViewHolder0) holder).onBind(Data.get(position));
}
else{
((ViewHolder2) holder).onBind(MeData.get(position));
}
}
#Override
public int getItemCount() {
return Data.size();
}
void addItem(Data data) {
Data.add(data);
}
void addItem(MeData data) {
MeData.add(data);
}
Why does an error occur if the return value of getItemViewType is 2? And how do we solve it?

RecyclerView with GridLayoutManager and first element with different viewHolder

I need to create a recyclerView with a GridLayoutManager on two lines, and the first element to be bigger than the rest. The result should look like this:
I managed to achieve this, but in an unconventional way. In my recyclerView adapter I use a different viewHolder for the first element, a bigger one. That would have been a great solution, but the second element would have gone below the first element. So I made a trick of giving recyclerView a fixed height same as the first element, that way the first element and the second would overlap and I would simply make the visibility of the second to gone.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0:
final View view = inflater.inflate(R.layout.big_item, parent, false);
return new BigViewHolder(view);
case 2:
final View view2 = inflater.inflate(R.layout.normal_item, parent, false);
return new NormalViewHolder(view2);
default:
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 1) {
holder.itemView.setVisibility(View.GONE);
}}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return 0;
} else return 2;
}
But I don't particularly like this approach. Does anyone have a better idea on this?
After some digging and counseling, I came to a pretty nice result. I will post it below:
First, the adapter should look like I mentioned in the question, without the hiding of the second element:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int TYPE_FIRST_ITEM = 0;
public static final int TYPE_ITEM = 1;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_FIRST_ITEM:
final View view = inflater.inflate(R.layout.big_item, parent, false);
return new BigViewHolder(view);
case TYPE_ITEM:
final View view2 = inflater.inflate(R.layout.normal_item, parent, false);
return new NormalViewHolder(view2);
default:
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case TYPE_FIRST_ITEM:
BigViewHolder bigViewHolder = (BigViewHolder) holder;
// Do what you need for the first item
break;
case TYPE_ITEM:
NormalViewHolder normalViewHolder = (NormalViewHolder) holder;
// Do what you for the other items
break;
}
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_FIRST_ITEM;
} else return TYPE_ITEM;
}
final class NormalViewHolder extends RecyclerView.ViewHolder {
public NormalViewHolder(View itemView) {
super(itemView);
}
// find your views here
}
final class BigViewHolder extends RecyclerView.ViewHolder {
public BigViewHolder(View itemView) {
super(itemView);
}
// find your views here
}
}
Second, the layout manager should implement a listener to specify how many spans (rows) should be at a specific position:
RecyclerView mRecyclerView = view.findViewById(R.id.my_recycler_view);
MyAdapter mAdapter = new MyAdapter();
GridLayoutManager mLayoutManager = new GridLayoutManager(getActivity(), 2, LinearLayoutManager.HORIZONTAL, false);
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
switch(mAdapter.getItemViewType(position)){
case MyAdapter.TYPE_FIRST_ITEM:
return 2;
case MyAdapter.TYPE_ITEM:
return 1;
default:
return -1;
}
}
});
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(mLayoutManager);
Hope it will help someone else too. Thank you Mike M. for your support.

RecyclerView not updating its views when updated with new data

I have a RecyclerView with a RecyclerView.Adapter implementation, which has a list of objects plus a header and a footer view, and I am switching on the viewType to show header for position 0 and footer for position getItemCount() (my "getItemCount()" implementation returns "data.size() + 2" to accommodate for header and footer). This all works great, the header and foot are shown at the correct places, but when I reach the end of the list and the footer is shown, I load more data and add that to my "data" list, and then I want the footer view to be replaced with the next loaded data view, but this does not happen. I have tried with alle the different "notifyX" methods on the adapter, but it seems that it does not get updated until i scroll the list (and then it all works as I want).
What am I missing here? How do I force the RecyclerView to redraw the last element in the list?
My adapter:
private class TransactionsAdapter extends RecyclerView.Adapter {
private List transactions;
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
private TransactionsAdapter(List transactions) {
this.transactions = transactions;
}
public void add(Transaction transaction) {
transactions.add(transaction);
notifyItemInserted(transactions.size());
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == TYPE_HEADER) {
ViewHolder vh = new ViewHolder(inflate(getContext(), R.layout.postings_list_header, null), TYPE_HEADER);
return vh;
} else if(viewType == TYPE_FOOTER) {
ViewHolder vh = new ViewHolder(inflate(getContext(), R.layout.postings_list_footer, null), TYPE_FOOTER);
return vh;
}
SlidingCellView cellView = new SlidingCellView(getContext());
cellViews.add(cellView);
ViewHolder vh = new ViewHolder(cellView, TYPE_ITEM);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
if (holder.getType() == TYPE_HEADER) {
final TextView text = (TextView) holder.itemView.findViewById(R.id.postings_header_text);
text.setText("Dette er nu godt");
} else if (holder.getType() == TYPE_ITEM) {
final Transaction transaction = transactions.get(position - 1);
((SlidingCellView) holder.itemView).update(new SlidingCellView.SlidingCellViewResources() {
#Override
public String getSlidingCellName() {
return transaction.getText();
}
});
} else if (holder.getType() == TYPE_FOOTER) {
final TextView text = (TextView) holder.itemView.findViewById(R.id.no_more_postings);
if(transactions.size() == 0) {
text.setText(getResources().getString(R.string.no_postings));
} else {
if(resources.getTransactions().hasMore()) {
text.setText(getResources().getString(R.string.more_postings));
} else {
text.setText(getResources().getString(R.string.end_of_postings));
}
}
}
}
#Override
public int getItemCount() {
return transactions.size() + 2;
}
#Override
public int getItemViewType(int position) {
if(position == 0) {
return TYPE_HEADER;
}
if(position == transactions.size() + 1) {
return TYPE_FOOTER;
}
return TYPE_ITEM;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private int type;
public ViewHolder(View itemView, final int type) {
super(itemView);
this.type = type;
}
public int getType() {
return type;
}
}
}
Code that add to transactions:
public void transactionsAdded() {
for (Transaction trans : resources.getTransactions()) {
postingsListAdapter.add(trans);
}
}
So, if I scroll to the bottom of the list, the "R.layout.postings_list_footer" view is correctly displayed at the bottom of the list, then when the service call returns (code not shown) the "transactionsAdded" gets called and I would expect the list to redraw itself and thus draw the last shown element as a normal item and not a footer, but this only happens when I start to scroll in the list.
Thank you
Søren
You can add to onBindViewHolder:
if (position == getItemCount() - 1) {
// request more transactions
}
Then your callback calls the add method, that should call notifyDataSetChanged:
public void add(Transaction transaction) {
transactions.add(transaction);
notifyDataSetChanged();
}
There is a working example without footer here.
If you need a good library to easily handle header/footers in your RecyclerView you should try SectionedRecyclerViewAdapter.

How to implement a chat list view with heterogenous row types (image video and text) using listview?

I'm building a normal chat application. Image,text and videos can be sent hence I need multiple row styles in my list. I used listview. But Listview returned (after recycling rows) wrong rows many times and my attempt failed. Any Idea how to do this? Can this be done using RecyclerView?
Thanks.
It can be done with recyclerview. Here is what I did for displaying multiple types of rows in a single recyclerview
// Different types of rows
private static final int TYPE_ITEM1 = 0;
private static final int TYPE_ITEM2 = 1;
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
// Viewholder for row type 0
}
class ViewHolder1 extends RecyclerView.ViewHolder {
// Viewholder for row type 1
}
#Override
public int getItemViewType(int position) {
// Here you have to write logic for which position have which type of row and return the same
if (position == 0) {
return TYPE_ITEM0;
} else {
return TYPE_ITEM1;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.itme0, viewGroup, false););
case 1: return new ViewHolder1(LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item1, viewGroup, false););
...
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
if (viewHolder.getItemViewType() == TYPE_ITEM0) {
// Code to populate type 0 view here
} else if (viewHolder.getItemViewType() == TYPE_ITEM1) {
// code to populate type 1 view here
}
}

How to Change the viewType of a RecyclerView item onClick

I have created a RecyclerView with Cards as its primary viewtype. What I am trying to achieve is that when user clicks on a item the ViewType of that item is changed from cards to another Viewtype(for example a list textviews placed horizontally scrollable).
My code for RecyclerView Adapter is as follows :
In the below piece of code I have created a arrraylist of enum types to keep track of the clicked state of each row in recyclerview, then I got the size of my dataset and initialized my arraylist for each row with SHOW_PRIMARY_CONTENT
public class DisplayItemsAdapter extends RecyclerView.Adapter<DisplayItemsAdapter.ViewHolder> {
private static ArrayList<clickedState> itemClickedState;
private enum clickedState {
SHOW_PRIMARY_CONTENT,
SHOW_SECONDARY_CONTENT
}
private ArrayList<ItemData> mDataset = new ArrayList<>();
public DisplayItemsAdapter(ArrayList<ItemData> dataset) {
mDataset = dataset;
itemClickedState = new ArrayList<>();
for (int i = 0; i < mDataset.size(); i++) {
itemClickedState.add(i, clickedState.SHOW_PRIMARY_CONTENT);
}
}
Below is my ViewHolder Class which holds references to all my child views, It Implements View.OnClickListener. Why it implements View.OnClickListener is so that it can toggle between clicked states to change viewTypes accordingly.
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tvItemName;
TextView tvItemNumber;
public ViewHolder(View itemView) {
super(itemView);
tvItemName = (TextView) itemView.findViewById(R.id.tvItemName);
tvItemNumber = (TextView) itemView.findViewById(R.id.tvItemNumber);
tvTicketClass = (TextView) itemView.findViewById(R.id.tvTicketClass);
}
#Override
public void onClick(View v) {
itemClickedState.add(getAdapterPosition(), clickedState.SHOW_SECONDARY_CONTENT);
}
}
This is where I am getting the value of viewType returned by getItemViewType and returning inflated layout
#Override
public DisplayItemsAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v1 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.display_Items_row, viewGroup, false);
View v2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.display_Items_ticket_class, viewGroup, false);
if (viewType == 0) {
return new ViewHolder(v1);
} else {
return new ViewHolder(v2);
}
}
Then here I m binding the views with values according to the viewType returned by the viewholderusing viewholder.getItemViewType
#Override
public void onBindViewHolder(DisplayItemsAdapter.ViewHolder viewHolder, final int position) {
if (viewHolder.getItemViewType() == 0) {
viewHolder.tvItemName.setText(mDataset.get(position).strItemName);
viewHolder.tvItemNumber.setText(mDataset.get(position).strItemNumber);
} else if (viewHolder.getItemViewType() == 1) {
for (int i = 0; i < mDataset.get(position).strClass.length; i++) {
viewHolder.tvTicketClass.setText(mDataset.get(position).strClass[i]);
}
}
}
#Override
public int getItemCount() {
return mDataset.size();
}
And finally my getItemViewType logic
#Override
public int getItemViewType(int position) {
if (itemClickedState.get(position) == clickedState.SHOW_SECONDARY_CONTENT)
return 1;
return 0;
}
}
What I could not understand is that why is not anything show when I run the code ,,,Everything seems valid to me . Help Me !!!!
see my example:
// RecycleItemObject.java
public class RecycleItemObject {
public static int LAYOUT_ITEM_PORTRAIN = 0;
public static int LAYOUT_ITEM_LANDSCAPE = 1;
private int type;
....................
/**
* #return get layout type of item
*/
public int getType() {
return type;
}
/**
* set layout type of item
* #param type
*/
public void setType(int type) {
this.type = type;
}
}
// adapter
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == RecycleItemObject.LAYOUT_ITEM_PORTRAIN) {
view = LayoutInflater.from(context).inflate(R.layout.item_recycle_view_main, parent, false);
} else {
view = LayoutInflater.from(context).inflate(R.layout.item_recycle_view_main_land, parent, false);
}
}
// event click item
#onClick....
curentObject.setType(RecycleItemObject.LAYOUT_ITEM_LANDSCAPE);
// notify item only
notifyItemChanged(itemPosition);
with me it work fine
Hope you have figured out your problem. If not then,I have done it f or you.
The buggy line is
#Override
public void onClick(View v) {
itemClickedState.add(getAdapterPosition(), clickedState.SHOW_SECONDARY_CONTENT);
}
Here what you are doing is adding a new Item to
itemClickedState.add(getAdapterPosition(), clickedState.SHOW_SECONDARY_CONTENT);
This will insert a new item and size of mdataList and itemClickState differs and hence mis matching happens.
Use itemClickedState.set(getAdapterPosition(), clickedState.SHOW_SECONDARY_CONTENT);
instead. And second thing I does not understand what tvTicketClass is . If it is a single TextView, then you should concatenate the string first and them do tvTicketClass.settext(concatenatedstrClass) . Hope you get helped from this post.If have any further issue , please do comment.
override getItemViewType in adapter and return itemtype you want. for more check out this link https://guides.codepath.com/android/Heterogenous-Layouts-inside-RecyclerView

Categories

Resources