In my onBindViewHolder of my RecyclerView.Adapter<SearchAdapter.ViewHolder> when user clicks on cardview a button becomes visible. But when I'm scrolling recyclerview some other items buttons are shown as visible too. Why is this happening?
this is my code:
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
viewHolder.card.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (viewHolder.content_layout.getVisibility() == View.VISIBLE) {
viewHolder.content_layout.setVisibility(View.GONE);
viewHolder.address.setMaxLines(2);
viewHolder.attribute.setMaxLines(2);
} else {
viewHolder.content_layout.setVisibility(View.VISIBLE);
viewHolder.address.setMaxLines(8);
viewHolder.attribute.setMaxLines(8);
}
}
});
...
}
Once you start scrolling down the list your views get recycled. This means a previously inflated ViewHolder (some that gets created in onCreateViewHolder) is reused.
So what you have to do is to remember the clicked positions (e.g. via a SparseBooleanArray) and check in onBindViewHolder whether the view should be visible (previously clicked) or not.
You can find a basic usage example of the SparseBooleanArray in this StackOverflow post
The 'other' visible items buttons are the ones using the same viewholder that was modified in the callback. So because viewholders (and views) are recycled :
They should only store information that can be retrieved each time the viewholder is bound to a position.
Anything that may be changed in the views state should be refreshed in onBindViewHolder()
In your case you should store the 'is selected' somewhere else and reset the visibility and maxlines in onBindViewHolder() (not only in the callback)
Good idea is to make a class object with all data you need for one item in recycler view, also add there one boolean isItemWasClicked and inside onBindViewHolder() check this boolean and make buttons visible or not.
For example:
public class OneItemOfList{
int priceToDisplay;
String name;
String date;
boolean wasClicked;
}
public class YourAdapter extends RecyclerView.Adapter<OneItemOfList.ViewHolder> {
ArrayList<OneItemOfList> items;
...
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
viewHolder.view.setText(items.get(position).name);
if (items.get(position).wasClicked)
viewHolder.button.setVisible(View.VISIBLE);
else
viewHolder.button.setVisible(View.GONE);
viewHolder.view2.setOnClickListener(...
OnClick(...){
items.get(position).wasClicked = !items.get(position).wasClicked;
});
}
...
}
create an array for example Boolean array, and when each position clicked, set true in same position of array. and in onBindViewHolder check if that array[position] is true set that item visible if.
Related
I am trying to make a text change when a button located along with the text (layoutPasswd) in recycler view and to change it back if the button is again pressed.Like a password hiding button. The values to the adapter is from a static class object as arraylist. The problem occurring now is that the value for all the items (only for layoutPasswd) in recycler view is same.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
holder.layoutUName.setText(users.get(position).getUserName());
pos = position;
holder.layoutPasswd.setText("********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.view1) {
holder.layoutPasswd.setText(users.get(pos).getPasswd());
holder.btnViewChanger.setText("hide");
holder.view1 = false;
} else {
holder.layoutPasswd.setText("********");;
holder.btnViewChanger.setText("Show");
holder.view1 = true;
}
}
});
You cannot rely on the ViewHolders or Views in a RecyclerView to hold any state, because they are recycled. Every time a view scrolls onto the screen, first it calls your onBindViewHolder function to update the contents of that ViewHolder to match the data.
Any configuration you set on the views or the ViewHolder instance in onBindViewHolder cannot be relied on to stay the same if the view scrolls off the screen, because the original ViewHolder might be recycled to be used for some other data, and when it scrolls back on screen, you might be looking at some other view that has been recycled from other data that just scrolled off the screen.
So if your views have configuration that you want to "stick", you have to back it up when you change it, and restore it in onBindViewHolder. The way you accomplish this will depend on how you are managing the data that you pass to the adapter.
If you can modify your User class, you can add a Boolean to it that stores whether it should show the password. Then in your onBindViewHolder, you restore the state based on this Boolean. And you also update this Boolean when the state changes.
I also updated the way the click listener works to simplify it for toggling. I removed the pos = position line, because almost certainly that is not something you should be doing.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
final User user = users.get(position)
holder.layoutUName.setText(user.getUserName());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
user.setShowPassword(!user.isShowPassword());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setText(user.isShowPassword() ? "hide" : "show");
}
});
// ...
}
If you cannot modify the User class, this is more complicated. Then the adapter should have its own ArrayList<Boolean> to store the state by position index, but you need to keep this list at least as long as the data that is bound, and reset everything to false if the whole list of data is refreshed.
I have Recycler View with a lot of items in. What I want to do is to change the text in TextView inside item that was clicked. I did it in that way:
wordList.set(position, newWord);
MyProgressActivityAdapter newAdapter = new MyProgressActivityAdapter(wordList, this);
newAdapter.notifyItemChanged(position);
recyclerView.setAdapter(newAdapter);
And everything works fine except of the fact that the screen goes to the top every time I click item. What can I do to avoid that?
You should use the payload version of notifyItemChanged, here is a simple example for you to get the hang of it:
adapter.notifyItemChanged(position, "updateText");
And then in your RecyclerAdapter override the payload version of onBindViewHolder:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position, #NonNull List payloads) {
if (payloads.isEmpty()) onBindViewHolder(holder, position);
else if ("updateText".equals(payloads.get(0))) {
if (holder instanceof YourViewHolder) {
((YourViewHolder) holder).textView.setText(dataProvider.get(position).getNewText());
}
}
}
Note that this approach prevents RecyclerView from creating a new ViewHolder and then binding your data, so you should just call the notifyItemChanged without resetting the adapter and so.
notifyItemChanged(position) should work if you handle it correctly. Try to handle this inside onBindViewHolder like below:
override fun onBindViewHolder(holder: RecyclerHolder, position: Int) {
holder.itemView.text_view.text = items[position]
holder.itemView.button.setOnClickListener {
items[position] = "New Text"
notifyItemChanged(position)
}
}
I have a rather strange issue when using either OnFocusChangeListener or TextWatcher in my RecyclerViewAdapter. I have a non-static list of input fields in the RecyclerView, and I want to listen for changes basically as soon as the user is done typing. When tapping on an input, the listeners or watchers begin firing infinitely about 10 times a second. The adapter is a RealmRecyclerViewAdapter but I don't think that is causing the issue.
I have tried setting the listeners in the ViewHolder constructor and in onBindViewHolder. Here is my FocusChangeListener
private class BodyFocusChangeListener implements OnFocusChangeListener {
private int position;
private boolean isFocused;
public void updatePosition(int position) {
this.position = position;
}
#Override
public void onFocusChange(View view, boolean hasFocus) {
String noteId = getItem(position).getId();
updateNote(noteId, ((EditText) view).getText().toString());
}
}
and in onBindViewHolder
holder.titleFocusChangeListener.updatePosition(holder.getAdapterPosition());
so that my listener knows which element is which.
In the below code 'setlisteners' method is called every time,should'nt value of position change?
There is only one method'setlisteners'in which parameter position is passed,but when in log ,i see that whenever click event happens on my buttons,it shows correct position. should'nt position overwrite itself and show incorrect position because click event can happen at any time ,by that time value of position will have changed?
let's suppose 5 views have been binded and i click on the first
itemview,then should'nt it show me the last position held by the last
binded itemview?
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
setListeners(holder,position);
}
private void setListeners(ViewHolder holder, final int position) {
holder.updownarrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.e("ddfg",""+position);
}
});
holder.leftlayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
position will hold the position of the last holder which was bind in onBindViewHolder, since my View does not call onBindViewHolder when i click on it,should'nt position hold the position last binded and show only that one position whenever any view gets clicked?
The position in onBindViewHolder is the same as the position in your adapter/data set. Your Click Listener is being set with the position the individual ViewHolder represented in your data set at the time of binding. Even though the click event isn't being called until after all your Views are binded, each function instance was already set up to use the corresponding position each view was set with.
If each View shared the same instance of a Click Listener, then you would only get the last position. But, each View holds its own instance of a Click Listener.
No, position will hold the position of the last holder which was bin in onBindViewHolder, since your View does not call onBindViewHolder when you click on it.
There are many possibilities to retrieve the correct position - for example if you set & get the position in the Views tag or search for the surrounding ViewHolder and check its position in the adapter or layout.
Dont use the position in onBindViewHolder. use this:
final int adapterPosition = viewholder.getAdapterPosition();
// do what ever with it
I'm new to this android/java stuff. I have this onItemSelected that will toast what has been selected in the spinner. I want to have the string resumeTableName accessible throughout my entire class that has the value of the selected spinner object. Right now it toasts the selected value however at other places in my class the resumeTableName remains null. I thought the public modifier would make it visible. How do I make this visible, do I use some sort of return?
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
// On selecting a spinner item
String resumeTableName = parent.getItemAtPosition(position).toString();
// Showing selected spinner item
Toast.makeText(parent.getContext(), resumeTableName,
Toast.LENGTH_LONG).show();
}
Use a static String resumeTableName; declared in your class and
in your onItemSelected write
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
// On selecting a spinner item
resumeTableName = parent.getItemAtPosition(position).toString();
// Showing selected spinner item
Toast.makeText(parent.getContext(), resumeTableName,
Toast.LENGTH_LONG).show();
}
In this way the value of resumeTableName updated by the method will be the same for the whole class.
If You are new in Android programming there is a nice solution for the problems with visibility of variables.
You have to create class in your package:
public class GlobalVar extends Application{
private int dummy1;
public int getDummy1() {
return dummy1;
}
public void setDummy1(int dummy1) {
this.dummy1 = dummy1;
}
}
then in any place in Your application You can get/set this data, by using:
GlobalVar gV = (GlobalVar)GetApplicationContext();
and then You just modify them by using Getters/Setters or any public methods from this class. I think it's very good solution for set of variables that You use often from different places of code.