What I'm trying to do is to display a list of cardViews with info in it, and one of the infos is a tablelayout populated with data added by user, so he can edit or delete a tableRow. The problem is that when an object is edit/deleted and notifyitemChanged(position), notifyDataSetChanged(position), notifyItemRemoved(position) methods are called, they duplicate the tableLayout view inside the cardView, giving it a creepy look, like this:
Tablelayout populated and duplicated after calling notify methods
private void removeRow(int position, int i) {
listX.get(position).getListY().remove(i);
if(listX.get(position).getListY().size()==0){
listX.remove(position);
this.notifyItemRemoved(position);
}
this.notifyDataSetChanged();
this.notifyItemChanged(position);
}
removeRow() is called everytime removeIcon is clicked
private void editRow(ObjectX objectX, int position, int index) {
Log.i("position : index",position+" : "+index);
listX.get(position).getListY().get(index).setAttributeX(objectX.getAttributeX());
this.notifyItemChanged(position);
}
editRow() is called everytime editIcon is clicked*
OnBindViewHolder()->
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
HolderExample holder = (HolderExample) viewHolder;
ObjectX objectX = this.listX.get(position);
holder.bind(objectX,listener);
for (TableRow tableRow:getRowsFromFata(objectX)) {
holder.tableLayout.addView(tableRow);
}
}
getRowsFromData(ObjectX objectX)->
private List<TableRow> getRowsFromFata(ObjectX objectX){
List<TableRow> tableRows = new ArrayList<>();
//... setting layoutParams
TableRow heading = new TableRow(context);
//Header
// ... creating and adding 4 textViews to "heading row" with addView(textView) method
tableRows.add(heading);
//Content
for(ExampleData exampleData:objextX.getListY()){
//creating and setting values of 4 textViews with each attribute of ExampleData
//getListY holds List<ExampleData> listY = ArrayList<>();
ImageView editIcon = new ImageView(context);
editIcon.setImageResource(R.drawable.ic_edit_black);
editIcon.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
public void onClick(View view) {
//opens an AlertDialog and when POSITIVE_BUTTON is clicked calls editRow() method
openAddTaskAlertDialog(objectX,listX.indexOf(objectX),objectX.getListY().indexOf(exampleData));
}
});
ImageView removeIcon = new ImageView(context);
removeIcon.setImageResource(R.drawable.ic_delete_forever);
removeIcon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
removeTaskCompleted(listX.indexOf(objectX),objectX.getListY().indexOf(exampleData));
}
});
TableRow contentRow = new TableRow(context);
//... adding textViews and ImageViews to contentRow
tableRows.add(contentRow);
}
return tableRows;
}
Related
I have two recycler views. The first recyclerview is basically a list of data where I can choose an item and its quantity and I am storing this chosen item data into a map. The second one is the list of the selected data. which I am generating from getting the values() from the map. The second one also has similar viewholder and I can change the quantity there also. One the quantity reaches zero I remove the item from the list and try to notifydatasetChanged().
The problem is the removing of an item from the second list is not working properly and the app crashes with error
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder
I am using a listener interface on my first Recycler so that when the quantity is changed and the item is added to the map. The adapter of the second recycler is notified of the changes. below is the code i am using to update the second recycler view.
public void updateList(){
mMap = ((UserMainActivity)getActivity()).getItemMap();
inputs.clear();
// adapter.notifyDataSetChanged();
adapter = new MyCartAdapter(inputs,getContext());
cartList.setAdapter(adapter);
for(AllItems t:mMap.values()) {
inputs.add(t);
}
adapter.notifyDataSetChanged();
}
Below is my second recycler view's adapter. Where I am changing the quantities of the selected items.
public class MyCartAdapter extends RecyclerView.Adapter<MyCartAdapter.MyCartViewHolder>{
private List<AllItems> listItems1;
private Context context;
private Typeface typeface;
public MyCartAdapter(List<AllItems> listItems1, Context context) {
this.listItems1 = listItems1;
this.context = context;
}
#NonNull
#Override
public MyCartAdapter.MyCartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cart_items_layout, parent, false);
return new MyCartViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull final MyCartAdapter.MyCartViewHolder holder, final int position) {
final AllItems orderItem = listItems1.get(position);
holder.setProductImage(orderItem.getImageUrl(),context);
holder.name.setText(orderItem.getName());
String price = String.valueOf(orderItem.getPrice());
holder.price.setText(price);
final HashMap<String, AllItems> items = ((UserMainActivity)context).getItemMap();
holder.counter.setText(orderItem.getQuantity());
holder.add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String quantity = String.valueOf(holder.counter.getText());
int count = Integer.parseInt(quantity)+1;
holder.counter.setText(String.valueOf(count));
String url = orderItem.getImageUrl();
AllItems newitem = new AllItems(orderItem.getName(),orderItem.getComname(),url, String.valueOf(count),orderItem.getWeight,orderItem.getPrice());
((UserMainActivity)context).addItem(orderitemname,newitem);
// notifyItemChanged(position);
}
});
//counter text iitem.textview showing the quantity of the selected item . integer count returns the value of counter text below i am checking if its zero than it simply sets the value to zero and else reduce it and update the map.
holder.minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String counterText = String.valueOf(holder.counter.getText());
int count = Integer.parseInt(counterText);
if (count==0){
holder.counter.setText("0");
}
if (count==1){
holder.counter.setText("0");
AllItems item = items.get(orderItem.getName());
if (item!=null){
String orderit = orderItem.getName();
((UserMainActivity)context).removeItem(orderit);
// here i am removing the value from the list which throws the exception
listItems1.remove(position);
notifyItemRemoved(position);
}
}
if (count>1){
String quantity = String.valueOf(count-1);
holder.counter.setText(quantity);
String orderitemname = orderItem.getName();
String url = orderItem.getImageUrl();
String weight = "100";
long weightl = Long.parseLong(weight);
AllItems newitem = new AllItems(orderItem.getName(),orderItem.getComname(),url, quantity,weight,orderItem.getPrice());
((UserMainActivity)context).addItem(orderitemname,newitem);
// listItems1.set(position, newitem);
// notifyItemChanged(position);
}
}
});
}
#Override
public int getItemCount() {
return listItems1.size();
}
public class MyCartViewHolder extends RecyclerView.ViewHolder {
public TextView name,price,count,comname;
public TextView weight;
LinearLayout add,minus;
TextView counter;
public MyCartViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.ProName);
price = (TextView) itemView.findViewById(R.id.proPrice);
weight = (TextView) itemView.findViewById(R.id.ProWeight);
counter = (TextView) itemView.findViewById(R.id.counter);
add = (LinearLayout) itemView.findViewById(R.id.addLin);
minus= (LinearLayout) itemView.findViewById(R.id.minusLin);
}
public void setProductImage(final String thumb_image, final Context ctx){
productImage = (ImageView) itemView.findViewById(R.id.ProImage);
Picasso.with(ctx).setIndicatorsEnabled(false);
Picasso.with(ctx)
.load(thumb_image)
.networkPolicy(NetworkPolicy.OFFLINE)
.placeholder(R.drawable.basket_b).into(productImage, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
Picasso.with(ctx).load(thumb_image).placeholder(R.drawable.basket).into(productImage);
}
});
}
public void setComname(String name){
comname = (TextView)itemView.findViewById(R.id.proComName);
comname.setText(name);
}
}
}
This jumps out at me:
listItems1.remove(position);
notifyItemChanged(position);
The notifyItemChanged() method exists to tell the adapter that the data at the given position has changed, and that the ViewHolder should be re-bound. This is not what you're doing; you're removing an item.
Probably your app is crashing because you're removing the last item in your data set (e.g. position 10) and then telling the adapter that the item at position 10 has changed... but now the maximum position in your data set is 9.
Instead, you should use the notifyItemRemoved() method.
listItems1.remove(position);
notifyItemRemoved(position);
When button clicked, i must update a TextView in same position and I have done it, but 9th and 10th position of RecyclerView follow first position and second position. In other word, if I clicked first button position, First position of TextView is updated, but, 9th position of TextView also updated, It should be not updated. How to solve this?
I follow this link
here is my Adapter
class ProductsByStoreAdapter extends RecyclerView.Adapter<ProductsByStoreAdapter.ViewHolder> {
private ArrayList<Products> products;
ProductsByStoreAdapter(ArrayList<Products> productses) {
this.products = productses;
//products = CenterRepository.getCenterRepository()
//.getListOfProductsInShoppingList();
}
#Override
public ProductsByStoreAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.products_card_item, viewGroup, false);
return new ProductsByStoreAdapter.ViewHolder(view);
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView tv_product_name, tv_product_price, tv_product_quantity;
private ImageView im_product_image;
private ImageButton button_add_product, button_min_product;
private EditText e_note;
private LinearLayout layout_note;
ViewHolder(View view) {
super(view);
im_product_image = (ImageView)view.findViewById(R.id.product_image);
tv_product_name = (TextView)view.findViewById(R.id.product_name);
tv_product_price = (TextView)view.findViewById(R.id.product_price);
tv_product_quantity = (TextView)view.findViewById(R.id.product_quantity);
e_note = (EditText)view.findViewById(R.id.e_note);
layout_note = (LinearLayout)view.findViewById(R.id.layout_note);
this.button_add_product = (ImageButton)view.findViewById(R.id.button_add_product);
button_min_product = (ImageButton)view.findViewById(R.id.button_min_product);
}
}
#Override
public void onBindViewHolder(final ProductsByStoreAdapter.ViewHolder viewHolder, final int position) {
Glide.with(viewHolder.im_product_image.getContext())
.load(products.get(position).getImage_uri())
.centerCrop()
.crossFade()
//.placeholder(R.drawable.placeholder_main)
.into(viewHolder.im_product_image);
CurrencyFormats currencyFormat = new CurrencyFormats();
viewHolder.tv_product_name.setText(products.get(position).getName());
viewHolder.tv_product_price.setText(currencyFormat.toRupiah(products.get(position).getPrice()));
//viewHolder.tv_product_quantity.setText("0");
viewHolder.button_add_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//current object
Products tempObj = (products).get(position);
((ProductsByStoreActivity)view.getContext()).updateItemCount(true);
tempObj.setQuantity(String.valueOf(1));
viewHolder.tv_product_quantity.setText(tempObj.getQuantity());
}
});
viewHolder.button_min_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Products tempObj = (products).get(position);
viewHolder.tv_product_quantity.setText(CenterRepository
.getCenterRepository().getListOfProductsInShoppingList()
.get(indexOfTempInShopingList).getQuantity());
((ProductsByStoreActivity)view.getContext()).updateItemCount(false);
}
}
}else {
}
}
});
}
#Override
public int getItemCount() {
//return products.size();
return products == null ? 0 : products.size();
}}
You need to move your onclick listener into onCreateViewHolder.
final ProductsByStoreAdapter.ViewHolder viewHolder = new ProductsByStoreAdapter.ViewHolder(view);
button_add_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//current object
Products tempObj = products.get(viewHolder.getAdapterPosition(););
((ProductsByStoreActivity)view.getContext()).updateItemCount(true);
tempObj.setQuantity(String.valueOf(1));
viewHolder.tv_product_quantity.setText(tempObj.getQuantity());
}
});
return viewHolder;
You can do the same with the other onclicklistener
Update: You do not setText to your product_quantity textview in the BindView function, unless a button is clicked. this means its value will be recycled from other items. you should check with an if statement what is the quantity of the item and present it even without clicking.
Old and not correct answer:
I am not sure if this is the problem, but its an easy check, so try it out. There are 2 positions - the adapter position, and the layout position. I think maybe the position you are using (the one that came from the onBind function) is the latter. You want the adapter position, so try using getAdapterPosition() like this:
Products tempObj = (products).get(getAdapterPosition());
add below line to resolve the problem of 9th and 10th position of item
#Override
public int getItemViewType(int position)
{
return position;
}
file with working code to test my issue, you have to add 2 items, then delete any of those and then add a new one to see how the deleted gets on top of the newly addded
https://www.dropbox.com/s/ojuyz5g5f3kaz0h/Test.zip?dl=0
I have a problem when deleting an item from the recyclerview, when ever I delete an item, IF I add a new item, the deleted item will appear in top of the newly added item, how could I get a fresh view or avod this from happening as is a big issue.
this is how i add items from my main activity
if (!resta || (diff > (3*60*1000)))
{
Ri.add(dataroot);
Integer position = adapter.getItemCount() + 1;
adapter.notifyItemInserted(position);
}
here my Adapter
public class ComandaAdapter extends RecyclerView.Adapter<ComandaAdapter.ComandaAdapterViewHolder>{
private Context mContext;
private ArrayList<Integer> lista_entradas = new ArrayList<>();
private ArrayList<Integer> lista_fondos = new ArrayList<>();
private ArrayList<Integer> lista_postres= new ArrayList<>();
private Boolean primeritem;
private ArrayList<DataRoot> Rir;
private TextView txt_comandas;
private TextView txt_entracola;
private TextView txt_fondocola;
private TextView txt_postrecola;
public ComandaAdapter(Context context, TextView tx_entracola, TextView tx_fondocola, TextView tx_postrecola, TextView tx_comandas, ArrayList<DataRoot> Rir)
{
this.mContext = context;
this.txt_comandas = tx_comandas;
this.txt_entracola = tx_entracola;
this.txt_fondocola = tx_fondocola;
this.txt_postrecola = tx_postrecola;
this.Rir= Rir;
}
#Override
public ComandaAdapter.ComandaAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
return new ComandaAdapter.ComandaAdapterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.row,parent,false));
}
#Override
public void onBindViewHolder(final ComandaAdapter.ComandaAdapterViewHolder holder, final int position)
{
DataRoot Rdata = Rir.get(position);
holder.setdata(Rdata);
}
public void delete(int position)
{
Rir.remove(position);
notifyItemRemoved(position);
}
public class ComandaAdapterViewHolder extends RecyclerView.ViewHolder
{
Button btn_cerrar;
public ComandaAdapterViewHolder(View itemView)
{
super(itemView);
btn_cerrar = (Button) itemView.findViewById(R.id.btn_cerrar);
void setData(final DataRoot Rdata)
{
btn_cerrar.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
btn_cerrar.setEnabled(false);
btn_cerrar.setBackgroundTintList(mContext.getColorStateList(R.color.cboff));
updateRetrofitEstadoorden(Rdata.get_id());
updateRetrofitNrocomanda(Rdata.get_id(), txt_comanda.getText().toString());
delete(getAdapterPosition());
}
});
Rdata.gerOrder();
creaboton():
and here my recyler
private void setAdapter()
{
adapter = new ComandaAdapter(this, txt_entracola, txt_fondocola, txt_postrecola, txt_comandas, Ri);
recyclerView.getRecycledViewPool().setMaxRecycledViews(-1, Ri.size());//va en 0 supuestamente -1 es default
recyclerView.setItemViewCacheSize(Ri.size()); //ver si hay que cambiar con cada item
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
}
Thanks in advance for any help.
Images to show the problem
You are using the Recyclerview in a very non standard way. The issue you are seeing is because the views are being recycled (as they should be in a Recyclerview) but you are not clearing out the items from the previous view.
The problem in in this method:
public void setData(String value) {
container.removeAllViews(); // Remove all previously added views
textview.setText(value);
Random r = new Random();
int i1 = r.nextInt(5 - 1) + 1;
for (int i = 0; i < i1; i++) {
be = new Button(mContext);
be.setText("Boton " + i);
be.setLayoutParams(new LinearLayout.LayoutParams(240, LinearLayout.LayoutParams.WRAP_CONTENT));
container.addView(be);
}
}
By calling container.addView(be), you are manually adding extra views to the views that the Recyclerview creates. When you remove these views, they are cached and reused the next time you press "Add". The problem is that the cached view still contains all of the manually added views so you are then adding more views under the existing ones.
As you can see in the code above, i added container.removeAllViews(); which removes the views that were added previously ensuring that "Container" is empty before you start adding your extra views to it.
Also, unless you have a very specific reason for doing so, I would removes these lines as I believe you are hurting performance by having them:
list.getRecycledViewPool().setMaxRecycledViews(-1, index);
list.setItemViewCacheSize(index);
Desired Result
Maintain scroll position and give right position on specific view of custom row.
I am using RecyclerView in which I am trying to add on Demand Data on beginning of list as well as end of list.
Fetching data through Scrolling bottom is working perfectly.
But Scrolling top having issues as follows:
Set Click listener in Adapter's onBindViewHolder.
Set Click listener in Recyclerview's OnItemTouchListener.
Here is the case:
When I scroll up the list and then It loads 15 data and add it to the top of list.
Our intention is to Maintain scroll position.
Which can be achieved through
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
But the Issue is while I am clicking on any Item without scrolling then View's OnClickListener gives wrong position.
You can check adapter's method:
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
String item = moviesList.get(position);
holder.title.setText(item);
holder.title.setTag(position);
holder.title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.e("onClick", "stringArrayList position: " + moviesList.get(position) + " - view position: " + moviesList.get((int) view.getTag()));
}
});
}
But at the same time in Recyclerview's addOnItemTouchListener gives right position with stringArrayList.get(position) but gives wrong position using getTag.
mRecyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
Log.e("onItemClick", "stringArrayList position: "+stringArrayList.get(position)+ " - view position: "+stringArrayList.get((int)view.getTag()));
}
}));
When I try to add data in the beginning of the list by following I am getting the exact issue.
// retain scroll position and gives wrong position onClick
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
// does not retain scroll position, scroll to top & gives right position onClick
//adapter.notifyDataSetChanged();
This code work for me, so try some thing like that
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final Typeface font;
public TextView mTitle, mDetail, mDate, mCategory;
public CheckBox mCheckBox;
public ImageButton mImageButton;
public ImageView mImage;
public View mview;
private SparseBooleanArray selectedItems = new SparseBooleanArray();
public MyViewHolder(View view) {
super(view);
mTitle = (TextView) view.findViewById(R.id.item_title);
mDetail = (TextView) view.findViewById(R.id.item_detail);
mImage = (ImageView) view.findViewById(R.id.item_thumbnail);
mDate = (TextView) view.findViewById(R.id.date);
mCategory = (TextView) view.findViewById(R.id.category);
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
mImageButton = (ImageButton) view.findViewById(R.id.imageButton);
mview = view;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(MainActivity.activity, Uri.parse(mDataSet.get(getAdapterPosition()).getPostLink()));
}
how i maintain the recyclerview position
public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) {
int scrollPosition = 0;
if (mRecyclerView.getLayoutManager() != null) {
scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
}
switch (layoutManagerType) {
case GRID_LAYOUT_MANAGER:
mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT);
mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER;
break;
case LINEAR_LAYOUT_MANAGER:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
break;
default:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
}
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.scrollToPosition(scrollPosition);
}
I have a RecyclerView that will contain list of item retrieved from the internet. So at first, the list will be empty. After the data retrieved from the internet, it will update the list and call notifyDataSetChanged().
I can adapt the data into the RecyclerView just fine. But, I have an ImageButton for each of item which has different Image if it's clicked. If I initialize the flags array inside onBindViewHolder, each time I scrolled the RecyclerView, the flag array will be reinitialize to false. If I initialize it in the Adapter constructor, it will be 0 index since the list will be empty at first. Where should I put array initializing in adapter if the data will come at some amount of time later?
Below is my code, but the flag array (isTrue) is always reinitialize each time I scrolled my RecyclerView.
public class SomethingAdapter extends RecyclerView.Adapter<SomethingAdapter.ViewHolder> {
private ArrayList<String> someList;
private boolean[] isTrue;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView someText;
public ImageButton someButton;
public ViewHolder(View v) {
super(v);
someText = (TextView) v.findViewById(R.id.text);
someButton = (ImageButton) v.findViewById(R.id.button);
}
}
public SomethingAdapter(ArrayList<String> someList) {
this.someList = someList;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
//TODO: This thing will make isTrue always reinitialize if scrolled
this.isTrue = new boolean[someList.getResults().size()];
viewHolder.someText.setText(someList.get(position));
if (isTrue[position]) {
viewHolder.someButton.setImageResource(R.drawable.button_true);
} else {
viewHolder.someButton.setImageResource(R.drawable.button_false);
}
viewHolder.someButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (isTrue[position]) {
//Connect to the internet and if response is positive {
//isTrue[position] = false;
//viewHolder.someButton.setImageResource(R.drawable.button_false);
//}
} else {
//Connect to the internet and if response is positive {
//isTrue[position] = true;
//viewHolder.someButton.setImageResource(R.drawable.button_true);
//}
}
}
});
}
#Override
public int getItemCount() {
return someList.size();
}
Initialize it when you add items to someList.
Also, don't add click listener in your onBind, create it in onCreateViewHolder. You cannot use position in the click callback, instead you should be using ViewHolder#getAdapterPosition.
See docs for details:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onBindViewHolder(VH, int)