onClick inside ViewHolder doesn't have access to clicked item in adapter - android

1) Problem
I would like to set 2 listeners on 2 buttons inside a CardView. I need to know which item has been selected. I use CardView with RecyclerView and FirebaseRecyclerAdapter (RecyclerView.Adapter)
2) Old Situation
I have created the listener in populateViewHolder() of my FirebaseRecyclerAdapter like this:
#Override
protected void populateViewHolder(final ItemViewHolder viewHolder, final Item model, final int position) {
viewHolder.firstButton.setOnClickListener(new View.OnClickListener() {
Item selectedItem = getItem(position);
//do something for the item selected
}
}
this works fine!
3) What I would like to do
Set listeners in the ViewHolder class (defined in his own file) and do something on the selected ITEM
I would like to set the listeners in the ViewHolder class because the same ViewHolder is used for different adapters and I think it's a better approach to have the behaviour of the listeners defined in only one place.
I did like this:
public class ItemViewHolder extends RecyclerView.ViewHolder {
public CardView cardView;
public TextView itemText;
public ImageView itemFirstButton;
public ImageView itemSecondButton;
public ItemViewHolder(View itemView) {
super(itemView);
cardView = (CardView)itemView.findViewById(R.id.item_card_view);
itemText = (TextView)itemView.findViewById(R.id.item_text);
itemFirstButton = (ImageView)itemView.findViewById(R.id.ic_first_action);
itemSecondButton = (ImageView)itemView.findViewById(R.id.ic_second_action);
itemFirstButton.setOnClickListener(firstListener);
itemSecondButton.setOnClickListener(secondListener);
}
private View.OnClickListener firstLinstener = new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
}
};
private View.OnClickListener secondListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
}
};
}
Inside the listener I can't get any reference on the item, just the position...
I have the impression I have to declare an interface for the listener and pass to the adapter, but I don't know how.
Does it make still sense to declare the listeners in the viewholder or I keep them in the adapter as the old solution?

If you want to tell your position to the adapter, just pass the some interface through ViewHolder constructor.
OR
the previous way.
The point is whom you want to delegate the position to.

Yes it is good practice to use interface. Create an interface like
public interface OnClick {
void getItem(int item);
}
Then setOnClicklistner like
itemViewHolder. itemText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
OnClick onClick = (OnClick)context;
onClick.getItem(position);
}
});
Then in your activity implement that method. From this you get position. Then by using position you get elements from array.

Related

how to set color the selected Item of Recycler View From Activity? [duplicate]

How to change the background color of only selected view in my recycle view example?only the background color of clicked itemview needs to be changed.
Only one selected item must be displayed with background color change at a time and the rest needs to be as before selecting.
here is my code :
MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView rv1;
private final String android_versions[]={
"Donut",
"Eclair",
"Froyo",
"Gingerbread",
"Honeycomb",
"Ice Cream Sandwich",
"Jelly Bean",
"KitKat",
"Lollipop",
"Marshmallow"
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews(){
rv1=(RecyclerView)findViewById(R.id.recyclerView1);
rv1.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager=new LinearLayoutManager(getApplicationContext());
rv1.setLayoutManager(layoutManager);
RecyclerDataAdapter rda=new RecyclerDataAdapter(rv1,getApplicationContext(),android_versions);
rv1.setAdapter(rda);
}
}
RecyclerDataadapter
public class RecyclerDataAdapter extends RecyclerView.Adapter<RecyclerDataAdapter.ViewHolder> {
private String android_versionnames[];
private Context context1;
private RecyclerView mRecyclerView;
public RecyclerDataAdapter(RecyclerView recylcerView,Context context,String android_versionnames[]){
this.android_versionnames=android_versionnames;
this.context1=context;
mRecyclerView=recylcerView;
setHasStableIds(true);
System.out.println("Inside dataadapter,Android names : \n ");
for(int i=0;i<android_versionnames.length;i++){
System.out.println("\n"+android_versionnames[i]);
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.row_layout,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.tv1.setText(android_versionnames[position]);
}
#Override
public int getItemCount() {
return android_versionnames.length;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv1;
LinearLayout row_linearlayout;
RecyclerView rv2;
public ViewHolder(final View itemView) {
super(itemView);
tv1=(TextView)itemView.findViewById(R.id.txtView1);
row_linearlayout=(LinearLayout)itemView.findViewById(R.id.row_linrLayout);
rv2=(RecyclerView)itemView.findViewById(R.id.recyclerView1);
/*itemView.setBackgroundColor(0x00000000);//to transparent*/
}
}
}
Finally, I got the answer.
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.tv1.setText(android_versionnames[position]);
holder.row_linearlayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
row_index=position;
notifyDataSetChanged();
}
});
if(row_index==position){
holder.row_linearlayout.setBackgroundColor(Color.parseColor("#567845"));
holder.tv1.setTextColor(Color.parseColor("#ffffff"));
}
else
{
holder.row_linearlayout.setBackgroundColor(Color.parseColor("#ffffff"));
holder.tv1.setTextColor(Color.parseColor("#000000"));
}
}
here 'row_index' is set as '-1' initially
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv1;
LinearLayout row_linearlayout;
RecyclerView rv2;
public ViewHolder(final View itemView) {
super(itemView);
tv1=(TextView)itemView.findViewById(R.id.txtView1);
row_linearlayout=(LinearLayout)itemView.findViewById(R.id.row_linrLayout);
rv2=(RecyclerView)itemView.findViewById(R.id.recyclerView1);
}
}
A really simple way to achieve this would be:
//instance variable
List<View>itemViewList = new ArrayList<>();
//OnCreateViewHolderMethod
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row, parent, false);
final MyViewHolder myViewHolder = new MyViewHolder(itemView);
itemViewList.add(itemView); //to add all the 'list row item' views
//Set on click listener for each item view
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
for(View tempItemView : itemViewList) {
/** navigate through all the itemViews and change color
of selected view to colorSelected and rest of the views to colorDefault **/
if(itemViewList.get(myViewHolder.getAdapterPosition()) == tempItemView) {
tempItemView.setBackgroundResource(R.color.colorSelected);
}
else{
tempItemView.setBackgroundResource(R.color.colorDefault);
}
}
}
});
return myViewHolder;
}
UPDATE
The method above may ruin some default attributes of the itemView, in my case, i was using CardView, and the corner radius of the card was getting removed on click.
Better solution:
//instance variable
List<CardView>cardViewList = new ArrayList<>();
public class MyViewHolder extends RecyclerView.ViewHolder {
CardView cardView; //THIS IS MY ROOT VIEW
...
public MyViewHolder(View view) {
super(view);
cardView = view.findViewById(R.id.row_item_card);
...
}
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final OurLocationObject locationObject = locationsList.get(position);
...
cardViewList.add(holder.cardView); //add all the cards to this list
holder.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//All card color is set to colorDefault
for(CardView cardView : cardViewList){
cardView.setCardBackgroundColor(context.getResources().getColor(R.color.colorDefault));
}
//The selected card is set to colorSelected
holder.cardView.setCardBackgroundColor(context.getResources().getColor(R.color.colorSelected));
}
});
}
UPDATE 2 - IMPORTANT
onBindViewHolder method is called multiple times, and also every time the user scrolls the view out of sight and back in sight!
This will cause the same view to be added to the list multiple times which may cause problems and minor delay in code executions!
To fix this,
change
cardViewList.add(holder.cardView);
to
if (!cardViewList.contains(holder.cardView)) {
cardViewList.add(holder.cardView);
}
I can suggest this solution, which I used in my app. I've placed this code of onTouchListener in my ViewHolder class's constructor. itemView is constructor's argument. Be sure to use return false on this method because this need for working OnClickListener
itemView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
v.setBackgroundColor(Color.parseColor("#f0f0f0"));
}
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)
{
v.setBackgroundColor(Color.TRANSPARENT);
}
return false;
}
});
Create Drawable file in Drawable foloder
<item android:drawable="#color/SelectedColor" android:state_pressed="true"></item>
<item android:drawable="#color/SelectedColor" android:state_selected="true"></item>
<item android:drawable="#color/DefultColor"></item>
And in xml file
android:background="#drawable/Drawable file"
In RecyclerView onBindViewHolder
holder.button.setSelected(holder.button.isSelected()?true:false);
Like toggle button
I was able to change the selected view color like this. I think this is the SIMPLE WAY (because you don't have to create instance of layouts and variables.
MAKE SURE YOU DONT GIVE ANY BACKGROUND COLOR INSIDE YOUR RECYCLER VIEW's TAG.
holder.itemView.setBackgroundColor(Color.parseColor("#8DFFFFFF"));
onBindViewHolder() method is given below
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, final int position) {
holder.item_1.setText(list_items.get(position).item_1);
holder.item_2.setText(list_items.get(position).item_2);
holder.select_cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
holder.itemView.setBackgroundColor(Color.parseColor("#8DFFFFFF"));
}else {
holder.itemView.setBackgroundColor(Color.parseColor("#FFFFFF"));
}
}
});
}
What I did to achieve this was actually taking a static variable to store the last clicked position of the item in the RecyclerView and then notify the adapter to update the layout at the position on the last clicked position i.e. notifyItemChanged(lastClickedPosition) whenever a new position is clicked. Calling notifyDataSetChanged() on the whole layout is very costly and unfeasible so doing this for only one position is much better.
Here's the code for this:
public class RecyclerDataAdapter extends RecyclerView.Adapter<RecyclerDataAdapter.ViewHolder> {
private String android_versionnames[];
private Context mContext;
private static lastClickedPosition = -1; // Variable to store the last clicked item position
public RecyclerDataAdapter(Context context,String android_versionnames[]){
this.android_versionnames = android_versionnames;
this.mContext = context;
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.row_layout,
parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.tv1.setText(android_versionnames[position]);
holder.itemView.setBackgroundColor(mContext.getResources().
getColor(R.color.cardview_light_background));
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
v.setBackgroundColor(mContext.getResources().
getColor(R.color.dark_background));
if (lastClickedPosition != -1)
notifyItemChanged(lastClickedPosition);
lastClickedPosition = position;
}
});
}
#Override
public int getItemCount() {
return android_versionnames.length;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv1;
public ViewHolder(final View itemView) {
super(itemView);
tv1=(TextView)itemView.findViewById(R.id.txtView1);
}
}
}
So we will be actually updating only the intended item and not re-running unnecessary updates to the items which have not even been changed.
If you use kotlin, it's really simple.
In your RecyclerAdapter class
userV.invalidateRecycler()
holder.card_User.setCardBackgroundColor(Color.parseColor("#3eb1ae").withAlpha(60))
In your fragment or Activity
override fun invalidateRecycler() {
if (v1.recyclerCompanies.childCount > 0) {
v1.recyclerCompanies.childrenRecursiveSequence().iterator().forEach { card ->
if (card is CardView) {
card.setCardBackgroundColor(Color.WHITE)
}
}
}
}
There is a very simple solution to this, you don't have to work in the adapter. To change the background of a clicked item in the RecyclerView you need to catch the click in the adapter using an iterface:
interface ItemClickListener {
fun onItemClickListener(item: Item, position: Int)
}
When we click we will get the item and the items position. In our bind function in the adapter we will set the on click listener:
container.setOnClickListener {
onClickListener.onItemClickListener(item, position)
}
In your activity you will then implement this interface:
class MainActivity : AppCompatActivity(), ItemAdapter.ItemClickListener {
Next we need to implement the background changing logic on item click. The logic is this: when the user clicks on an item, we check if the background on the clicked item is white (the item is not previously clicked) and if this condition is true, we change the background on all of the items in the RecyclerView to white (to invalidate previously clicked and marked items if there are any) and then change the background color of the clicked item to teal to mark it. And if the background of the clicked item is teal (which means the user clicks again on the same previously marked item), we change the background color on all of the items to white. First we will need to get our item background color as a ColorDrawable. We will use an iterator function to go through all of the items (children) of the RecyclerView and forEach() function to change the background on everyone of them. This method will look like this:
override fun onItemClickListener(item: Item, position: Int) {
val itemBackground: ColorDrawable =
binding.recycler[position].background as ColorDrawable
if (itemBackground.color == ContextCompat.getColor(this, R.color.white)) {
binding.recycler.children.iterator().forEach { item ->
item.setBackgroundColor(
ContextCompat.getColor(
this,
R.color.white
)
)
}
binding.recycler[position].setBackgroundColor(
ContextCompat.getColor(this, R.color.teal_200)
)
} else {
binding.recycler.children.iterator().forEach { item ->
item.setBackgroundColor(
ContextCompat.getColor(
this,
R.color.white
)
)
}
}
}
So now you change the background on item click, if you click the same item, you will change the background back to what it was before.
My solution:
public static class SimpleItemRecyclerViewAdapter
extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> {
private final MainActivity mParentActivity;
private final List<DummyContent.DummyItem> mValues;
private final boolean mTwoPane;
private static int lastClickedPosition=-1;
**private static View viewOld=null;**
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View view) {
DummyContent.DummyItem item = (DummyContent.DummyItem) view.getTag();
if (mTwoPane) {
Bundle arguments = new Bundle();
arguments.putString(ItemDetailFragment.ARG_ITEM_ID, item.id);
ItemDetailFragment fragment = new ItemDetailFragment();
fragment.setArguments(arguments);
mParentActivity.getSupportFragmentManager().beginTransaction()
.replace(R.id.item_detail_container, fragment)
.commit();
} else {
Context context = view.getContext();
Intent intent = new Intent(context, ItemDetailActivity.class);
intent.putExtra(ItemDetailFragment.ARG_ITEM_ID, item.id);
context.startActivity(intent);
}
**view.setBackgroundColor(mParentActivity.getResources().getColor(R.color.SelectedColor));
if(viewOld!=null)
viewOld.setBackgroundColor(mParentActivity.getResources().getColor(R.color.DefaultColor));
viewOld=view;**
}
};
viewOld is null at the beginning, then points to the last selected view.
With onClick you change the background of the selected view and redefine the background of the penultimate view selected.
Simple and functional.
In your adapter class make Integer variable as index and assign it to "0" (if you want to select 1st item by default, if not assign "-1").Then on your onBindViewHolder method,
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, final int position) {
holder.texttitle.setText(listTitle.get(position));
holder.itemView.setTag(listTitle.get(position));
holder.texttitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
index = position;
notifyDataSetChanged();
}
});
if (index == position)
holder.texttitle.setTextColor(mContext.getResources().getColor(R.color.selectedColor));
else
holder.texttitle.setTextColor(mContext.getResources().getColor(R.color.unSelectedColor));
}
Thats it and you are good to go.in If condition true section place your selected color or what ever you need, and else section place unselected color or what ever.
Calling Notifydatasetchanged May be expensive when you need to change one item We can overcome by saving the old position and call notifyItemChanged
var old_postion=-1
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.tv1.setText(android_versionnames[position]);
holder.row_linearlayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
notifyItemChanged(old_position)
//After item change happens set the old_postion as current position
old_position=position
}
});
if(old_position==position){
holder.row_linearlayout.setBackgroundColor(Color.parseColor("#567845"));
holder.tv1.setTextColor(Color.parseColor("#ffffff"));
}
else
{
holder.row_linearlayout.setBackgroundColor(Color.parseColor("#ffffff"));
holder.tv1.setTextColor(Color.parseColor("#000000"));
}
}
Create a selector into Drawable folder:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#color/blue" />
</shape>
</item>
<item android:state_pressed="false">
<shape>
<solid android:color="#android:color/transparent" />
</shape>
</item>
</selector>
Add the property into your xml (where you declare the RecyclerView):
android:background="#drawable/selector"
Add click listener for item view in .onBindViewHolder() of your RecyclerView's adapter. get currently selected position and change color by .setBackground() for previously selected and current item
Most Simpler Way From My Side is to Add a variable in adapterPage as last Clicked Position.
in onBindViewHolder paste this code which checks for last stored position matched with loading positions
Constants is the class where i declare my global variables
if(Constants.LAST_SELECTED_POSITION_SINGLE_PRODUCT == position) {
//change the view background here
holder.colorVariantThumb.setBackgroundResource(R.drawable.selected_background);
}
//on view click you store the position value and notifyItemRangeChanged will
// call the onBindViewHolder and will check the condition
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view){
Constants.LAST_SELECTED_POSITION_SINGLE_PRODUCT=position;
notifyItemRangeChanged(0, mColorVariants.size());
}
});
I managed to do this from my Activity where i'm setting my Rv and not from the adapter
If someone need to do something similar here's the code
In this case the color changes on a logClick
#Override
public void onLongClick(View view, int position) {
Toast.makeText(UltimasConsultasActivity.this, "Item agregado a la lista de mails",
Toast.LENGTH_SHORT).show();
sendMultipleMails.setVisibility(View.VISIBLE);
valueEmail.setVisibility(View.VISIBLE);
itemsSeleccionados.setVisibility(View.VISIBLE);
listaEmails.add(superListItems.get(position));
listaItems ="";
NameOfyourRecyclerInActivity.findViewHolderForAdapterPosition(position).NameOfYourViewInTheViewholder.setBackgroundColor((Color.parseColor("#336F0D")));
for(int itemsSelect = 0; itemsSelect <= listaEmails.size() -1; itemsSelect++){
listaItems += "*"+listaEmails.get(itemsSelect).getDescripcion() + "\n";
}
itemsSeleccionados.setText("Items Seleccionados : "+ "\n" + listaItems);
}
}));
My Solution
With my solution I'm not using notifyDataSetChanged(), because annoying whenever item is clicked, all the items from list got refreshed. To tackle this problem, I used notifyItemChanged(position); This will only change the selected item.
Below I have added the code of my omBindViewHolder.
private int previousPosition = -1;
private SingleViewItemBinding previousView;
#Override
public void onBindViewHolder(#NonNull final ItemViewHolder holder, final int position) {
holder.viewBinding.setItem(itemList.get(position));
holder.viewBinding.rlContainerMain.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
clickRecyclerView.clickRecyclerItem(position, 0);
previousPosition = position;
notifyItemChanged(position);
if(previousView != null){
previousView.rlContainerMain.setBackgroundColor(
ContextCompat.getColor(context, R.color.colorGrayLight));
}
}
});
if(position == previousPosition){
previousView = holder.viewBinding;
holder.viewBinding.rlContainerMain.setBackgroundColor(
ContextCompat.getColor(context, R.color.colorPrimary));
}
else {
holder.viewBinding.rlContainerMain.setBackgroundColor(
ContextCompat.getColor(context, R.color.colorGrayLight));
}
}
in the Kotlin you can do this simply:
all you need is to create a static variable like this:
companion object {
var last_position = 0
}
then in your onBindViewHolder add this code:
holder.item.setOnClickListener{
holder.item.setBackgroundResource(R.drawable.selected_item)
notifyItemChanged(last_position)
last_position=position
}
which item is the child of recyclerView which you want to change its background after clicking on it.
I made this implementation in kotlin I thing is not very efficient but works
ivIsSelected is a ImageView that represent in my case a check mark
var selectedItems = mutableListOf<Int>(-1)
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
// holder.setData(ContactViewModel, position) // I'm passing this to the ViewHolder
holder.itemView.setBackgroundColor(Color.WHITE)
holder.itemView.ivIsSelected.visibility = INVISIBLE
selectedItems.forEach {
if (it == position) {
holder.itemView.setBackgroundColor(Color.argb(45, 0, 255, 43))
holder.itemView.ivIsSelected.visibility = VISIBLE
}
}
holder.itemView.setOnClickListener { it ->
it.setBackgroundColor(Color.BLUE)
selectedItems.add(position)
selectedItems.forEach { selectedItem -> // this forEach is required to refresh all the list
notifyItemChanged(selectedItem)
}
}
}
A faster and simpler way is saving the previous View element selected, so you don't have to use notifyDataSetChanged() or notifyItemChanged(position).
First, add an instance variable inside your Adapter (RecyclerDataAdapter):
View prevElement;
Then, inside your function onClick() (or in my case the lambda function version) you insert this:
holder.itemView.setOnClickListener(v -> {
// CODE TO INSERT
if (prevElement != null)
prevElement.setBackgroundColor(Color.TRANSPARENT);
v.setBackgroundColor(R.color.selected);
prevElement = v;
// DO SOMETHING
...
});
As you can see, the first thing done is checking if the prevElement is not null (an element was clicked before this), so we change its background color to Color.TRANSPARENT (even if it's the same element clicked twice). Then, we set the background color of the View element clicked (v) is changed to R.color.selected. Finally set the element clicked to the prevElement variable, so it can be modified in the next click action.
The response from #Sudhanshu Vohra above was the best in my case and much simpler.
I did minor changes to handle the new selection and previous selection to adjust the display.
I modified it as:
//Handle selected item and previous selection
if (lastSelectedIndex != -1) {
notifyItemChanged(lastSelectedIndex);
}
notifyItemChanged(bindingAdapterPosition);
lastSelectedIndex = bindingAdapterPosition;
Now I refresh only two items, rather than the entire list and it works like a charm. Thank you.
I got it like this
public void onClick(View v){
v.findViewById(R.id.textView).setBackgroundColor(R.drawable.selector_row);
}
Thanks
я не знаю на сколько это поможет но я типа так сделал:) в адаптере #Override public void onBindViewHolder(#NonNull NoteViewHolder holder, int position) { holder.bind(sortedList.get(position)); holder.itemView.setBackgroundResource(R.drawable.bacground_button); }

Add item to RecyclerView dynamically

I want to be able to add items to my ReclycerView dynamically.
When an item loads -> setText() -> I add another item on list.
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
This is what I have inside onBindViewHolder. When I am on the first case if(), and I click the button, the item is added to the list. On the second Case else(), I would like for the text to be set on this current item and than already add another one.
How can I achieve this?
Moreover, why add() works inside onClickListener but not outside of it?
The error I get is:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
Thanks! :)
The error itself is self explanatory ... It is dangerous to setOnClickListener in onBindViewHolder. This method is step of refresh each recycler item.
You should move method setOnClickListener to ViewHolder which is inner class on your adapter.
class MyViewHolder extends RecyclerView.ViewHolder
{
private final OnClickListener mOnClickListener = new MyOnClickListener();
Button mAnswer1Button, mAnswer2Button;
public MyViewHolder (View itemView) {
super(itemView);
mAnswer1Button = (Button) itemView.findViewById(R.id.item);
mAnswer2Button = (Button) itemView.findViewById(R.id.item);
mAnswer1Button.setOnClickListener(mOnClickListener);
}
#Override
public void onClick(final View view) {
//Now your Logic ....
}
}
One more thing you could do is set Create an interface OnItemClickListener
and declare onItemClick and then make the activity from where you are setting up the adapter for the particular recycler view implment OnItemClickListener this and over there you can dynamically add another item and setAdapter again or notifyDataSetChanged()
Your InterFace
public interface OnItemClickListener{
public void onItemClick(int position);
}
Your MainActivity
class MainActivity implements OnItemClickListener{
RecyclerView mRecyclerView ;
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.layout);
mRecyclerView = (RecyclerView ) findViewById(R.id.recyclerview);
mAdapter= new MyAdapter(ArrayList, getContext(), MainActivity.this);
}
#Overrride
public void onItemClick(int position)
{
//Now your Logic ....
}
}
Now you can call this method onItemClick from the Adpater clas by setting onClickListener mAnswer1Button in the ViewHolder class and calling this method with in the onClick
As commented, I think it should work like this:
private int position;
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
this.position = position;
}
#Override
public void onViewAttachedToWindow(ViewHolder holder) {
final Message message = mDataset.get(position);
if(message.isAnswers()) {
holder.mAnswer1Button.setText(message.getAnswer1());
holder.mAnswer2Button.setText(message.getAnswer2());
holder.mAnswer1Button.setOnClickListener(v -> {
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mAnswer1Button.setClickable(false);
holder.mAnswer2Button.setEnabled(false);
}
});
} else {
holder.mMessageTextView.setText(message.getMessage());
if(message.getChild1() > 0) {
add(position + 1, dataListShared.get(message.getChild1()));
holder.mMessageTextView.setEnabled(false);
}
}
}
In onBindViewHolder(), you apparently cannot change the dataset - I guess as the framework is still busy displaying the previous dataset. But when saving the position in an instance variable, and updating the dataset in onViewAttachedToWindow(), the RecylerView should be ready for more data.
To be honest though, I wouldn't add all this logic to the ViewHolder, but pass an interface to it so the logic can be kept in a more central place, like a presenter.

Working with Long Click Listener with recycler-android

i am working on a notapad like android app project. i which i have implemented recycler.
My project contains NotedAdaper class that extends RecyclerView.Adapter<NotesAdapter.ViewHolder>
in that class using the below code i used click listener,
public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.ViewHolder> {
private List<Notes> mNotes;
private Context mContext;
public NotesAdapter(Context context, List<Notes> notes) {
mNotes = notes;
mContext = context;
}
#Override
public NotesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View notesView = inflater.inflate(R.layout.items_notes, parent, false);
// Return a new holder instance
ViewHolder viewHolder = new ViewHolder(notesView);
return viewHolder;
}
// Easy access to the context object in the recyclerview
private Context getContext() {
return mContext;
}
#Override
public void onBindViewHolder(NotesAdapter.ViewHolder viewHolder, final int position) {
// Get the data model based on position
Notes notes = mNotes.get(position);
// Set item views based on your views and data model
TextView textView = viewHolder.preTitle;
textView.setText(notes.getTitle());
TextView textView1 = viewHolder.preText;
textView1.setText(notes.getText());
String color=notes.getColor();
CardView preCard=viewHolder.preCard;
preCard.setBackgroundColor(Color.parseColor(color));
ImageView img = viewHolder.preImage;
img.setVisibility(View.GONE);
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Notes notes = mNotes.get(position);
Intent intent = new Intent(view.getContext(),EditNote.class);
Bundle bundle = new Bundle();
bundle.putSerializable("DATA",notes);
intent.putExtras(bundle);
getContext().startActivity(intent);
Toast.makeText(getContext(), "Recycle Click" + position+" ", Toast.LENGTH_SHORT).show();
}
});
}
// Returns the total count of items in the list
#Override
public int getItemCount() {
return mNotes.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
// Your holder should contain a member variable
// for any view that will be set as you render a row
public RobotoTextView preTitle, preText;
public ImageView preImage;
public CardView preCard;
public ViewHolder(View itemView) {
super(itemView);
preTitle = (RobotoTextView) itemView.findViewById(R.id.preTitle);
preText = (RobotoTextView) itemView.findViewById(R.id.preText);
preImage=(ImageView) itemView.findViewById(R.id.preImage);
preCard=(CardView) itemView.findViewById(R.id.preCard);
}
}}
And its absolutely working find. on clicking of a item in recycler, it retrieves the data using position of that item. and showing in another activity.
just like, suppose a activity shows the list of notes created by user. and clicking on any note, it shows the full content of that note.
but now i want to implement Long click listener on the item. and get the position.
so that, i used the following code ...
viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
Notes notes = mNotes.get(position);
Toast.makeText(getContext(), "long Click" + position+" ", Toast.LENGTH_SHORT).show();
return false;
}
});
so, its also working.
but what i want is, on long click, it should only show that Toast.
but its not only showing the long click toast. but also recognising click listener and after showing the toast>> "Long click: ..." it executing the the code written for single click event.
n i dont want it.
both listeners should work separately.
but why its executing single click after long click??? any idea???
Am i making mistake anywhere?
So, the following changes in my code, help me to achieve my output.
1) The method onBindViewHolder is called every time when you bind your view with data. So there is not the best place to set click listener. You don't have to set OnClickListener many times for the one View. Thats why, i wrote click listeners in ViewHolder, (actually that was not my question, but i read somewhere that it would be the best practice, thats why i am following it)
like this,
public static class ViewHolder extends RecyclerView.ViewHolder {
// Your holder should contain a member variable
// for any view that will be set as you render a row
public ImageView preImage;
public CardView preCard;
// We also create a constructor that accepts the entire item row
// and does the view lookups to find each subview
public ViewHolder(final View itemView) {
// Stores the itemView in a public final member variable that can be used
// to access the context from any ViewHolder instance.
super(itemView);
itemView.findViewById(R.id.preTitle);
preImage=(ImageView) itemView.findViewById(R.id.preImage);
preCard=(CardView) itemView.findViewById(R.id.preCard);
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
int p=getLayoutPosition();
System.out.println("LongClick: "+p);
return true;// returning true instead of false, works for me
}
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int p=getLayoutPosition();
Notes notes = mNotes.get(p);
Toast.makeText(getContext(), "Recycle Click" + p +" ", Toast.LENGTH_SHORT).show();
}
});
}
}
You may notice that, in onLongClick, i have returned "true", bydefault it was "false".
and this change works for me.
just make onLongClick(View v) returns return true instead of return false
this solved my problem it should solve yours too
i think you should set both the listeners from ViewHolder class.
itemView.setOnClickListener(...);
itemView.setOnLongClickListener(...);
And call getAdapterPosition() from ViewHolder to get the adapter position of the item.
You can checkout the following resource.
https://www.bignerdranch.com/blog/recyclerview-part-1-fundamentals-for-listview-experts/
I think this is an easier way to implement onClick and longClickListeners to RecyclerViews. Here's my code snippet. I've cut out unnecessary codes from here.
public class PrescriptionAdapter extends RecyclerView.Adapter<PrescriptionAdapter.ViewHolder> {
static final String TAG = "RecyclerViewAdapterMedicineFrequency";
ArrayList<Prescription> pdata;
Context context;
OnItemClickListener onItemClickListener;
OnItemLongClickListener onItemLongClickListener;
public PrescriptionAdapter(Context context, ArrayList<Prescription> presData, OnItemClickListener onItemClickListener, OnItemLongClickListener onItemLongClickListener) {
this.pdata = presData;
this.context = context;
this.onItemClickListener = onItemClickListener;
this.onItemLongClickListener = onItemLongClickListener;
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
TextView pName, totalMeds;
ImageView pImage;
OnItemClickListener onItemClickListener;
OnItemLongClickListener onItemLongClickListener;
public ViewHolder(View itemView, OnItemClickListener onItemClickListener, OnItemLongClickListener onItemLongClickListener) {
super(itemView);
pName = (TextView) itemView.findViewById(R.id.prescriptionName);
totalMeds = (TextView) itemView.findViewById(R.id.totalMeds);
pImage = (ImageView) itemView.findViewById(R.id.prescriptionImage);
this.onItemClickListener = onItemClickListener;
this.onItemLongClickListener = onItemLongClickListener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
onItemClickListener.onItemClick(getAdapterPosition());
}
#Override
public boolean onLongClick(View v) {
onItemLongClickListener.onItemLongClick(getAdapterPosition());
return true;
}
}
public interface OnItemClickListener {
void onItemClick(int i);
}
public interface OnItemLongClickListener {
void onItemLongClick(int i);
}
}

Open a new fragment and pass data when click on my Recycler CardView Grid?

Hello I need help to open a new fragment and pass data when clicked on my Recycler CardView Grid.
Android Grid Image
I want to click on for example the champion Aatrox (first grid) and open a new fragment with Aatrox Informatión. the same with the others champions of League of Legends.
I know that is inside of onClick function but I dont know how to do it.
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setClickable(true);
holder.itemView.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
}
});
Here is my full ChampAdapter.java
public class ChampAdapter extends RecyclerView.Adapter<ChampAdapter.ViewHolder> {
public List<ChampionItemModel> champItem;
public ChampAdapter(List<ChampionItemModel> champItem){this.champItem = champItem;}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView champName;
TextView roleChamp;
ImageView champImg;
public ViewHolder(View itemView) {
super(itemView);
this.champName = (TextView)itemView.findViewById(R.id.champ_name);
this.roleChamp = (TextView)itemView.findViewById(R.id.champ_role);
this.champImg = (ImageView)itemView.findViewById(R.id.champ_image);
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_champs,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setClickable(true);
holder.itemView.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
}
});
holder.champName.setText(champItem.get(position).champName);
holder.roleChamp.setText(champItem.get(position).roleChamp);
holder.champImg.setImageResource(champItem.get(position).champImg);
}
#Override
public int getItemCount() { return champItem.size();}
}
First you should embed the RecyclerView inside a fragment, like you normally would, let's call it ChampionOverviewFragment.
Now you should have a SingleChampionFragment with a static newInstance method that accepts as parameters everything that you need to build the champion information (for example a String with the id of your champ). We want to open this fragment when we click on one of the cards in your cardview.
Your activity now only has one HostFragment that you fill with the ChampionOverviewFragment in its onCreate method. See my answer on how to create nested fragments.
Your onClick method can now look like this:
#Override
public void onClick(View v){
((MainActivity) holder.itemView.getContext()).openChampionFragment(holder.getChampionId);
}
Of course, then your MainActivity has to include the following method:
public void openChampionFragment(String id)
this.hostFragment.replaceFragment(SingleChampionFragment.newInstance(id));
}
If you also need backstack navigation, refer to the tutorial I linked in the other answer.
Below is a general method on how to communicate between fragments, so it should be applicable to your issue also.
Place the recyclerView inside a fragment.
I am assuming you are able to get the position of the Adapter. Put recyclerview in a fragment call RV and it's response is to be seen in fragment say RVvalues. You use an interface called PassRVValues
Code in RV fragment:
public class RV extends Fragment{
int RVposition;
PassRVValues passRVValues;
//
your code for recyclerview and other things
//
RVposition = position_value_obtained;
passRVValues.sendToOtherFragment(RVposition);
Here's the code for the interface. Make a new java class having just the interface.
public interface PassRVValues{
sendToOtherFragment(int value_from_RVFragment);
}
Code in the activity
public class MainActivity implements PassRVValues{
//
some code
//
#Override
public void sendToOtherFragment(int use_value_from_RVFragment){
//
Do something with data if you want
//
RVvalues Frag = (RVValues)getFragmentManager().findFragmentById(R.id.rvvalues);
Frag.ShowDataBasedOnPositionInRV(something_based_from_any_process_in_sendToOtherFragment_Method);
}
Now for the code in the RVValues fragment
public class RVValues extends Fragment{
//again any codes//
public void ShowDataBasedOnPositionInRV(int data_based_on_RV_position){
//do something//
}
This is the way to implement inter-fragment communication in the simplest manner. Hope this helps!
Cheers!

RecyclerView CardView onItemClick

Got stuck on this problem for few days hope to find the solution.
I am unable to pass the data written on the card (Card View) to the next activity.
For clicking card, I am implementing its onClickListner in its recyclerView Adapter.
I want to pass the position of the card clicked but since card View does not support onItemClickListner I am facing the problem.
RecyclerViewAdapter class
public class RVAdapter extends recyclerView.Adapter<RVAdapter.PersonViewHolder> implements View.OnClickListener, View.OnLongClickListener {
int position;
#Override
public void onBindViewHolder(PersonViewHolder holder, int i) {
setEventsForViews(holder, i);
}
private void setEventsForViews(PersonViewHolder holder, int i) {
position = i;
holder.cv.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Navigator.toAddFriends(context, placeList.get(getPosition()).getPlaceId());
}
public static class PersonViewHolder extends RecyclerView.ViewHolder {
CardView cv;
PersonViewHolder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv);
}
}
I tried to set up a variable position and tried to pass its value but since onBindViewHolder is called intially for all items in the view and I end up in setting the value of position as last one. I know this is the wrong approach but as I am new to this card View thing please suggest me any good and clean way to do this.
Thanks in advance.
Create a new interface, like:
public interface RecyclerViewClickListener {
public void recyclerViewListClicked(View v, int position);
}
On your Fragment/Activity, implement the new interface:
public class MyFragment extends BaseFragment implements RecyclerViewClickListener { }
Now, you will be forced to implement the interface method recyclerViewListClicked(View v, int position) on your Fragment/Activity.
Do that so you can do whatever you want when one item is clicked. Do it like so:
#Override
public void recyclerViewListClicked(View v, int position) {
myList.get(position);
}
Now lets go to your Adapter.
On your adapter constructor, add a new listener parameter. It's type is the interface we have just created (don't forget to create a private static listener and set it on your constructor):
private static RecyclerViewClickListener mListener;
public myListAdapter(List<Thing> things, final Context context, RecyclerViewClickListener itemClickListener) {
mThings = things;
mContext = context;
mListener = itemClickListener;
}
Inside your adapter, on your ViewHolder, implement View.OnClickListener like:
public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
You will now be forced do #Override the onClickMethod inside your ViewHolder. Do it:
#Override
public void onClick(View v) { }
On your ViewHolder constructor, set the onClickListener of your view. Like:
public MyViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
myCardViewLayout.setOnClickListener(this);
}
Now implement the behaviour to your ViewHolder onClickMethod. In this case, I call the recyclerViewListClicked method of the static listener we declared in our adapter. This method is the one we have #Overriden in our fragment/activity, so it requires the position parameter.
Get the position by calling getLayoutPosition() inside your view holder. It will look like this:
#Override
public void onClick(View v) {
mListener.recyclerViewListClicked(v, getLayoutPosition());
}
Finally, get back to your fragment/activity, and pass the listener to the adapter constructor with this (since now we implement the interface RecyclerViewClickListener it can just pass itself as a listener to the adapter):
mRecyclerView.setAdapter(new MyListAdapter(this.mList, this.getActivity(), this))
Now, when you click on a myCardViewLayout on your list items it's going to call the recyclerViewListClicked(View v, int position) of your Fragment/Activity and you will be able to work with the position parameter as you'd like.
You can do this to any layout you want to get a click event from inside your list. Just replace myCardViewLayout.setOnClickListener(this) inside your ViewHolder constructor with myDesiredToHaveOnClickListenerLayout.setOnClickListener(this).
Hope this helps.
#EDIT:
Some tips:
Remove implements View.OnClickListener, View.OnLongClickListener from your adapter.
And looks like you don't need to call setEventsForViews(holder, i) on your onBindViewHolder anymore. Confirm?
#UPDATE
This is how your ViewHolder will look like:
public static class PersonViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
CardView cv;
PersonViewHolder(View itemView) {
super(itemView);
cv = (CardView) itemView.findViewById(R.id.cv);
cv.setOnClickListener(this);
}
#Override
public void onClick(View v) {
mListener.recyclerViewListClicked(v, getLayoutPosition());
}
}

Categories

Resources