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

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);
}
}
});

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);
}
}

Recycler view reusability of view_holder. How to hold visibility of item of particular position?

This was an answer posted in a question which is about repeating button views in certain positions . Code works fine. My doubt is how to hold a visibility of first item even after scrolling in recyclerview?
class MyViewHolder extends RecyclerView.ViewHolder {
TextView message;
Button button;
public MyViewHolder(View itemView) {
super(itemView);
message = (TextView) itemView.findViewById(R.id.message);
button = (Button) itemView.findViewById(R.id.button);
}
}
and place it inside the method onBindViewHolder of your adapter:
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
holder.button.setVisibility(View.GONE);
holder.message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.button.setVisibility(View.VISIBLE);
}
});
}
I assume that in your Adapter, you hold an array of objects that represents the items you want to be displayed.
Add a property to this object named for example ButtonVisible and set the property when you press the button.
Complete sample adapter follows. This displays a list of items with a button that, when pressed, is made non-visible. The visibility is remembered no matter how many items in the list or how much you scroll.
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.VH> {
public static class MyData {
public boolean ButtonVisible = true;
public String Text;
public MyData(String text) {
Text = text;
}
}
public List<MyData> items = new ArrayList<>();
public TestAdapter() {
this.items.add(new MyData("Item 1"));
this.items.add(new MyData("Item 2"));
this.items.add(new MyData("Item 3"));
}
#Override
public TestAdapter.VH onCreateViewHolder(ViewGroup parent, int viewType) {
return new VH((
LayoutInflater.from(parent.getContext())
.inflate(R.layout.test_layout, parent, false))
);
}
#Override
public void onBindViewHolder(TestAdapter.VH holder, final int position) {
final MyData itm = items.get(position);
holder.button.setVisibility(itm.ButtonVisible ? View.VISIBLE : View.GONE);
holder.text.setText(itm.Text);
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
itm.ButtonVisible = false;
notifyItemChanged(position);
}
});
}
#Override
public int getItemCount() {
return items.size();
}
public class VH extends RecyclerView.ViewHolder {
Button button;
TextView text;
public VH(View itemView) {
super(itemView);
button = itemView.findViewById(R.id.toggle);
text = itemView.findViewById(R.id.text1);
}
}
}
test_layout.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="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Set an array of boolean variables associated with each item.
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
if(visibilityList.get(position)){
holder.button.setVisibility(View.VISIBLE);
}else{
holder.button.setVisibility(View.GONE);
}
holder.message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(visibilityList.get(position)){
visibilityList.set(position, false);
holder.button.setVisibility(View.GONE);
}else{
visibilityList.set(position, true);
holder.button.setVisibility(View.VISIBLE);
}
}
});
}
Note: visibilityList is the List variable where each value is set to default (either true or false as per your requirement)
Use HashMap to keep those positions which you need to show. Write code in onBindViewHolder method
if(map.contains(holder.getAdapterPosition()){
holder.btn.setVisibility(View.VISIBLE);
} else {
holder.btn.setVisibility(View.GONE);
}
Note: - do write else case too, otherwise recyclerView will misbehave due to reusability.

Android: RecyclerView adapter longClickListener not working

I am trying to use longClickListener in my Fragment but for some reason it is just not working. If I do a long click, event is received by onItemClickListener
This is my code
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private ArrayList<Info> Infos;
private ClickListener clickListener;
private MyLongClickListener myLongClickListener;
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
// each data item is just a string in this case
public ImageView image;
public/* Roboto */ TextView name;
public/* Roboto */ TextView companyName;
public/* Roboto */ TextView city;
public/* Roboto */ TextView time;
public/* Material */ TextView icon;
public ViewHolder(View convertView) {
super(convertView);
companyName = (TextView) convertView
.findViewById(R.id.t1);
city = (TextView) convertView
.findViewById(R.id.t2);
convertView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
clickListener.onItemClick(getPosition(), v);
}
#Override
public boolean onLongClick(View v) {
myLongClickListener.onItemLongClick(getPosition(), v);
return true;
}
}
public void add(int position, Info item) {
Infos.add(position, item);
notifyItemInserted(position);
}
// Provide a suitable constructor (depends on the kind of dataset)
public RecyclerViewAdapter(ArrayList<Info> dummyModelList) {
Infos = dummyModelList;
}
// Create new views (invoked by the layout manager)
#Override
public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.genre_list_item, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Info currInfo = Infos.get(position);
holder.city.setText(currInfo.getTitle());
holder.companyName.setText(currInfo.getArtist());
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return Infos.size();
}
public void setOnItemClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
public void setOnItemLongClickListener(MyLongClickListener clickListener) {
this.myLongClickListener = clickListener;
}
public interface ClickListener {
void onItemClick(int position, View v);
}
public interface MyLongClickListener {
boolean onItemLongClick(int position, View v);
}
}
This is how I use it inside my Fragment. This onLongClick and onItemClickListener both are placed in onResume function of the Fragment.
recyclerViewAdapter.setOnItemLongClickListener(new
RecyclerViewAdapter.MyLongClickListener() {
#Override
public boolean onItemLongClick(int position, View v) {
// Task
return true;
}
});
I can't see any mistake in my code but don't know why it is not working. The touch event is always received by onItemClickListener. Any help would be appreciated, Thanks !!!
Set both click and onclicklisteners in your constructor.
convertView.setOnClickListener(this);
convertView.setOnLongClickListener(this);
You can not listen long clicks without setOnLongClickListener.

RecyclerView causes issue when recycling

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

How do I get the position selected in a RecyclerView?

I am experimenting with the support library's recyclerview and cards. I have a recyclerview of cards. Each card has an 'x' icon at the top right corner to remove it:
The card xml, list_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/taskDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:textSize="40sp"
android:text="hi"/>
<ImageView
android:id="#+id/xImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:src="#drawable/ic_remove"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
I attempted to tag the row with the position I would use in notifyItemRemoved(position) in TaskAdapter.java:
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
private List<Task> taskList;
private TaskAdapter thisAdapter = this;
// cache of views to reduce number of findViewById calls
public static class TaskViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
protected TextView taskTV;
protected ImageView closeBtn;
public TaskViewHolder(View v) {
super(v);
taskTV = (TextView)v.findViewById(R.id.taskDesc);
}
#Override
public void onClick(View v) {
int position = v.getTag();
adapter.notifyItemRemoved(position);
}
}
public TaskAdapter(List<Task> tasks) {
if(tasks == null)
throw new IllegalArgumentException("tasks cannot be null");
taskList = tasks;
}
// onBindViewHolder binds a model to a viewholder
#Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
final int position = pos;
Task currTask = taskList.get(pos);
taskViewHolder.taskTV.setText(currTask.getDescription());
taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
thisAdapter.notifyItemRemoved(position);
}
});
}
#Override
public int getItemCount() {
return taskList.size();
}
// inflates row to create a viewHolder
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.list_item, parent, false);
return new TaskViewHolder(itemView);
}
}
This won't work because you can't set a tag nor can I access the adapter from onClick.
Set your onClickListeners on onBindViewHolder() and you can access the position from there. If you set them in your ViewHolder you won't know what position was clicked unless you also pass the position into the ViewHolder
EDIT
As pskink pointed out ViewHolder has a getPosition() so the way you were originally doing it was correct.
When the view is clicked you can use getPosition() in your ViewHolder and it returns the position
Update
getPosition() is now deprecated and replaced with getAdapterPosition()
Update 2020
getAdapterPosition() is now deprecated and replaced with getAbsoluteAdapterPosition() or getBindingAdapterPosition()
Kotlin code:
override fun onBindViewHolder(holder: MyHolder, position: Int) {
// - get element from your dataset at this position
val item = myDataset.get(holder.absoluteAdapterPosition)
}
A different method - using setTag() and getTag() methods of the View class.
use setTag() in the onBindViewHolder method of your adapter
#Override
public void onBindViewHolder(myViewHolder viewHolder, int position) {
viewHolder.mCardView.setTag(position);
}
where mCardView is defined in the myViewHolder class
private class myViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public View mCardView;
public myViewHolder(View view) {
super(view);
mCardView = (CardView) view.findViewById(R.id.card_view);
mCardView.setOnClickListener(this);
}
}
use getTag() in your OnClickListener implementation
#Override
public void onClick(View view) {
int position = (int) view.getTag();
//display toast with position of cardview in recyclerview list upon click
Toast.makeText(view.getContext(),Integer.toString(position),Toast.LENGTH_SHORT).show();
}
see https://stackoverflow.com/a/33027953/4658957 for more details
To complement #tyczj answer:
Generic Adapter Pseido code:
public abstract class GenericRecycleAdapter<T, K extends RecyclerView.ViewHolder> extends RecyclerView.Adapter{
private List<T> mList;
//default implementation code
public abstract int getLayout();
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(getLayout(), parent, false);
return getCustomHolder(v);
}
public Holders.TextImageHolder getCustomHolder(View v) {
return new Holders.TextImageHolder(v){
#Override
public void onClick(View v) {
onItem(mList.get(this.getAdapterPosition()));
}
};
}
abstract void onItem(T t);
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
onSet(mList.get(position), (K) holder);
}
public abstract void onSet(T item, K holder);
}
ViewHolder:
public class Holders {
public static class TextImageHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView text;
public TextImageHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text);
text.setOnClickListener(this);
}
#Override
public void onClick(View v) {
}
}
}
Adapter usage:
public class CategoriesAdapter extends GenericRecycleAdapter<Category, Holders.TextImageHolder> {
public CategoriesAdapter(List<Category> list, Context context) {
super(list, context);
}
#Override
void onItem(Category category) {
}
#Override
public int getLayout() {
return R.layout.categories_row;
}
#Override
public void onSet(Category item, Holders.TextImageHolder holder) {
}
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
FrameLayout root;
public ViewHolder(View itemView) {
super(itemView);
root = (FrameLayout) itemView.findViewById(R.id.root);
root.setOnClickListener(this);
}
#Override
public void onClick(View v) {
LogUtils.errorLog("POS_CLICKED: ",""+getAdapterPosition());
}
}
Get focused child, and use it to get position in adapter.
mRecyclerView.getChildAdapterPosition(mRecyclerView.getFocusedChild())
Personally, the simplest way that I have found and works great for me is as follows:
Create an interface inside your "RecycleAdapter" Class (Subclass)
public interface ClickCallback {
void onItemClick(int position);
}
Add a variable of the interface as a parameter in the Constructor.
private String[] items;
private ClickCallback callback;
public RecyclerAdapter(String[] items, ClickCallback clickCallback) {
this.items = items;
this.callback = clickCallback;
}
Set a Click listener in the ViewHolder (another subclass) and pass the 'position' to through the interface
AwesomeViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callback.onItemClick(getAdapterPosition());
}
});
mTextView = (TextView) itemView.findViewById(R.id.mTextView);
}
Now, when initializing the recycler adapter in an activity/fragment, just Create a new 'ClickCallback' (interface)
String[] values = {"Hello","World"};
RecyclerAdapter recyclerAdapter = new RecyclerAdapter(values, new RecyclerAdapter.ClickCallback() {
#Override
public void onItemClick(int position) {
// Do anything with the item position
}
});
That's it for me. :)
I solved this way
class MyOnClickListener implements View.OnClickListener {
#Override
public void onClick(View v) {
int itemPosition = mRecyclerView.getChildAdapterPosition(v);
myResult = results.get(itemPosition);
}
}
And in the adapter
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_wifi, parent, false);
v.setOnClickListener(new MyOnClickListener());
ViewHolder vh = new ViewHolder(v);
return vh;
}
1. Create class Name RecyclerTouchListener.java
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener
{
private GestureDetector gestureDetector;
private ClickListener clickListener;
public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
this.clickListener = clickListener;
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
#Override
public void onLongPress(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null && clickListener != null) {
clickListener.onLongClick(child, recyclerView.getChildAdapterPosition(child));
}
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
clickListener.onClick(child, rv.getChildAdapterPosition(child));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
public interface ClickListener {
void onClick(View view, int position);
void onLongClick(View view, int position);
}
}
2. Call RecyclerTouchListener
recycleView.addOnItemTouchListener(new RecyclerTouchListener(this, recycleView,
new RecyclerTouchListener.ClickListener() {
#Override
public void onClick(View view, int position) {
Toast.makeText(MainActivity.this,Integer.toString(position),Toast.LENGTH_SHORT).show();
}
#Override
public void onLongClick(View view, int position) {
}
}));
onBindViewHolder() is called for each and every item and setting the click listener inside onBindVieHolder() is an unnecessary option to repeat when you can call it once in your ViewHolder constructor.
public class MyViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
public final TextView textView;
public MyViewHolder(View view){
textView = (TextView) view.findViewById(R.id.text_view);
view.setOnClickListener(this);
// getAdapterPosition() retrieves the position here.
}
#Override
public void onClick(View v){
// Clicked on item
Toast.makeText(mContext, "Clicked on position: " + getAdapterPosition(), Toast.LENGTH_SHORT).show();
}
}
I think the most correct way to get item position is
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(View v) {
View view = v;
View parent = (View) v.getParent();
while (!(parent instanceof RecyclerView)){
view=parent;
parent = (View) parent.getParent();
}
int position = recyclerView.getChildAdapterPosition(view);
}
Because view, you click not always the root view of your row layout. If view is not a root one (e.g buttons), you will get Class cast exception. Thus at first we need to find the view, which is the a dirrect child of you reciclerview. Then, find position using recyclerView.getChildAdapterPosition(view);
No need to have your ViewHolder implementing View.OnClickListener. You can get directly the clicked position by setting a click listener in the method onCreateViewHolder of RecyclerView.Adapter here is a sample of code :
public class ItemListAdapterRecycler extends RecyclerView.Adapter<ItemViewHolder>
{
private final List<Item> items;
public ItemListAdapterRecycler(List<Item> items)
{
this.items = items;
}
#Override
public ItemViewHolder onCreateViewHolder(final ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, parent, false);
view.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
int currentPosition = getClickedPosition(view);
Log.d("DEBUG", "" + currentPosition);
}
});
return new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(ItemViewHolder itemViewHolder, int position)
{
...
}
#Override
public int getItemCount()
{
return items.size();
}
private int getClickedPosition(View clickedView)
{
RecyclerView recyclerView = (RecyclerView) clickedView.getParent();
ItemViewHolder currentViewHolder = (ItemViewHolder) recyclerView.getChildViewHolder(clickedView);
return currentViewHolder.getAdapterPosition();
}
}
#Override
public void onClick(View v) {
int pos = getAdapterPosition();
}
Simple as that, on ViewHolder
When using data binding and you need to know a RecyclerView click position from inside of an item's click listener:
Kotlin
val recyclerView = view.parent as RecyclerView
val position = recyclerView.getChildAdapterPosition(view)

Categories

Resources