RecyclerView causes issue when recycling - android

I have a list of items that I created using RecyclerView. When the user clicks on one of them I change the background color of that selected item.
The problem is, when I scroll through my items, and they get recycled, some of the items get the selected item's background color (which is wrong).
Here you can see my Adapter's code:
public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.ViewHolder> {
private static final String SELECTED_COLOR = "#ffedcc";
private List<OrderModel> mOrders;
public OrderAdapter() {
this.mOrders = new ArrayList<>();
}
public void setOrders(List<OrderModel> orders) {
mOrders = orders;
}
public void addOrders(List<OrderModel> orders) {
mOrders.addAll(0, orders);
}
public void addOrder(OrderModel order) {
mOrders.add(0, order);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.order_main_item, parent, false);
// Return a new holder instance
ViewHolder viewHolder = new ViewHolder(contactView);
return viewHolder;
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
final OrderModel orderModel = mOrders.get(position);
// Set item views based on the data model
TextView customerName = viewHolder.customerNameText;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss:S");
String time = simpleDateFormat.format(orderModel.getOrderTime());
customerName.setText(time);
TextView orderNumber = viewHolder.orderNumberText;
orderNumber.setText("Order No: " + orderModel.getOrderNumber());
Button button = viewHolder.acceptButton;
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.userActions.acceptButtonClicked(position);
}
});
final LinearLayout orderItem = viewHolder.orderItem;
orderItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.userActions.itemClicked(orderModel);
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
}
});
}
#Override
public int getItemCount() {
return mOrders.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements OrderContract.View {
public TextView customerNameText;
public Button acceptButton;
public TextView orderNumberText;
public OrderContract.UserActions userActions;
public LinearLayout orderItem;
public ViewHolder(View itemView) {
super(itemView);
userActions = new OrderPresenter(this);
customerNameText = (TextView) itemView.findViewById(R.id.customer_name);
acceptButton = (Button) itemView.findViewById(R.id.accept_button);
orderNumberText = (TextView) itemView.findViewById(R.id.order_number);
orderItem = (LinearLayout) itemView.findViewById(R.id.order_item_selection);
}
#Override
public void removeItem() {
}
}

The problem is recyclerView recycling behavior which assign your out of screen ViewHolder items to new items coming to be displayed on screen.
I would not suggest you to bind your logic based on ViewHolder object as in all above answers. It will really cause you problem.
You should build logic based on the state of your data object not ViewHolder Object as you will never know when it gets recycled.
Suppose you save a
state boolean isSelected in ViewHolder to check, but and if it is true, then the same state will be there for new Item when this viewHolder will be recycled.
Better way to do above is holding the any state in DataModel object. In your case just a boolean isSelected.
Sample example like
package chhimwal.mahendra.multipleviewrecyclerproject;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.CardView;
import android.widget.TextView;
import java.util.List;
/**
* Created by mahendra.chhimwal on 12/10/2015.
*/
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
public MyRecyclerViewAdapter(Context context, List<DataModel> rViewDataList) {
this.mContext = context;
this.mRViewDataList = rViewDataList;
}
#Override
public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindDataWithViewHolder(mRViewDataList.get(position));
}
#Override
public int getItemCount() {
return mRViewDataList != null ? mRViewDataList.size() : 0;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private LinearLayout llView;
private DataModel mDataItem=null;
public ViewHolder(View itemView) {
super(itemView);
llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
textView = (TextView) itemView.findViewById(R.id.tvItemName);
cvItemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// One should handle onclick of event here based on the dataItem i.e. mDataItem in this case.
// something like that..
/* Intent intent = new Intent(mContext,ResultActivity.class);
intent.putExtra("MY_DATA",mDataItem); //If you want to pass data.
intent.putExtra("CLICKED_ITEM_POSTION",getAdapterPosition()); // If one want to get selected item position
startActivity(intent);*/
Toast.makeText(mContext,"You clicked item number "+ViewHolder.this.getAdapterPosition(),Toast.LENTH_SHORT).show();
}
});
}
//This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
//Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
public void bindDataWithViewHolder(DataModel dataItem){
this.mDataItem=dataItem;
if(mDataItem.isSelected()){
llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
}else{
llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
}
//other View binding logics like setting text , loading image etc.
textView.setText(mDataItem);
}
}
}
As #Gabriel asked in comment,
what if one want to select a single item at time?
In that case, again one should not save selected item state in ViewHolder object, as the same it gets recycled and cause you problem. For that better way is have a field int selectedItemPosition in Adapter class not ViewHolder .
Following code snippet show it.
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
//variable to hold selected Item position
private int mSelectedItemPosition = -1;
public MyRecyclerViewAdapter(Context context, List<DataModel> rViewDataList) {
this.mContext = context;
this.mRViewDataList = rViewDataList;
}
#Override
public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindDataWithViewHolder(mRViewDataList.get(position),position);
}
#Override
public int getItemCount() {
return mRViewDataList != null ? mRViewDataList.size() : 0;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private LinearLayout llView;
private DataModel mDataItem=null;
public ViewHolder(View itemView) {
super(itemView);
llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
textView = (TextView) itemView.findViewById(R.id.tvItemName);
cvItemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Handling for background selection state changed
int previousSelectState=mSelectedItemPosition;
mSelectedItemPosition = getAdapterPosition();
//notify previous selected item
notifyItemChanged(previousSelectState);
//notify new selected Item
notifyItemChanged(mSelectedItemPosition);
//Your other handling in onclick
}
});
}
//This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
//Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
public void bindDataWithViewHolder(DataModel dataItem, int currentPosition){
this.mDataItem=dataItem;
//Handle selection state in object View.
if(currentPosition == mSelectedItemPosition){
llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
}else{
llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
}
//other View binding logics like setting text , loading image etc.
textView.setText(mDataItem);
}
}
}
If you only have to maintain selected Item state, I strongly discourage use of notifyDataSetChanged() method of Adapter class as RecyclerView provides much more flexibility for these cases.

You should modify your logic assign the value inside the item (object) not the view:
orderItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
orderItem.setSelected(xxxx);
}
});
Then in your onBindViewHolder method you have to assing the color according to this value in the item.
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(xxxx);
} else {
viewHolder.orderItem.setBackgroundColor(xxxx);
}

This is quite a common mistake that has an easy solution.
Quick answer: add this line in your onBindViewHolder method:
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
} else {
viewHolder.orderItem.setBackgroundColor(Color.parseColor(DEFAULT_COLOR));
}
(with DEFAULT_COLOR the color that the viewholder has by default)
Explained answer: when the system recycles a viewholder it just calls onBindViewHolder method so if you have changed anything of that viewholder you'll have to reset it. This will happen if you change background, item's position, etc. Any change that is not related to content per se should be reset in that method

Related

Recyclerview item color change based on the text

I have a recyclerview with some items, I want to change the background color of a few items to different color, how can I achieve this.
Please see the image below. I want to change the background color or text color to different.
I have a recyclerview with some items, I want to change the background color of a few items to different color, how can I achieve this.
Please see the image below. I want to change the background color or text color to different.
MY_ADAPTER CLASS
package com.cmrlabs.tracker;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class AdapterBusStops extends RecyclerView.Adapter<AdapterBusStops.ViewHolder> {
private List<String> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
AdapterBusStops(Context context, List<String> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_bus_stop, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each row
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setIsRecyclable(false);
String bus = mData.get(position);
holder.busStopName.setText(bus);
}
// total number of rows
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView busStopName;
ViewHolder(View itemView) {
super(itemView);
busStopName = itemView.findViewById(R.id.tvBusStop);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mData.get(id);
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
You can write logic like in onBindView for this TextView like
String bus = mData.get(position);
holder.busStopName.setText(bus);
if (holder.busStopName.getText().toString().endsWith("BUS LEFT -") || holder.busStopName.getText().toString().contains("BUS LEFT")) {
// Set text color what should be for Bus left
} else {
// Set text color what should be for upcoming buses
}
You can also store the color in your model
public class BusModel {
private String text;
private int color;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
}
Adapter:
public class AdapterBusStops extends RecyclerView.Adapter<AdapterBusStops.ViewHolder> {
private List<BusModel> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
AdapterBusStops(Context context, List<BusModel> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_bus_stop, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each row
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setIsRecyclable(false);
BusModel bus = mData.get(position);
holder.busStopName.setText(bus.getText());
holder.busStopName.setTextColor(bus.getColor());
}
// total number of rows
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView busStopName;
ViewHolder(View itemView) {
super(itemView);
busStopName = itemView.findViewById(R.id.tvBusStop);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
BusModel getItem(int id) {
return mData.get(id);
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

Multiple views in a Recyclerview gets changed instead of just one view

I'm creating an app with a Recyclerview having a Textview and a Button for each item. Initially, all the Textviews will be invisible. I want it to be visible when the corresponding button gets pressed. I managed to make this but it has a problem.
Here's a simplified version of my code:
MainActivity.java:
package com.example.so;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private WordListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recyclerview);
mAdapter = new WordListAdapter(this);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
}
}
WordListAdapter.java:
package com.example.so;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.WordViewHolder> {
private LayoutInflater mInflator;
public WordListAdapter(Context context) {
mInflator = LayoutInflater.from(context);
}
class WordViewHolder extends RecyclerView.ViewHolder {
private final TextView itemTextview;
private final Button itembutton;
final WordListAdapter mAdapter;
public WordViewHolder(View itemView, WordListAdapter adapter) {
super(itemView);
itemTextview = itemView.findViewById(R.id.item_text);
itembutton = itemView.findViewById(R.id.item_button);
this.mAdapter = adapter;
itembutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
itemTextview.setVisibility(View.VISIBLE);
}
});
}
}
#NonNull
#Override
public WordListAdapter.WordViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View mItemView = mInflator.inflate(R.layout.wordlist_item, parent, false);
return new WordViewHolder(mItemView, this);
}
#Override
public void onBindViewHolder(#NonNull WordListAdapter.WordViewHolder holder, int position) {
}
#Override
public int getItemCount() {
return 20;
}
}
wordlist_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:layout_margin="6dp">
<TextView
android:id="#+id/item_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center"
android:layout_weight="1"
android:text="Button clicked!"
android:visibility="invisible"/>
<Button
android:id="#+id/item_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
The problem with this is that when I press the first Button, the 15th Textview, in addition to the first Textview becomes visible. Similarly, when I press the 16th button, the 2nd Textview, in addition to the
16th Textview becomes visible. The same goes for other buttons.
I'm not sure why two Textviews become visible when pressing a single button. What's wrong with the code?
When you scroll and new RecyclerView items are being show to you, they are redrawn (recycled) and data is being newly inserted into them, using onBindViewHolder() function.
I would advise you to have some sort a Collection or an Array of visible items and use some logic in the onBindViewHolder() to hide or display them.
The Constructor
private boolean[] visibleItems;
public WordListAdapter(Context context) {
mInflator = LayoutInflater.from(context);
visibleItems = new boolean[20];
}
and the ViewHolder
itembutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int pos = getAdapterPosition();
visibleItems[pos] = true;
notifyItemChanged(pos);
}
});
and the onBindViewHolder()
#Override
public void onBindViewHolder(#NonNull WordListAdapter.WordViewHolder holder, int position) {
if(visibleItems[position])
itemTextview.setVisibility(View.VISIBLE);
else itemTextview.setVisibility(View.INVISIBLE);
}
The same problem you have happens if there are EditTexts in the RecyclerView items and they are not set them using setText() inside the same function, but are only editable.
When you are scrolling in a recycler view, the items are recycled. So create a variable to store the position of the item of which you clicked the button.
Implement in the onBindViewHolder
#Override
public void onBindViewHolder(#NonNull WordListAdapter.WordViewHolder holder, final int position){
holder.itemTextview.setVisibility(View.INVISIBLE);
if(currentPosition == position){
holder.itemTextview.setVisibility(View.VISIBLE);
}
holder.itembutton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View p1){
// TODO: Implement this method
currentPosition = position;
notifyDataSetChanged();
}
});
}
The class WordListAdapter will look like this.
public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.WordViewHolder>
{
private Context context;
private static int currentPosition=0;
public WordListAdapter(Context context){
this.context = context;
}
class WordViewHolder extends RecyclerView.ViewHolder
{
private final TextView itemTextview;
private final Button itembutton;
public WordViewHolder(View itemView){
super(itemView);
itemTextview = (TextView)itemView.findViewById(R.id.item_text);
itembutton = (Button) itemView.findViewById(R.id.item_button);
}
}
#NonNull
#Override
public WordListAdapter.WordViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType){
View mItemView = LayoutInflater.from(context).inflate(R.layout.wordlist_item, parent, false);
return new WordViewHolder(mItemView);
}
#Override
public void onBindViewHolder(#NonNull WordListAdapter.WordViewHolder holder, final int position){
holder.itemTextview.setVisibility(View.INVISIBLE);
if(currentPosition == position){
holder.itemTextview.setVisibility(View.VISIBLE);
}
holder.itembutton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View p1){
// TODO: Implement this method
currentPosition = position;
notifyDataSetChanged();
}
});
}
#Override
public int getItemCount(){
return 20;
}
}
Try to implement getItemId() method inside adapter.
#Override
public long getItemId(int position) {
return position;
}
You should assign a visible/hidden status for each item. And change the status when the button clicked.
And in onBindViewHolder() you should use if/else to make the TextView visible/gone. Something like below. data is the array of your items. VisibilityStatus is the status i mentioned above. You can either put it in the data model, or keep it in another Array or SparseBooleanArray.
if (data.getVisibilityStatus(position)) {
itemTextview.setVisibility(View.VISIBLE);
} else {
itemTextview.setVisibility(View.GONE);
}
You need to add this method in your adapter
This method Return the view type of the item at position for the purposes of view recycling.
#Override
public int getItemViewType(int position) {
return position;
}
In adapter
public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.WordViewHolder> {
private LayoutInflater mInflator;
public WordListAdapter(Context context) {
mInflator = LayoutInflater.from(context);
}
class WordViewHolder extends RecyclerView.ViewHolder {
private TextView itemTextview;
private Button itembutton;
// final WordListAdapter mAdapter;
public WordViewHolder(View itemView) {
super(itemView);
itemTextview = itemView.findViewById(R.id.item_text);
itembutton = itemView.findViewById(R.id.item_button);
}
}
#NonNull
#Override
public WordListAdapter.WordViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View mItemView = mInflator.inflate(R.layout.wordlist_item, parent, false);
return new WordViewHolder(mItemView);
}
#Override
public void onBindViewHolder(#NonNull final WordListAdapter.WordViewHolder holder, int position) {
holder.itembutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.itemTextview.setVisibility(View.VISIBLE);
}
});
}
#Override
public int getItemCount() {
return 20;
}
#Override
public int getItemViewType(int position) {
return position;
}
}
Extension to Viktor Stojanov's answer:
This behaviour is due to recyclerView's property of "recycling" the views.
When the list is scrolled, the views that become invisible are made eligible for reuse and those views are used again for the items that are about be be shown on the screen.
i.e. the same upper views are used, just the new data is binded using onBindViewHolder().
I would suggest you to maintain an Arraylist of simple POJOs and refer their state to update the views in your recyclerView.
e.g.
public class Word{
private boolean isVisible;
private String content;
//getters and setters for both properties
}
In bindViewHolder check the visibility of item and accordingly show the textView.
#Override
public void onBindViewHolder(#NonNull WordListAdapter.WordViewHolder holder, int position) {
Word currentWord = wordArraylist.get(position);
if(currentWord.getVisibility()){
itemTextView.setText(currentWord.getContent());
itemTextview.setVisibility(View.VISIBLE);
} else{
if(itemTextView.getVisibility != View.INVISIBLE){
itemTextview.setVisibility(View.INVISIBLE);
}
}
}
And in your ViewHolder class you could set the onClickListener on the button to change the visibility, something like this:
itembutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Word currentWord = wordArraylist.get(getAdapterPosition());
if(!currentWord.getVisibility()){
itemTextView.setText(currentWord.getContent());
itemTextview.setVisibility(View.VISIBLE);
}
}
});

Button doesn't react at clicking in RecycleView

I have created a simple RecycleView component. Then create a new activity for that component. In one row I have a TextView and Button. What I want to do is to change something in object which was put into the excatly one row.
First I initialized the Button:
public class RecycleViewAllWashesActivity extends Activity {
private Button isFavorite;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.row_recycleview);
isFavorite = (Button) findViewById(R.id.addToFav);
isFavorite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.w("myApp", String.valueOf(v.getId()));
int id = v.getId();
}
});
}
}
The adapter of RecycleView looks like:
#Override
public MyAdapter.WashLocationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View view = inflater.inflate(R.layout.row_recycleview, parent, false);
// Return a new holder instance
WashLocationViewHolder viewHolder = new WashLocationViewHolder(view);
return viewHolder;
}
// Involves populating data into the item through holder
#Override
public void onBindViewHolder(MyAdapter.WashLocationViewHolder viewHolder, int position) {
// Get the data model based on position
WashLocation w = washLocations.get(position);
String info = w.getWashName();
// Set item views based on your views and data model
TextView textView = viewHolder.info;
textView.setText(info);
Integer fav = w.getFav();
Boolean favorite = fav == 1 ? true : false;
Button addToFav = viewHolder.favorite;
addToFav.setText(favorite == true ? "Usuń z ulubionych" : "Dodaj do ulubionych");
}
Questions:
How to get the object which was put into one row?
When I clicked the Button (code above) it doesn't react, even breakpoint which was set there doesn't react? -> the thing in console which I see while clicking the button is:
04-17 13:11:00.137 7671-7671/com.example.micha.locationtest D/ViewRootImpl: ViewPostImeInputStage ACTION_DOWN
==============================
Update:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ItemHolder> {
private List<WashLocation> washLocations;
private OnItemClickListener onItemClickListener;
private LayoutInflater layoutInflater;
public MyAdapter(List<WashLocation> washLocations, Context context) {
layoutInflater = LayoutInflater.from(context);
this.washLocations = washLocations;
}
#Override
public MyAdapter.ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = layoutInflater.inflate(R.layout.row_recycleview, parent, false);
return new ItemHolder(itemView, this);
}
#Override
public void onBindViewHolder(MyAdapter.ItemHolder holder, int position) {
// Get the data model based on position
WashLocation w = washLocations.get(position);
String info = w.getWashName();
// Set item views based on your views and data model
TextView textView = holder.info;
textView.setText(info);
Integer fav = w.getFav();
Boolean favorite = fav == 1 ? true : false;
Button addToFav = holder.favorite;
addToFav.setText(favorite == true ? "Usuń z ulubionych" : "Dodaj do ulubionych");
}
#Override
public int getItemCount() {
return washLocations.size();
}
public void setOnItemClickListener(OnItemClickListener listener) {
onItemClickListener = listener;
}
public OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
public interface OnItemClickListener {
public void onItemClick(ItemHolder item, int position);
}
/* public void add(int location, String iName){
itemsName.add(location, iName);
notifyItemInserted(location);
}
public void remove(int location){
if(location >= itemsName.size())
return;
itemsName.remove(location);
notifyItemRemoved(location);
}*/
public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private MyAdapter parent;
TextView textItemName;
public TextView info;
public Button favorite;
public ItemHolder(View itemView, MyAdapter parent) {
super(itemView);
itemView.setOnClickListener(this);
this.parent = parent;
info = (TextView) itemView.findViewById(R.id.textView);
favorite = (Button) itemView.findViewById(R.id.addToFav);
}
public void setItemName(CharSequence name) {
textItemName.setText(name);
}
public CharSequence getItemName() {
return textItemName.getText();
}
#Override
public void onClick(View v) {
final OnItemClickListener listener = parent.getOnItemClickListener();
if (listener != null) {
listener.onItemClick(this, getPosition());
}
}
}
}
The activity class:
final DataBaseHelper dataBaseHelper = new DataBaseHelper(getApplicationContext());
washLocations = dataBaseHelper.getWashLocation();
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
mAdapter = new MyAdapter(washLocations, this);
mAdapter.setOnItemClickListener(this);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
#Override
public void onItemClick(MyAdapter.ItemHolder item, int position) {
Log.d("myApp", "fdsgfds");
}
In Recycle review you must set an onClickListener for the views explicitly.
It does not have any default methods to handle click events like the listview.
You need to set an Onclick listener for your button in your WashLocationViewHolder
You will receive a click event there. From the view holder, you can send the event back to your fragment or activity using an interface.
if you need further help regarding this, mention it in the comments. I will provide you some samples.
How to get the object which was put into one row?
To do this you can set the object as a Tag using the setTag(Object) method in the layout or the button for which you will be providing a click listener. Then you can get the object using the getTag() method from the view.
Hope this helps.

Get the internal details of the items in recycler view if the items are shuffeled

I have a recycler view, which consists of list. The items in the list have their own details like this (each item will have image, id, name, description), and the items are shuffeled, not in order.
Example : recyclerview item position = 0, list item id= 7.
recyclerview item position = 1, list item id= 5.
recyclerview item position = 2, list item id= 12.
So i need to get the list item id on recycler on click. If i click on recyclerview position = 0, If i have to get the item id as 7. So that i can work on that furthur based on that id.
My example code is
recommendedrecyclerView=(RecyclerView)view.findViewById(R.id.recommended_recycler_view);
recommendedrecyclerView.setNestedScrollingEnabled(false);
recommendedrecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(),OrientationHelper.VERTICAL,true));
recommendedrecyclerView.setItemAnimator(new DefaultItemAnimator());
I am getting data from server and setting it to adapter
setrecommendedAppsContent(response.body().getData());
public void setrecommendedAppsContent(List<FeaturedAppsData> data){
if (data!=null&&data.size()>0) {
recommendedAdapter = new RecommendedAdapter(mCurrentContext, data);
recommendedrecyclerView.setAdapter(recommendedAdapter);
recommendedAdapter.setClickListener(this);
}
}
This is my adapter class
public class RecommendedAdapter extends RecyclerView.Adapter<RecommendedAdapter.ItemViewHolder> {
private LayoutInflater inflater = null;
private Context context;
private List<FeaturedAppsData> itemsData;
private ClickListener clicklistener = null;
public TextView recommendedAppTitle,recommendedAppCategory,recommendedAppDescription;
public ImageView recommendedAppIcon;
Button recommendedBtn;
String appId;
public void setClickListener(ClickListener clickListener) {
this.clicklistener = clickListener;
}
public RecommendedAdapter(Context context, List<FeaturedAppsData> itemsData) {
this.context = context;
this.itemsData = itemsData;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
holder.recommendedAppTitle.setText(itemsData.get(position).getName());
holder.recommendedAppCategory.setText(itemsData.get(position).getApp_description());
holder.recommendedAppDescription.setText(itemsData.get(position).getOffer_description());
holder.recommendedAppBtn.setText(itemsData.get(position).getButton_text());
String imageUrl = String.valueOf(itemsData.get(position).getImage().getUrl());
Glide.with(context).load(ApiConstant.ApiBaseUrl + imageUrl).into(recommendedAppIcon);
appId=itemsData.get(position).getId();
}
#Override
public int getItemCount() {
int size = 0;
if (itemsData != null && itemsData.size() > 0) {
size = itemsData.size();
}
return size;
}
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView recommendedAppTitle,recommendedAppCategory,recommendedAppDescription,offerDescription,offerDetailDescription,rewardDetail;
public ImageView recommendedAppIcon;
public Button recommendedAppBtn;
public ArrayList<FeaturedAppsData> dataItems;
private Context context;
public ItemViewHolder(Context context, View itemView, int viewType) {
super(itemView);
this.context = context;
itemView.setOnClickListener(this);
recommendedAppTitle = (TextView) itemView.findViewById(R.id.recommended_app_title);
recommendedAppCategory = (TextView) itemView.findViewById(R.id.recommended_app_category);
recommendedAppDescription = (TextView) itemView.findViewById(R.id.recommended_app_description);
recommendedAppIcon = (ImageView) itemView.findViewById(R.id.recommended_app_icon);
recommendedAppBtn=(Button)itemView.findViewById(R.id.recommended_app_card_btn);
}
#Override
public void onClick(View v) {
if (clicklistener != null) {
clicklistener.itemClicked(v, getAdapterPosition());
}
}
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
inflater = LayoutInflater.from(context);
view = inflater.inflate(R.layout.recommended_item_layout, parent, false);
recommendedAppIcon=(ImageView)view.findViewById(R.id.recommended_app_icon);
return new ItemViewHolder(context, view, viewType);
}
}
I am not sure of the Onclick method. So please suggest me as required along with Onclick event.
The setOnClickListener inside the ViewHolder is a good way.
Considering your current solution, the easiest way is to change the ClickListener interface, put the data inside the Holder during onBindViewHolder and then pass it to the listener during onClick. Like this:
Change the view holder fields to:
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView recommendedAppTitle,recommendedAppCategory,recommendedAppDescription,offerDescription,offerDetailDescription,rewardDetail;
public ImageView recommendedAppIcon;
public Button recommendedAppBtn;
public FeaturedAppsData data; // <<< ADD THIS
// remove that u don't need public ArrayList<FeaturedAppsData> dataItems;
// remove that u don't need private Context context;
... the rest of your holder
then inside onBindViewHolder
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
holder.data = itemsData.get(position);
... the rest on bind
then inside onClick, inside the holder:
#Override
public void onClick(View v) {
if (clicklistener != null) {
clicklistener.itemClicked(v, data.getId());
}
}

Handling clicks for recyclerview's internal item

I have a recyclerview to show facebook like newsfeed with a comment button. I cant figure out how to show a dialog when user clicks on comment button. All the solutions I found here were to handle the entire item's click but not its internal view. I tried this soultion also. How to handle multiple layout clicks in recyclerView in Android
Here is my adapter class.
public class NewsfeedAdapter extends RecyclerView.Adapter<NewsfeedAdapter.NewsfeedViewHolder> {
private final LayoutInflater inflater;
private Context context;
ArrayList<NewsfeedItem> data= new ArrayList<NewsfeedItem>();
public NewsfeedAdapter(Context context,ArrayList<NewsfeedItem> data){
inflater= LayoutInflater.from(context);
this.data=data;
this.context = context;
}
#Override
public NewsfeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.custom_newsfeed_row, parent, false);
NewsfeedViewHolder holder = new NewsfeedViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(NewsfeedViewHolder newsfeedViewHolder, int i) {
NewsfeedItem current = data.get(i);
newsfeedViewHolder.username.setText(current.username);
newsfeedViewHolder.icon.setImageResource(current.iconid);
newsfeedViewHolder.timestamp.setText((CharSequence) current.timestamp);
newsfeedViewHolder.news.setText(current.news);
}
#Override
public int getItemCount()
{
return data.size();
}
class NewsfeedViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView news, username, timestamp, comment_btn, like_btn;
ImageView icon;
private NewsfeedClickInterface clickListener;
public NewsfeedViewHolder(View itemView) {
super(itemView);
news = (TextView) itemView.findViewById(R.id.news);
timestamp = (TextView) itemView.findViewById(R.id.timestamp);
username = (TextView) itemView.findViewById(R.id.username);
icon = (ImageView) itemView.findViewById(R.id.profile_img);
comment_btn = (TextView) itemView.findViewById(R.id.comment_btn);
comment_btn.setTag("comment");
comment_btn.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (clickListener != null) {
clickListener.onItemClicked(getPosition(), v.getTag().toString());
}
}
}
public interface NewsfeedClickInterface {
public void onItemClicked(int position, String tag);
}
}
First define an interface in your viewholder. And when button is clicked, call this interface.
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public interface ViewHolderClickListener {
public void onItemClick(View caller, int position);
}
public ViewHolderClickListener mListener;
public ViewHolder(View itemView, ViewHolderClickListener listener) {
super(itemView);
mListener = listener;
//other initializations...
}
#Override
public void onClick(View v) {
mListener.onItemClick(v, getPosition());
}
}
Then create another interface in a separate file, let's say you create this interface in a class called Commons:
public class Commons {
public interface OnRecyclerItemClickedListener{
public void onRecyclerItemClicked(String parameter);
}
}
Then let your fragment/activity implement that interface. And pass this interface to your adapter's constructor as a parameter.
public class MyFragment extends Fragment implements Commons.OnRecyclerItemClickedListener{
#Override
public void onRecyclerItemClicked(String productId) {
//do whatever you want on click
}
}
Pass this interface to your adapter's constructor:
//here "this" parameter is your listener since your fragment implements
//the interface you defined
MyRecyclerViewAdapter adapter = new MyRecyclerViewAdapter(items, R.layout.yourlayout, getContext(), this);
and in your adapter, create an instance of the interface you defined in Commons class and set it in your constructor. Then create your viewholder in onCreateViewHolder method. Finally, call the interface you defined in your Commons class in your Viewholder's click interface:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder>{
private OnRecyclerItemClickedListener onRecyclerItemClickedListener;
public MyRecyclerViewAdapter(List<Item> items, int rowLayout, Context context, OnRecyclerItemClickedListener onRecyclerItemClickedListener){
this.items = items;
this.rowLayout = rowLayout;
this.mContext = context;
this.onRecyclerItemClickedListener = onRecyclerItemClickedListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(rowLayout, parent, false);
ViewHolder holder = new ViewHolder(v, new ViewHolder.ViewHolderClickListener() {
#Override
public void onItemClick(View caller, int position) {
onRecyclerItemClickedListener.onRecyclerItemClicked(put_parameters_if_you_want);
}});
return holder;
}
}
It is a little complicated, but here is the explanation of what you do: When button is clicked, your button click listener calls another listener(which was defined in your viewholder), and that listener calls another listener which was implemented in your fragment.
That is all you need to do.
Inside
#Override
public void onClick(View v) {
if(view.getId() == comment_btn.getId()){
Log.d("LOGTAG","Bingo !!! Your view Clicked.");
}
if (clickListener != null) {
clickListener.onItemClicked(getPosition(), v.getTag().toString());
}
}
you can check the Id's of the views and fire different click listeners according to which view was clicked.
like
comment_btn.setOnClickListener(this);
anotherButton.setOnClickListener(this);
switch (v.getId())
case R.id.comment_btn:
code goes here
case R.id.another_button:
hope this helps.

Categories

Resources