I'm trying to implement a way to handle item selection on a RecyclerView. I personally don't like the way suggested in some answers on SO of passing through gestures, and I thought that implementing an OnClickListener, as suggested here and here, was waaay cleaner.
The fact is that... this pattern doesn't actually work! I'm really not able to understand why my OnClickListener.onClick is never called. It's kinda like another method intercepts the click before onClick can take care of it.
This is my code:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tvName;
ImageView star;
public ViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.CHAT_ITEM_name);
star = (ImageView) itemView.findViewById(R.id.CHAT_ITEM_star);
Fonts.setTypeface(tvName, regular);
}
#Override
public void onClick(View view) {
int position = getLayoutPosition();
select(position);
}
}
Unfortunately it's very important for me to able to access the position of the clicked item in the whole dataset, in order to remove it, so doing something like indexOfChild isn't acceptable too: I tried, but this method gives you the position of the item in the visibile part of the list, thus making list.remove(position) impossible.
Looking at the updated code: you are not setting the onClickListener to any of the views in the ViewHolder. It is an understandable mistake to forget the click listener.
Just use:
tvName.setOnClickListener(this);
star.setOnClickListener(this);
You can set to both or just one of them. You can also simply get the parent layout of these two views, so that the whole item itself in the adapter can be clickable.
itemView.setOnClickListener(this);
You can do it in your onBindViewHolder
#Override
public void onBindViewHolder(ReportViewHolder holder, int position {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// handle your click here.
} });
}
Simplely Click Handler your ViewHolder. Recycler View don't have special attaching click handlers like ListView which has the method setOnItemClickListener().
** public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
** in public ViewHolder(Context context, View itemView) set public void onClick(View view)
** get position by: int position = getLayoutPosition(); User user = users.get(position);
Related
I'm creating an adapter for a RecyclerView; currently creating an onClickListener to the items within the RecyclerView; I wish to go back to a fragment which I can't seem to do, so I have tried to add onBackPressed() with super.onBackPressed in the method as the fragment is on the previous page; I have called this in the onClickListener and it doesn't seem to work, any ideas how I can fix this?
Code is as follows:
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView text1, text2, text3;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
// This onClick doesn't accept onBackPressed() or popBackStack(), however I need to get this to the previous page which is a fragment.
}
});
You need to pass an OnClickListener (or a custom callback interface) to the Adapter and that you pass on to every ViewHolder. Once the viewholder onClick is called, you call the passed listener which then bubbles up to the Fragment/Activity where you created your Adapter.
A good sample of that can be found in this answer: https://stackoverflow.com/a/28304164/180538
And of course you can call onBackPressed() in this passed onClickListener or whatever code you want to have executed there
Context:
I've implemented a RecyclerView in my to-do list app.
I wanted to be able to use various onClick methods for items within the RecyclerView so I created an interface called onTaskListener.
This interface has two method stubs, one for onClick and one for onLongClick. In my ViewHolder, I implement both the onClick() and onLongClick() methods which simply pass off control to my onTaskClickListener().
In my adapter, I create an onTaskClickListener().
Then in my main activity, I implement the methods within onTaskClickListener().
My issue is that while my onTaskClick() works perfectly, my onTaskLongClick doesn't seem to function at all. Is there something wrong with the way I set up my RecyclerView/Adapter/ViewHolder/ViewModel pattern?
Question: If the way I have implemented my interface is wrong, how do I include multiple types of click events within a single interface?
Here are the relevant contents of each file (I know it's a lot, I'm very sorry for the wall of code):
onTaskClickListener.java:
public interface OnTaskListener {
void onTaskClick(int position); // Interfaces are implicitly abstract
void onTaskLongClick(int position);
}
itemViewHolder.java:
public class itemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
View itmView; // This is the general view
TextView txtView; // This is the specific text view that shows up as a singular task in the list of to-do tasks
OnTaskListener onTaskListener; // Create an OnTaskListener inside our view holder which allows the view holder to realize it's been clicked
public itemViewHolder(#NonNull View itemView, OnTaskListener inputOnTaskListener) {
super(itemView);
itmView = itemView;
txtView = itemView.findViewById(R.id.txtTask);
this.onTaskListener = inputOnTaskListener; // Take an onTaskListener that is passed into the object and store it internally
itemView.setOnClickListener(this); // passes the View.OnClickListener context to the itemView via "this"
}
#Override
public void onClick(View view) {
onTaskListener.onTaskClick(getAdapterPosition()); // This says that whenever we register a click event, we pass the logic onto the taskClick event
}
#Override
public boolean onLongClick(View view) {
onTaskListener.onTaskLongClick(getAdapterPosition()); // This says that whenever we register a longClick event, we pass the logic onto the taskClick event
return true; // This means that we have successfully consumed the long click event. No other click events will be notified
}
}
dataAdapter.java
public class dataAdapter extends RecyclerView.Adapter<itemViewHolder> {
List<taskItem> taskItemList;
private OnTaskListener onTaskListener;
public dataAdapter(List<taskItem> inputTaskItemList, OnTaskListener inputOnTaskListener){
this.taskItemList = inputTaskItemList;
this.onTaskListener = inputOnTaskListener;
}
#NonNull
#Override
public itemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View localView = LayoutInflater.from(parent.getContext()).inflate(R.layout.taskholder, parent, false); //Don't even know what this line does, it's all so over my head
return new itemViewHolder(localView, onTaskListener); // Return an instance of whatever we made directly above this line
}
#Override
public void onBindViewHolder(#NonNull itemViewHolder holder, final int position) {
holder.txtView.setText(taskItemList.get(position).taskTitle);
// Look inside our ViewModel and get the text for this specific instance of the ViewModel, which corresponds to the current position
}
#Override
public int getItemCount() {
return taskItemList.size();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements OnTaskListener{
private RecyclerView taskList; // Creates a RecyclerView to hook up to our RecyclerView widget in the UI
private dataAdapter localAdapter; // Instantiates our custom adapter class
List<taskItem> myItems; // Stores the items in a list of taskItem's
private RecyclerView.LayoutManager localLayoutManager; // God knows what this does :(
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
taskList = findViewById(R.id.taskList); // Connects our list from UI to recycler view code
localLayoutManager = new LinearLayoutManager(this); // assigns our localLayoutManager to an actual Layout Manager
taskList.setLayoutManager(localLayoutManager); // connecting our layout manager to our recycler view
taskList.setHasFixedSize(true);
myItems = new ArrayList<>(); // Now we FINALLY make our to-do list and populate it with actual tasks
myItems.add(new taskItem("groceries"));
myItems.add(new taskItem("practice bjj"));
localAdapter = new dataAdapter(myItems, this); // Pass the to do list to the adapter so it can feed it to the recycler view
taskList.setAdapter(localAdapter); // Lastly set the recycler view's adapter to the one we made above
}
#Override
public void onTaskClick(int position) {
taskItem currentTask = myItems.get(position);
if(!(currentTask.taskTitle.startsWith("Done: "))){ // Logic that marks a task as done on tap
currentTask.taskTitle = "Done: " + currentTask.taskTitle;
//logic that moves the tapped item to bottom of list
myItems.remove(position);
myItems.add(myItems.size(), currentTask);
localAdapter.notifyItemMoved(position, myItems.size());
}
else if(myItems.get(position).taskTitle.startsWith("Done: ")){ // Logic for if user taps a task already marked "done"
currentTask.taskTitle = currentTask.taskTitle.replaceFirst("Done: ", "");
myItems.set(position, currentTask); // Remove prefix
localAdapter.notifyItemChanged(position);
myItems.remove(position);
myItems.add(0, currentTask);
}
localAdapter.notifyDataSetChanged(); // Let the activity know that the data has changed
}
#Override
public void onTaskLongClick(int position) { // This branch deals with deleting tasks on long click
myItems.remove(position);
localAdapter.notifyItemRemoved(position); // Item has been deleted
}
}
You never call setOnLongClickListener():
public itemViewHolder(#NonNull View itemView, OnTaskListener inputOnTaskListener) {
super(itemView);
itmView = itemView;
txtView = itemView.findViewById(R.id.txtTask);
this.onTaskListener = inputOnTaskListener; // Take an onTaskListener that is passed into the object and store it internally
itemView.setOnClickListener(this); // passes the View.OnClickListener context to the itemView via "this"
// Add this line
itemView.setOnLongClickListener(this); // passes the View.OnLongClickListener context to the itemView via "this"
}
Alternatively, you can avoid going through this entirely by inlining the entire OnLongClickListener (and similarly for the OnClickListener):
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
onTaskListener.onTaskLongClick(getAdapterPosition()); // This says that whenever we register a longClick event, we pass the logic onto the taskClick event
return true; // This means that we have successfully consumed the long click event. No other click events will be notified
}
});
Thus avoiding having your itemViewHolder class implement the OnLongClickListener interface and making it impossible to forget to call setOnLongClickListener().
I am designing a RecyclerView list. Each item of the Recyclerview contains a LinearLayout. This LinearLayout contains two views, the first one is an EditText and the second one is Button. When user taps on the button, it fires an onclick event. From onClick listener, I need to get the content of the EditText. I don't find a way to access the content of a sibling view when the user taps on another sibling view.
My question is not "how can I set on click listener to a button inside adapter". Most of the people answered how to set onClick listener to a button which is there inside the recyclerview item. My question is bit different, when I am inside onClick method which is fired from button, how will I access the edittext which is a sibling of button. Every item has one edittext, so when I click on a button how will I find the correct edittext?
For example, I have a recylerview of size 10. And each item of recyclerview contains a LinearLayout and inside linearlayout two item, one is an Edittext and the other one is a Button. when I tap on 7th items button, how will I get the text of 7th item's Edittext? I hope I have explained it well
Any help would be appreciated.
First of, you need two references: one to your EditText and one to your Button. You can get those in your ViewHolder. Next, you need an OnClickListener. The ViewHolder can conveniently also implement one but you could also use onBindViewHolder() for that.
Inside that OnClickListener you can filter out your id with a switch statement if you want to and then get the content of the EditText like this:
switch(viewId) {
case R.id.buttonId:
String text = editText.getText().toString();
// do something with that text
return true;
}
In case you implemented an OnClickListener in your ViewHolder you can then do this button.setOnClickListener(this); inside your ViewHolder to make sure onClick() is actually called when you click the button.
EDIT:
Here's some sample code that should work for your case. I'm implementing View.OnClickListener here as mentioned above.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
EditText editText;
Button button;
public ViewHolder(View itemView) {
super(itemView);
editText = itemView.findViewById(R.id.editText);
button = itemView.findViewById(R.id.button);
button.setOnClickListener(this);
}
#Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.button:
String text = editText.getText().toString();
break;
}
}
}
This is what it would look like if you were to do it in your onBindViewHolder() (in this case you would NOT implement the OnClickListener in your ViewHolder obviously):
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String text = holder.editText.getText().toString();
}
});
}
Step 1 :Make an abstract function in your adapter.
abstract void onButtonClicked(String text);
Step 2: Declare your adapter Abstract.
Step 3: Override the method (onButtonClicked(String text);) in your activity were you have instantiated the adapter.
Step 4: In your adapter inside the onClickListener for your button call the function :
onButtonClicked(editText.getText().toString());
and you'll get the string in your activity where you overrided the method.
You can use holder pattern for RecylcerView adapter. Then you can set click listener on your button and get the text from EditText.
public class SimpleViewHolder extends RecyclerView.ViewHolder {
private EditText simpleEditText;
private Button simpleButton;
public SimpleViewHolder(final View itemView) {
super(itemView);
simpleEditText = (EditText) itemView.findViewById(R.id.simple_edit_text);
simpleButton = (Button) itemView.findViewById(R.id.simple_button);
simpleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String text = simpleEditText.getText().toString()
}
});
}
}
You can do it inside your adapter which is being set on the recycler view.
public class MyViewHolder extends RecyclerView.ViewHolder {
public ImageView mImageView;
public MyViewHolder(View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.imageView);
}
}
And in the bindview holder you can access the views
public void onBindViewHolder(#NonNull final ImageAdapter.MyViewHolder holder, int position) {
holder.mImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Your Code to access the edit text content
}
});
}
use a custom listener like this in the holder:
public class SimpleViewHolder extends RecyclerView.ViewHolder {
private EditText simpleEditText;
private Button simpleButton;
public SimpleViewHolder(final View itemView, final OnItemSelectedListener listener) {
super(itemView);
simpleEditText = (EditText) itemView.findViewById(R.id.simple_edit_text);
simpleButton = (Button) itemView.findViewById(R.id.simple_button);
simpleButton.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
String text = simpleEditText.getText().toString();
if(listener != null) listener.onItemSelected(text);
}
});
}
public interface OnItemSelectedListener{
void onItemSelected(String value);
}
}
Simply Use Your ViewHolder. It contains all the children you want. Implement the code inside your adapter where each item is inflated. Here is an example.
//inside the onBindViewHolder
viewHolder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String text = viewHolder.editText.getText().toString();
Log.d("output", text);
}
});
If you are using FirebaseUI Implement this inside the populateViewHolder for older version and onBindViewHolder for the later versions.
People put -1 when they don't know the answer or when they do not understand question. So funny!! In onclick I took parent(getParent()) from the view and accessed the second child of the parent. With that I am able to access the content of the sibling.
` public void onClick(View v) {
for(int i = 0;i<parent.getChildCount();i++){
if(parent.getChildAt(i)instanceof EditText){
passwordView = (EditText)parent.getChildAt(i);
}
}
}`
I have a RecyclerView. When I click a button inside an item in RecyclerView, I want to change the color of a View in that item. The following is my code and it works fine. But, the problem is the item will have an animation which is ugly. I want to update the item without the animation. How should I do that? By the way, I don't want to turn off the animation, only for this click event.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageView imageView;
public Button button;
public ItemViewHolder(View view) {
super(view);
//do something
button.setOnClickListener(this);
}
#Override
public void onClick(View view) {
//change color
notifyItemChanged(getAdapterPosition());
}
}
Try this
notifyItemChanged(position, Object);
This will update the position without animating it as we are passing our Object in it.
Try this and do let me know.
For Kotlin you can use
notifyItemChanged(int position, #Nullable Object payload)
Based on the Rakshit's answer, in Kotlin 1.2 the following code works fine:
notifyItemChanged(position, Unit)
There is a dedicated method to disable just item changed animations:
((SimpleItemAnimator) myRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
Ref.: https://developer.android.com/reference/androidx/recyclerview/widget/SimpleItemAnimator
in kotlin : recyclerView.itemAnimator = null
in java : recyclerView.setItemAnimator(null);
developer said :
A null return value indicates that there is no animator and that item changes will happen without any animations.
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#getItemAnimator()
recyclerView.getItemAnimator().setChangeDuration(0);
Or this.
Try this
csRecyclerView.getItemAnimator().setChangeDuration(0);
for more information RecyclerView.ItemAnimator
Try this:
public ItemViewHolder(View view) {
super(view);
//do something
button.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
holder.itemView.setBackgroundColor(Color.parseColor("#000000"));
notifyDataSetChanged();
});;
}
}
I've got stuck with an issue about setting an OnItemClickListener to my RecyclerView items. I tried to set a listener the way described in the RecyclerView sample of Android Studio. So a listener is set in the ViewHolder class for my RecyclerView.
public class ProgramViewHolder extends RecyclerView.ViewHolder {
protected TextView vName;
protected ImageView vProgramImage;
public ProgramViewHolder(View v) {
super(v);
vName = (TextView) v.findViewById(R.id.programName);
vProgramImage = (ImageView) v.findViewById(R.id.programImage);
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// HERE PROBLEM !!
MainActivity.openSettings(1);
}
});
}
}
Now I want to call a method of my MainActivity openSettings(int ) to load a fragment:
public void openSettings(int layoutId) {
settingsFragment setFrag = new settingsFragment();
Bundle information = new Bundle();
information.putInt("layoutId", layoutId);
setFrag.setArguments(information);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, setFrag)
.commit();
}
But now the problem. When I try to compile, it says "Non-static method 'openSettings(int )' cannot be referenced from a static context."
I quite not understand this error. Why is it a static context? The class ProgramViewHolder ist not declared static.
And the most important part: How can I fix it? I want to set a OnClickListener to every item of RecyclerView and call a public method of MainActivity.
Thanks a lot to you, for your time spending to help me.
It's not that ProgramViewHolder is static, it's because attempting to call your activity from a static context (you aren't calling a specific instance of the activity).
What you should do is pass the activity into your recyclerViewAdapter so that you have access to it.
For example
MainActivity mainActivity;
public CustomRecyclerViewAdapter(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
And to create the recyclerViewAdapter from MainActivity
CustomRecyclerView recyclerViewAdapter = new CustomRecyclerViewAdapter(this);
recyclerViewAdapter.setAdapter(recyclerViewAdapter);
You should then be able to access your method like this
mainActivity.openSettings(1);
Let me know if you have any trouble
//Edit
Here's how you would set onClick from bindViewHolder. You want to set up any onClickListeners here due to the way RecyclerView "recycles" data. For example, if each row should perform a different action on click, you need to make sure the click listener is tied to the specific row. Creating this in onBindViewHolder ensures this. If you want an entire row to be clickable, rather than elements inside, just create an outer view that fills the entire row. Then tie the onClickListener to that.
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
ProgramViewHolder programViewHolder = (ProgramViewHolder) holder;
programViewHolder.vName.setOnClicklistener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mainActivity.openSettings(1);
}
});
}
if you have context of the activity containing recyclerView, then you can simply do this:
your_view_holder.v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// HERE SOLUTION!!
((MainActivity)context).openSettings(1);
}
});
You can place this in onBindViewHolder(...)
How to get context:
Create another parameter of context in your Adapter's constructor , and pass the context from your activity once instantiating Adapter .
why pass the context:
i would recommend you to always pass context and assign it to any adapter's variable because this is something you would require every now and then while working with your adapter, so instead of using a workaround every time for context, just save it once .