I want to add an onClickListener to items in my RecyclerView. I added the listener in the Holder class as follows:
public class Holder extends RecyclerView.ViewHolder {
TextView firstName;
TextView lastName;
public Holder (final View itemView) {
super(itemView);
firstName = itemView.findViewById(R.id.firstName );
lastName= itemView.findViewById(R.id.lastName);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Do work
}
}
}
But, I think this will cause the scrolling of the list to be a little jerky and not perfectly smooth specially on old devices.
Question 1:
Is there a better way to do that? Or how can I optimize my code?
Question 2:
I intend to add a dynamically changing variable for each item in the list such as a timer, and I don't want the scrolling to be too slow! How should I update the timers the best way?
Create a member variable for item OnClickListener and set it in Holder's constructor.It will be one listener in your adapter when app is running.
Jerky Scrolling
Since you are using RecyclerView I don't think that you will face any issue with scrolling because RecyclerView inherently comes with ViewHolder Pattern. (In case of Simple listView you have to make ViewHolder to avoid jerky scrolling)
Code improvement
Instead of adding a Listener in ViewHolder, make it a Class variable in your RecyclerView Adapter.
There is a standard way to add a Listener in RecyclerView
Create a listener
interface ClickListener{
void click();
}
implement this listener to Your Activity
YourActivity implements ClickListener{
}
Typecast this listener in Your Adapter
YourAdapter extends RecyclerView.Adapter<YourAdapter.Holder>{
ClickListener listener;
public YourAdapter(Context context)
{
this.context = context;
listener = (ClickListener)context;
}
public class Holder extends RecyclerView.ViewHolder {
TextView firstName;
TextView lastName;
public Holder (final View itemView) {
super(itemView);
firstName = itemView.findViewById(R.id.firstName );
lastName= itemView.findViewById(R.id.lastName);
}
// Item Click listener goes here.
#Override
public void onBindViewHolder(DownLoadViewHolder holder, final int position) {
// Do something
listener.click();
}
}
Just giving you the overview.
You can see THIS for reference.
Related
I have an activity which contain a recycler view, in this activity is implemented the edit mode like a lot of applications. everythings works well but I have some performance issue and i'am tryng to goes more deep in the pest practices.
User goes in edit mode by select a menuItem in the toolbar which is placed in the activity, so in the menuItemClickListener in the activity I call a method of the adapter which is used to tell him that user want to go in edit mode:
mAdapter.setEditMode(true);
then in the adapter:
public void setEditMode(boolean editMode){
this.editMode = editMode;
notifyDataSetChanged(); //in order to change the items layout
}
Now the most difficult part: I need to change the itemClickListener when the editMode variable is set to true, so the listener associated with the holder's itemView change dinamically. I am doing this think in onBindViewHolder so I can set the right listener when the edit mode variable change.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder,int position) {
if(editMode){
holder.itemView.setOnClickListener(listener1);
}
else{
holder.itemView.setOnClickListener(listener2);
}
}
this solution works but I know that placing a listener inside onBindViewHolder method is a bad practice so I would like to find a solution that allows to implement the listener in the viewHolder constructor.
This is not simple because when the editMode variable is changing the viewHolder constructor is not being called, so he can't set the right listener.
are there any best practice to do this?
After scouring various StackOverFlow answers regarding the most optimum location for a clickListener, people seem to be divided across multiple implementations. Here is what I know for adding a listener in the ViewHolder.
1. Adapter:
In your Adapter, override the onCreateViewHolder() method
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(//pass in your args);
ImageView imageview1 = //init your views
TextView textView = //init your views
return new MyViewHolder(view, textView);
}
2. Viewholder:
When you create your Viewholder class, allow it to implement View.OnClickListener and override the onClick method there.
public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageView imageView1;
private MyViewHolder(View itemView, ImageView imageView) {
super(itemView);
itemView.setOnClickListener(this);
imageView1 = imageView;
}
#Override
public void onClick(View view) {
//Implement your click functionality here
}
}
Edited:
I want know about creating Anonymous Listener in bindViewHolder method cause any performance problem or not for large data set.
Suppose i have a RecyclerView Adapter. And in bindViewHolder method if i set all my listeners Anonymously does this cause any performance problem? Because when user scrolls the RecyclerView it will create lots of Anonymous listeners and set them to the views.
Example:
view.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
}
});
Or i can implements OnClickListener in my ViewHolder class and just add to views.Like
view.setOnClickListener(this);
Here lots of Anonymous Listeners are not created. Does this perform better from previous in performance calculation for large dataset?
Thanks in advance.
RecyclerView will only display few items, ViewHolder will only be created for items that are visible, so even if you have 1000s of items in your adapter, only small fraction of ViewHolders are created.
But you will have to be careful with addListener methods, for most setListener methods, you will be setting same listener again and again when item is recycled, which does not take less then few milliseconds as it only keeps reference of the listener implementation.
But with addListener, you will have to remove old listener before adding new one.
Example of setListener is setClickListener and example of addListener is addTextWatcher
//.. part of adapter
private TextWatcher textWatcher;
public void bindViewHolder(DataViewHolder holder, int index){
// no performance issue
holder.button.setClickListener( .... );
// wrong, this is added everytime
holder.editText.addTextWatcher( .... );
// this is safe...
if(textWatcher != null)
holder.editText.removeTextWatcher(textWatcher);
textWatcher = new TextWatcher(){
// ... implementation
};
holder.editText.addTextWatcher(textWatcher);
}
Basically, you set a OnClickListener in every item of your RecyclerView and "connect" it to your Activity or Fragment. This "connection" is important, so you can have your onItemClick method inside your Activity or Fragment and access the members there.
A minimal implementation would look like this (in a Fragment, but you can also use an Activity):
public class YourFragment extends Fragment implements RecyclerViewAdapter.ItemClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_your, container, false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(activity);
recyclerViewAdapter.setClickListener(this);
recyclerView.setAdapter(recyclerViewAdapter);
return view;
}
#Override
public void onItemClick(View view, int position) {
// do something here
}
}
And the Adapter class
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private ItemClickListener itemClickListener;
void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
interface ItemClickListener {
void onItemClick(View view, int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// TextView is an example
final TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
textView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (itemClickListener != null) {
itemClickListener.onItemClick(view, getAdapterPosition());
}
}
}
}
I'm pretty sure the compiler just creates a no-name concrete version of your anonymous class under the hood. That is nearly identical to implementing the interface and providing this as a concrete listener. Realistically, you shouldn't have a performance problem with either.
Just keep in mind that of the fact that an anonymous class holds a reference to the outer class. That might create memory leaks (example: if the outer class is an activity) or just make it so that garbage collection happens all at once instead of small pieces over time. See Implicit and Synthetic Parameters in the oracle documentation for more details on that.
I have implement a RecyclerView and it works fine. I have an ArrayList which contains the data for the recycler view. The layout of each item is complicated. It contains two frameLayout. The framelayout1 contains an image and a text and the framelayout2 contains an image and four texts. When the user clicks on the framelayout1 I want to open the Activity1 and when the users clicks on the framelayout2 I want to open the Activity2. I have already search for the onClick in Recycler View and I have found very useful this. But how can I get the position of the arrayList in order to pass it via Intent in the activity1 or activity2?
Try getAdapterPosition() from inside the view holder so that you may get the adapter position of the click the user made.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Toast.makeText(context, String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT).show();
}
}
For more in getAdapterPosition() follow this link
Try this
public class ClosetListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
ClosetListAdapter (CallBack callback){
this.callback = callback
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder baseholder, int position) {
ViewHolder holder = (ViewHolder) baseholder;
holder.setPosition(position);
holder.name.setText(product.getName());
}
static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView name = null;
private OnProductClickListener onProductClickListener;
public ViewHolder(View itemView, OnProductClickListener onClickListener) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.item_name);
itemView.setOnClickListener(this)
}
public void setProdcut(Product product) {
this.product = product;
}
#Override
public void onClick(View v) {
if (callback!= null) {
callback.itemClicked(pos);
}
}
public void setPosition(int position){
this.pos = position;
}
}
interface CallBack {
void itemClicked(int position);
}
}
I've also faced the same problem.
I wanted to find of the position of the clicked/selected item of the RecyclerView() and perform some specific operations on that particular item.
getAdapterPosition() method works like a charm for these kind of stuff. I found this method after a day of long research and after trying numerous other methods.
int position = getAdapterPosition();
Toast.makeText(this, "Position is: "+position, Toast.LENGTH_SHORT).show();
You do not have to use any extra method. Just create a global variable named 'position' and initialize it with getAdapterPosition() in any of the major method of the adapter (class or similar).
Here is a brief documentation from this link.
getAdapterPosition
added in version 22.1.0
int getAdapterPosition ()
Returns the Adapter position of the item represented by this ViewHolder.
Note that this might be different than the getLayoutPosition() if there are pending adapter updates but a new layout pass has not happened yet.
RecyclerView does not handle any adapter updates until the next layout traversal. This may create temporary inconsistencies between what user sees on the screen and what adapter contents have. This inconsistency is not important since it will be less than 16ms but it might be a problem if you want to use ViewHolder position to access the adapter. Sometimes, you may need to get the exact adapter position to do some actions in response to user events. In that case, you should use this method which will calculate the Adapter position of the ViewHolder.
Happy to help. Feel free to ask doubts.
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);
Hi I have a app that uses a Recycler view to display a bunch of items. Now I want to run an android test on the list but I don't know how to set a programmatic click on a given item. Can anyone tell me how to achieve this?
You have to implement it from scratch. The RecyclerView lacks for some of the awesome features that a ListView provides by default. Given this, in the adapter you have to declare an interface for the Observer between Fragment/Activity and the RecyclerView.
The correct method to attach this click event is onBindViewHolder. Then in the adapter you keep the reference to the CustomListener or the View.OnClickListener.
Here is a quick example on how to do this:
#Override
public void onBindViewHolder(SearchListAdapter.ViewHolder holder, final int position) {
//Item clicked
holder.mParent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Select or deselect
mListener.notify(holder, position);
}
});
}
And the ViewHolder should be like:
public class ViewHolder extends RecyclerView.ViewHolder {
private View mParent;
public ViewHolder(View itemView) {
super(itemView);
mParent = itemView;
}
}
Where mParent is the current row View.