The result of my code is that all the checkboxes are checked only after I scroll down past the checkboxes in the recyclerView - when I scroll back up, they are checked.
How can I have them checked as soon as I click the button?
In my activity I have:
private void publicButton() {
publicContacts.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//loop through the contacts
int count = PopulistoContactsAdapter.theContactsList.size();
for (int i = 0; i < count; i++) {
PopulistoContactsAdapter.theContactsList.get(i).setSelected(true);
}
}
});
}
And for the recyclerViewAdapter:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
//bind the views into the ViewHolder
//selectPhoneContact is an instance of the SelectPhoneContact class.
//We will assign each row of the recyclerview to contain details of selectPhoneContact:
//The number of rows will match the number of phone contacts
final SelectPhoneContact selectPhoneContact = theContactsList.get(position);
//if the row is a matching contact
if (viewHolder.getItemViewType() == 1)
{
//in the title textbox in the row, put the corresponding name etc...
((MatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
((MatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
//((MatchingContact) viewHolder).check.setText("Cheeckbox" + position);
((MatchingContact) viewHolder).check.setChecked(theContactsList.get(position).isSelected);
((MatchingContact) viewHolder).check.setTag(position);
((MatchingContact) viewHolder).check.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//pos is the row number that the clicked checkbox exists in
Integer pos = (Integer) ((MatchingContact) viewHolder).check.getTag();
Toast.makeText(context_type, theContactsList.get(pos).getPhone() + " clicked!", Toast.LENGTH_SHORT).show();
if (theContactsList.get(pos).getSelected()) {
theContactsList.get(pos).setSelected(false);
} else {
theContactsList.get(pos).setSelected(true);
}
}
});
}
}
The onClick in the Adapter works as planned for individual checkboxes; their state is preserved. It's just when I try to select all of them with the button in the activity that the problem arises.
The simplest answer by far is to simply call notifyDataSetChanged() on your adapter after you make all of your setSelected() calls.
From your code, I can't tell if PopulistoContactsAdapter is an adapter instance, or if PopulistoContactsAdapter.theContactsList is a static variable... you'll need an adapter instance to call notifyDataSetChanged(). If you only have a reference to the RecyclerView, you can call myRecyclerView.getAdapter().notifyDataSetChanged().
Related
how to checkbox without button and if the user does not check something is going return null.
How I called data from the check box
I called the data from the adapter with using a statement.
is send the data to interface with layout position
I called all layout data in my loop
I converted string to double to get sum all value is checked.
This is my task with using the button. I called this from the adapter.
I declared checkbox in my adapter:
holder.setItemCheckBox(new CheckboxListener() {
#Override
public void respond(View v, int position) {
CheckBox chk = (CheckBox) v;
double checkedSum = 0;
//Check if checked or not
if(chk.isChecked()){
checkdata.add(catalogDatabases.get(position));
}else if(!chk.isChecked()){
checkdata.remove(catalogDatabases.get(position));
}
}
});
This is my interface :
public interface CheckboxListener {
void respond (View v, int position);
}
I have my adapters with image buttons and so. When pressing the sixth one it does as suppose to do. This is the last time it was updated correctly.
But then it keeps moving up the list doing the same thing. Now this
#Override
public void onClick(View view) {
TimeCard card = MyAdapter.cards.get(position);
switch (view.getId()) {
case R.id.playButton:
startTimer(card);
///new Logger(TimeCardButton.class).debug("Play button was pressed");
break;
case R.id.editButton:
Intent intent = new Intent(context, TimeCardAdd.class);
intent.putExtra("cardPosition", position);
context.startActivity(intent);
//TODO: Finish the editing so we can modify the timer card
Toast.makeText(context, "Edit button has been pressed.", Toast.LENGTH_SHORT).show();
break;
case R.id.stopButton:
stopTimer(card);
break;
case R.id.pauseButton:
pauseTimer(card);
break;
}
}
Is called only once. Which is correct. But this is called every second from the UI update call
private void sendPlayTimeButtons() {
cardButtons.get(TimeCardButtonId.PLAY_BUTTON.getId()).setVisibility(View.INVISIBLE);
cardButtons.get(TimeCardButtonId.EDIT_BUTTON.getId()).setVisibility(View.INVISIBLE);
cardButtons.get(TimeCardButtonId.PAUSE_BUTTON.getId()).setVisibility(View.VISIBLE);
cardButtons.get(TimeCardButtonId.STOP_BUTTON.getId()).setVisibility(View.VISIBLE);
// logger.debug("Sending Play Buttons");
}
Here's the code for my BindViewHolder on the Adapter
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
//TODO: add everything back
holder.playButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.editButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.pauseButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.stopButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
}
And last but finally the code for my ViewHolder inside the adapter
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView timerTitle;
public TextView timeRemaining;
ImageButton playButton;
ImageButton editButton;
ImageButton pauseButton;
ImageButton stopButton;
LinkedList<ImageButton> buttons = new LinkedList<>();
public MyViewHolder(final View view) {
super(view);
timerTitle = view.findViewById(R.id.titleCardName);
timeRemaining = view.findViewById(R.id.timeLeftTextCard);
playButton = view.findViewById(R.id.playButton);
editButton = view.findViewById(R.id.editButton);
pauseButton = view.findViewById(R.id.pauseButton);
stopButton = view.findViewById(R.id.stopButton);
buttons.add(playButton);
buttons.add(editButton);
buttons.add(pauseButton);
buttons.add(stopButton);
}
}
Here's the startTimer function which I have tested and it's called only once.
private void startTimer(TimeCard card) {
new Logger(TimeCardButton.class).debug("Play button was pressed");
if (!card.isTimeStarted()) {
card.setTimeStarted(true);
sendPlayTimeButtons();
logger.info("Starting Timer!");
} else if(card.isTimerPaused() && card.isTimeStarted()) {
TimerTask.notifyUpdate();
card.setTimerPaused(false);
sendPlayTimeButtons();
logger.info("Resuming from being paused!");
}
}
By all of them one at a time by my task system. My Task system only sends updates to the recycler from the activity handling the UI calls...
Again On those images, the buttons will move up the list every second having no reason. I tried replacing the button ids with tags. But that still failed.
You need to have a separate array in your adapter having the track of the list playing/paused/stopped. Let us consider the following.
0 -> Stopped
1 -> Playing
2 -> Paused
Now take an array in your adapter like the following.
// This initializes the array with the number of elements of your list
// and all initialized by 0 to indicate primarily all tracks were not playing.
int[] trackPlayer = new int[cards.size];
Now when you are clicking the buttons to do some action you need to update the array with the action as well.
#Override
public void onClick(View view) {
TimeCard card = MyAdapter.cards.get(position);
switch (view.getId()) {
case R.id.playButton:
startTimer(card);
trackPlayer[position] = 1; // Playing
break;
case R.id.editButton:
Intent intent = new Intent(context, TimeCardAdd.class);
intent.putExtra("cardPosition", position);
context.startActivity(intent);
//TODO: Finish the editing so we can modify the timer card
Toast.makeText(context, "Edit button has been pressed.", Toast.LENGTH_SHORT).show();
break;
case R.id.stopButton:
stopTimer(card);
trackPlayer[position] = 0; // Stopped
break;
case R.id.pauseButton:
pauseTimer(card);
trackPlayer[position] = 2; // Paused
break;
}
}
Now inside your onBindViewHolder, you need to change the button's visibility based on the trackPlayer values.
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.playButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.editButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.pauseButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
holder.stopButton.setOnClickListener(new TimeCardButton(context, holder.getAdapterPosition(), holder.buttons).checkStatus());
// Set the buttons visibility changes here.
if(playTrack[position] == 1) {
// Item in this position is being played
playButton.setVisibility(View.INVISIBLE);
editButton.setVisibility(View.INVISIBLE);
pauseButton.setVisibility(View.VISIBLE);
stopButton.setVisibility(View.VISIBLE);
} else if(playTrack[position] == 0) {
// Item in this position is not being played/stopped
playButton.setVisibility(View.VISIBLE);
editButton.setVisibility(View.VISIBLE);
pauseButton.setVisibility(View.INVISIBLE);
stopButton.setVisibility(View.INVISIBLE);
} else if(playTrack[position] == 2) {
// Item in this position is paused
playButton.setVisibility(View.VISIBLE);
editButton.setVisibility(View.INVISIBLE);
pauseButton.setVisibility(View.INVISIBLE);
stopButton.setVisibility(View.VISIBLE);
}
}
And remove the sendPlayTimeButtons function call from your startTimer function. I think you might consider removing the sendPlayTimeButtons function as well.
private void startTimer(TimeCard card) {
new Logger(TimeCardButton.class).debug("Play button was pressed");
if (!card.isTimeStarted()) {
card.setTimeStarted(true);
notifyDataSetChanged(); // Call notifyDataSetChanged instead here.
} else if(card.isTimerPaused() && card.isTimeStarted()) {
TimerTask.notifyUpdate();
card.setTimerPaused(false);
notifyDataSetChanged(); // Call notifyDataSetChanged instead here.
}
}
Hope you get the idea.
I have two recyclerview in one layout. The vertical recyclerview shows all the user and the horizontal recyclerview shows the selected user from vertical recyclerview. The vertical recyclerview has a checkbox(clickable=false) to know that the user picks it and to do that I created an interface for vertical recyclerview, an ItemCheck and for the horizontal I created an interface of onItemClick. So my plan is when user click in vertical recyclerview it will add the item on the horizontal view and when the user unCheck it, the item will be remove to the horizontal recyclerview. And in the horizontal recyclerview, when the user clicks the item, the item will be remove in the selected user and in the vertical recyclerview it will uncheck the checkbox. This is the code for doing that.
EDIT:
In the Activity:
private void setUpAdapter() {
mUsersAdapter = new PickMemberAdapter(PickMemberActivity.this, mUserNameList,
mUserDescList, mUserPicList, new PickMemberAdapter.RecyclerViewItemClick() {
#Override
public void OnItemCheckClickListener(PickMemberAdapter.UsersViewHolders holder,
String name, int position) {
String userKey = mUserKey.get(position);
if (!holder.mCheckBox.isChecked()) {
holder.mCheckBox.setChecked(true);
mSelectedUser.add(userKey);
mSelectedName.add(name);
} else {
holder.mCheckBox.setChecked(false);
mSelectedUser.remove(userKey);
mSelectedName.remove(name);
}
mUsersAdapter.notifyDataSetChanged();
mSelectedAdapter.notifyDataSetChanged();
Toast.makeText(PickMemberActivity.this, mSelectedUser.toString()
+ "\n" + mSelectedName.toString(), Toast.LENGTH_LONG).show();
}
});
mSelectedAdapter = new SelectedUserAdapter(mSelectedName,
new SelectedUserAdapter.RecyclerViewUnselect() {
#Override
public void ItemRemoveClick(String name, int position) {
String userKey = mUserKey.get(position);
mSelectedUser.remove(userKey);
mSelectedName.remove(name);
mUsersAdapter.notifyDataSetChanged();
mSelectedAdapter.notifyDataSetChanged();
Toast.makeText(PickMemberActivity.this, mSelectedUser.toString()
+ "\n" + mSelectedName.toString(), Toast.LENGTH_LONG).show();
}
});
mSearchList.setAdapter(mUsersAdapter);
mSelectedUserList.setAdapter(mSelectedAdapter);
}
In the Adapter
public void onBindViewHolder(#NonNull final UsersViewHolders holder, int position) {
holder.setName(mUserNameList.get(position));
holder.setDesc(mUserDescList.get(position));
holder.setImage(mUserPicList.get(position));
holder.mCheckBox.setOnCheckedChangeListener(null);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.OnItemCheckClickListener(holder,
mUserNameList.get(holder.getAdapterPosition()), holder.getAdapterPosition());
}
});
}
public interface RecyclerViewItemClick {
void OnItemCheckClickListener(UsersViewHolders holder, String name, int position);
}
Now my problem is the checkbox is either not checking or checking another position.
It should look like this (from Messenger Lite app)
You are updating data of RecyclerView so you should notify adapter. Call notifyDataSetChanged() after you update list.
mSearchList.notifyDataSetChanged();
mSelectedUserList.notifyDataSetChanged();
I solved this problem to maintain separate boolean for check and uncheck in user model and user it onBindViewHolder to update view.
I have a ListView with rows with different layouts. So I'm using the pattern of ViewHolder.
If the user clicks on a row, one sub-layout of the same row must be shown/hidden.
viewHolder.btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (int) v.getTag();
Log.d(TAG, "Line in position " + position + " clicked");
if (!checkBoxSendChoice[position]) {
checkBoxSendChoice[position] = true;
viewHolder.layout_choice.setVisibility(View.VISIBLE);
} else {
checkBoxSendChoice[position] = false;
viewHolder.layout_choice.setVisibility(View.GONE);
}
}
});
However I noticed that the entire ListView is refreshed (getView is called multiple times for all rows), because of setVisibility(). If I comment out the two setVisibility() instructions, the ListView isn't refreshed anymore.
Is it possible to optimize and avoid refreshing all the views in the ListView?
I think there is a better way of doing this. Instead of editing the view directly, you should have a Boolean isVisible inside the list item and change that, then notify the adapter that an item has changed. This will make the holder re-bind to the item. And inside the holder's bind function you can set the view's visibility depends on the boolean. Here is a rough example (half pseudo code):
List<MyItem> items;
viewHolder.btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (int) v.getTag();
Log.d(TAG, "Line in position " + position + " clicked");
checkBoxSendChoice[position] != checkBoxSendChoice[position];
items.get(position).isVisible = heckBoxSendChoice[position];
adapter.notifyItemRangeChanged(position, 1);
}
});
class MyItem {
boolean isVisible = true;
}
class holder {
View layout_choice;
private void onBind(MyItem item) {
if (item.isVisible) {
layout_choice.setVisibility(View.VISIBLE);
} else {
layout_choice.setVisibility(View. GONE);
}
}
}
By notifying the adapter with notifyItemRangeChanged, the adapter will know what items have been update and therefore will only refresh them.
If you want i'll be happy to edit my answer with a working tested example. Hope this helps!
I have a RecyclerView with each element representing an event. I want to let the user select events by clicking it. Once selected, the event(s) and a report button will be colored:
UI before performing a click: click here.
UI After performing a click: click here.
It's pretty simple and allegedly works; I set an OnClickListener for each ViewHolder which is responsible for coloring the item, and when fired it's triggering another event in the owning activity named onOccurrenceSelected, which is responsible for changing the button's state.
However, when scrolling through the RecyclerView's items, other irrelevant items are colored like their OnClickListener was triggered (though it wasn't), and when scrolling back the selected event is colored as not selected. While this is happening, the only event that's supposed to color the items is not triggered.
Any explanation for such behavior? Thanks!
EDIT: Here are some relevant code from the adapter:
private List<Occurrence> mDataSet;
private Activity activity;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!occurrence.isSelected()) {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
} else {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Recyclerview always resuse views while scrolling so you have to store selected positions into temporary arraylist and then keep condition check into onBindViewHolder that whether that particular position is already exists in arraylist or not? I updated your adaper. find the below changes with comment
private List<Occurrence> mDataSet;
private Activity activity;
//Added here temporary ArrayList
private ArrayList<String> mSelectedPosition = new ArrayList<String>;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//Set ViewTag
holder.itemView.setTag(position);
//Check everyposition during view binding process
if(mSelectedPosition.contains(String.valueOf(position))){
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}else{
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Get Position
int position = (int) view.getTag();
//Remove SelectedPosition if Already there
if(mSelectedPosition.contains(position))
mSelectedPosition.remove(String.valueOf(position));
else
mSelectedPosition.add(String.valueOf(position));
notifyDataSetChanged();
//Not sure about this lines
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Its the default behaviour of recyclerview. it will recycle/reuse views which are not in use currently. If you want to save the state which is colored or not. Then save a parameter in your List<Object> per position. and as per position in onBindViewHolder method use that position to change the color.
Try by Setting Tag to your item in onBindViewHolder of Adapter
holder.yourItem.setTag(position);
And then Inside the onClickListener,Just save that position in shared Pref. if it's selected, whenever you set adapter then before setting values just check that is it selected or not based on shared Pref. and perform action for same.
public void onClick(View view) {
if (!occurrence.isSelected()) {
//save position in share pref.
}
}