Data before scrolling
Data after scrolling
The problem with my app is shown in the pictures above.
After entering the data, if i scroll after adding the item as scrollable, the data disappears.
As a further explanation, sometimes the entered data appears in other items that have been added.
To explain the app, it is an exercise recording app, and it uses a multi-type recycler view.
I used ListAdapter and also DiffUtil. And the picture is related to the Detail item.
TextWatcher was used to save the entered data.
I've been searching a lot to solve this.
The two most searched solutions are here
using getItemViewType(), getItemId()
->I used this method as shown in this link, but it didn't solve the problem.
Using setIsRecyclable(false) inside the holder
-> This method worked. But I heard that setIsRecyclable(false) is a function that does not recycle.
If i use this, isn't it a good way to do it because there is no advantage to using RecyclerView?
RoutineAdapter.java
public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {
Context context;
RoutineListAdapter.OnRoutineItemClickListener routinelistener;
RoutineListAdapter.OnRoutineAddClickListener routineAddListener;
final static int TYPE_ROUTINE = 1;
final static int TYPE_ROUTINE_DETAIL = 2;
final static int TYPE_ROUTINE_FOOTER = 3;
public RoutineListAdapter(#NonNull DiffUtil.ItemCallback<Object> diffCallback) {
super(diffCallback);
}
// add routine interface
public interface OnRoutineAddClickListener {
public void onAddRoutineClick();
}
public void setOnAddRoutineClickListener(RoutineListAdapter.OnRoutineAddClickListener listener) {
this.routineAddListener = listener;
}
// add/remove detail interface
public interface OnRoutineItemClickListener {
public void onAddBtnClicked(int curRoutinePos);
public void onDeleteBtnClicked(int curRoutinePos);
public void onWritingCommentBtnClicked(int curRoutinePos);
}
public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
this.routinelistener = listener;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View itemView;
if(viewType == TYPE_ROUTINE){
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
return new RoutineListAdapter.RoutineViewHolder(itemView);
}
else if(viewType == TYPE_ROUTINE_DETAIL){
itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
return new RoutineListAdapter.RoutineDetailViewHolder(itemView);
}
else {
itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
return new RoutineListAdapter.RoutineAddFooterViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Object curItem;
switch (getItemViewType(position)) {
case TYPE_ROUTINE:
curItem = getItem(position);
setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
break;
case TYPE_ROUTINE_DETAIL:
curItem = getItem(position);
RoutineDetailModel item = (RoutineDetailModel) curItem;
((RoutineListAdapter.RoutineDetailViewHolder) holder).bind(item);
((RoutineDetailViewHolder) holder).weight.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
item.setWeight(((RoutineDetailViewHolder) holder).weight.getText().toString());
}
});
break;
case TYPE_ROUTINE_FOOTER:
break;
}
}
private void setRoutineData(RoutineListAdapter.RoutineViewHolder holder, RoutineModel routineItem){
holder.routine.setText(routineItem.getRoutine());
}
public Object getRoutineItem(int position) {
if(getCurrentList() == null || position < 0 || position >= getCurrentList().size())
return null;
return getItem(position);
}
#Override
public int getItemCount() {
return getCurrentList().size() + 1;
}
#Override
public int getItemViewType(int position) {
if(position == getCurrentList().size()) {
return TYPE_ROUTINE_FOOTER;
}
else {
Object obj = getItem(position);
if(obj instanceof RoutineModel) {
return TYPE_ROUTINE;
}
else {
// obj instanceof RoutineDetailModel
return TYPE_ROUTINE_DETAIL;
}
}
}
private class RoutineViewHolder extends RecyclerView.ViewHolder {
public TextView routine;
public Button addSet;
public Button deleteSet;
public Button comment;
public RoutineViewHolder(#NonNull View itemView) {
super(itemView);
routine = itemView.findViewById(R.id.routine);
addSet = itemView.findViewById(R.id.add_set);
deleteSet = itemView.findViewById(R.id.delete_set);
comment = itemView.findViewById(R.id.write_comment);
addSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onAddBtnClicked(getAdapterPosition());
}
});
deleteSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onDeleteBtnClicked(getAdapterPosition());
}
});
comment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
}
});
}
}
private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
private TextView set;
private EditText weight;
public RoutineDetailViewHolder(#NonNull View itemView) {
super(itemView);
set = itemView.findViewById(R.id.set);
weight = itemView.findViewById(R.id.weight);
}
private void bind(RoutineDetailModel item) {
set.setText(item.getSet().toString() + "set");
weight.setText(item.getWeight());
}
}
private class RoutineAddFooterViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public RoutineAddFooterViewHolder(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.add_text);
ConstraintLayout regionForClick = itemView.findViewById(R.id.clickable_layout);
regionForClick.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (routineAddListener != null) {
routineAddListener.onAddRoutineClick();
}
}
});
}
}
}
UPDATED
Adapter
public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {
// detail add / remove iterface
public interface OnRoutineItemClickListener {
public void onAddBtnClicked(int curRoutinePos);
public void onDeleteBtnClicked(int curRoutinePos);
public void onWritingCommentBtnClicked(int curRoutinePos);
public void onWritingWeight(int curRoutinePos, View view); // write weight
}
public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
if(this.routinelistener != null)
this.routinelistener = null;
this.routinelistener = listener;
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Object curItem;
switch (getItemViewType(position)) {
case TYPE_ROUTINE:
curItem = getItem(position);
setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
break;
case TYPE_ROUTINE_DETAIL:
((RoutineListAdapter.RoutineDetailViewHolder) holder).bind();
break;
case TYPE_ROUTINE_FOOTER:
break;
}
}
private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
private TextView set;
private EditText weight;
public RoutineDetailViewHolder(#NonNull View itemView) {
super(itemView);
set = itemView.findViewById(R.id.set);
weight = itemView.findViewById(R.id.weight);
weight.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
routinelistener.onWritingWeight(getAdapterPosition(), itemView);
}
});
}
private void bind() {
RoutineDetailModel item = (RoutineDetailModel) getItem(getAdapterPosition());
set.setText(item.getSet().toString() + "set");
weight.setText(item.getWeight()); // Setting the saved value
}
}
Activity
public class WriteRoutineActivity extends AppCompatActivity implements WritingCommentDialogFragment.OnDialogClosedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_write_routine);
initViews();
setPageTitle(getIntent());
setRoutineRecyclerview();
diffUtil2 = new RoutineDiffUtil2();
listAdapter = new RoutineListAdapter(diffUtil2);
items = new ArrayList<>();
routine_rv.setAdapter(listAdapter);
listAdapter.setOnRoutineClickListener(new RoutineListAdapter.OnRoutineItemClickListener() {
#Override
public void onWritingWeight(int curRoutinePos, View v) {
RoutineDetailModel item = (RoutineDetailModel) listAdapter.getRoutineItem(curRoutinePos);
EditText weight = v.findViewById(R.id.weight);
item.setWeight(weight.getText().toString()); // This is saved to set the value again when recycled.
});
}
}
If you need any other extra code, please tell me
The issue lies with your TextWatcher that you're adding in onBindViewHolder.
At the moment you have it setup so that every time the RecyclerView binds a view (which can happen multiple times per actual view), you're adding a new TextWatcher and then also setting the text to the item's weight, which is then triggering the previous watchers you added, setting the item's weight to something else, in this case an empty string.
What you should be doing is either removing all listeners before you add another one, or add the listener in onCreateViewHolder and use the holder's adapter position to get your item.
Here is some pseudocode to clarify my suggestions:
Adding the listener in onCreateViewHolder
RoutineDetailViewHolder {
private EditText weight;
RoutineDetailViewHolder {
weight.addTextChangedListener {
items[adapterPosition].setWeight(...)
}
}
}
Removing the listeners before binding again:
RoutineDetailViewHolder {
private EditText weight;
private TextWatcher weightWatcher;
void bind() {
weight.removeTextChangedListener(weightWatcher);
weightWatcher = new TextWatcher();
weight.addOnTextChangedListener(weightWatcher);
}
}
Related
I have attached a picture to help you understand my problem.
I'm making an Workout app.
When the button is pressed, the routine is added
In the added routine, it can use the button again to add detailed data (set, lbs, reps).
So, I am using a two type RecyclerView (except footer).
I used DiffUtil to update the items being added.
I found a strange phenomenon while experimenting for testing. (See photo)
It wasn't an error, so I can't figure out what's wrong with it.
After entering the data, when i added a number of detailed items, the value of the data entered first appeared in the items added later.
And if i scrolled after many items were added, the data moved randomly.
However, what is expected is this phenomenon when you add a lot of items or scroll.
What's wrong?
RoutineAdapter.java
public class RoutineAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final static int TYPE_ROUTINE = 1;
final static int TYPE_ROUTINE_DETAIL = 2;
final static int TYPE_ROUTINE_FOOTER = 3;
private Context context;
private List<Object> mItems = new ArrayList<>();
OnRoutineItemClickListener routinelistener;
OnRoutineAddClickListener routineAddListener;
public void updateRoutineList(List<Object> newRoutineList) {
final RoutineDiffUtil diffCallback = new RoutineDiffUtil(this.mItems, newRoutineList);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
this.mItems.clear();
this.mItems.addAll(newRoutineList);
diffResult.dispatchUpdatesTo(this);
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View itemView;
if(viewType == TYPE_ROUTINE){
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
return new RoutineViewHolder(itemView);
}
else if(viewType == TYPE_ROUTINE_DETAIL){
itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
return new RoutineDetailViewHolder(itemView);
}
else {
itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
return new RoutineAddFooterViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Object obj;
switch (getItemViewType(position)) {
case TYPE_ROUTINE:
obj = mItems.get(position);
setRoutineData((RoutineViewHolder) holder, (RoutineModel) obj);
break;
case TYPE_ROUTINE_DETAIL:
obj = mItems.get(position);
RoutineDetailModel item = (RoutineDetailModel) obj;
((RoutineDetailViewHolder) holder).setDetailItem(item);
break;
case TYPE_ROUTINE_FOOTER:
break;
}
}
#Override
public int getItemCount() {
if(mItems == null)
return -1;
return mItems.size() + 1; // for footer
}
#Override
public int getItemViewType(int position) {
if(position == mItems.size()) {
return TYPE_ROUTINE_FOOTER;
}
else {
Object obj = mItems.get(position);
if(obj instanceof RoutineModel) {
return TYPE_ROUTINE;
}
else {
// obj instanceof RoutineDetailModel
return TYPE_ROUTINE_DETAIL;
}
}
}
// add routine interface
public interface OnRoutineAddClickListener {
public void onAddRoutineClick();
}
public void setOnAddRoutineClickListener(OnRoutineAddClickListener listener) {
this.routineAddListener = listener;
}
// details add / remove interface
public interface OnRoutineItemClickListener {
public void onAddBtnClicked(int curRoutinePos);
public void onDeleteBtnClicked(int curRoutinePos);
public void onWritingCommentBtnClicked(int curRoutinePos);
}
public void setOnRoutineClickListener(OnRoutineItemClickListener listener) {
this.routinelistener = listener;
}
private class RoutineViewHolder extends RecyclerView.ViewHolder {
public TextView routine;
public Button addSet;
public Button deleteSet;
public Button comment;
public RoutineViewHolder(#NonNull View itemView) {
super(itemView);
initViews();
addSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onAddBtnClicked(getAdapterPosition());
}
});
deleteSet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onDeleteBtnClicked(getAdapterPosition());
}
});
comment.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
}
});
}
}
private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
public TextView set;
public TextView weight;
public RoutineDetailViewHolder(#NonNull View itemView) {
super(itemView);
initViews();
}
private void initViews() {
set = itemView.findViewById(R.id.set);
weight = itemView.findViewById(R.id.weight);
}
private void setDetailItem(RoutineDetailModel item) {
set.setText(item.getSet().toString() + "set");
}
}
}
RoutineDiffUtil.java
public class RoutineDiffUtil extends DiffUtil.Callback {
private List<Object> oldRoutineList;
private List<Object> newRoutineList;
public RoutineDiffUtil(List<Object> oldRoutineList, List<Object> newRoutineList) {
this.oldRoutineList = oldRoutineList;
this.newRoutineList = newRoutineList;
}
#Override
public int getOldListSize() {
return oldRoutineList.size();
}
#Override
public int getNewListSize() {
return newRoutineList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Object oldObj = oldRoutineList.get(oldItemPosition);
Object newObj = newRoutineList.get(newItemPosition);
if (oldObj instanceof RoutineModel && newObj instanceof RoutineModel) {
return ((RoutineModel) oldObj).id == ((RoutineModel) newObj).id;
}
else if (oldObj instanceof RoutineDetailModel && newObj instanceof RoutineDetailModel) {
return ((RoutineDetailModel) oldObj).id == ((RoutineDetailModel) newObj).id;
}
else if(oldObj instanceof RoutineModel && newObj instanceof RoutineDetailModel) {
return false;
}
else {
return false;
}
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return (oldRoutineList.get(oldItemPosition)).equals(newRoutineList.get(newItemPosition));
}
}
RoutineDetailModel.java
public class RoutineDetailModel {
public int id;
private int set = 1;
private int weight;
public RoutineDetailModel() {
Random random = new Random();
this.id = random.nextInt();
}
public RoutineDetailModel(int set) {
Random random = new Random();
this.id = random.nextInt();
this.set = set+1;
}
public Integer getSet() {
return set;
}
public int getId() {
return id;
}
#Override
public int hashCode() {
return Objects.hash(set, weight);
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj != null && obj instanceof RoutineDetailModel) {
RoutineDetailModel model = (RoutineDetailModel) obj;
if(this.id == model.getId()) {
return true;
}
}
return false;
}
}
First of all, you should use ListAdapter class with diff util. The problem with your adapter is that, Recycler view recycles views again and again. That is to say that when you entered a text for your first item, this item is used for other views. To solve it after any text change you should keep this text in your model class then you should set this text to the field in onBind() method. To sum up, recycler view uses same view for different items so any data related to any item should be kept in a model and the model should be set to the view in onBind().
I have recyclerview with the buttons inside of items. When I click one button in one of them I want to hide buttons in others items. I know how to do it in same item where I clicked button, but I no have idea how to do it in other things without touching them. How to do it? I tried with the boolean in the ViewHolder, but it doesn't work.
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ListViewHolder> {
private List<Task> tasks;
private boolean visible;
public class ListViewHolder extends RecyclerView.ViewHolder {
private TextView nameTextView, idTextView;
private Button travelButton, travellingButton, workButton, stopButton;
public ListViewHolder(#NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.name_text_view);
idTextView = itemView.findViewById(R.id.id_text_view);
travelButton = itemView.findViewById(R.id.travel_button);
travellingButton = itemView.findViewById(R.id.travelling_button);
workButton = itemView.findViewById(R.id.work_button);
stopButton = itemView.findViewById(R.id.stop_button);
if (visible == true) {
travelButton.setVisibility(View.GONE);
}
else {
travelButton.setVisibility(View.VISIBLE);
}
}
}
#NonNull
#Override
public ListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
return new ListViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ListAdapter.ListViewHolder holder, final int position) {
Task task = tasks.get(position);
holder.nameTextView.setText(task.getName());
holder.idTextView.setText(String.valueOf(task.getId()));
holder.travelButton.setVisibility(View.VISIBLE);
holder.travelButton.setText(R.string.start_travel);
holder.travellingButton.setText(R.string.travelling);
holder.travelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.travelButton.setVisibility(View.GONE);
holder.travellingButton.setVisibility(View.VISIBLE);
holder.workButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFECB3"));
visible = true;
}
});
holder.workButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.workButton.setText(R.string.working);
holder.travelButton.setVisibility(View.GONE);
holder.travellingButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFCDD2"));
}
});
holder.stopButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.travelButton.setText(R.string.start_travel);
holder.travelButton.setVisibility(View.VISIBLE);
holder.travellingButton.setVisibility(View.GONE);
holder.workButton.setText(R.string.work);
holder.workButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.GONE);
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
visible = false;
}
});
}
#Override
public int getItemCount() {
return tasks == null ? 0 : tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
}
Ok, I found the answer to my question. I just made an interface that informs activity about the need to refresh the adapter and it works.
ListAdapter:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ListViewHolder> {
private List<Task> tasks;
private int travelButtonVisibility=-1;
private OnButtonClickListener mOnButtonClickListener;
public void setOnButtonClickListener(OnButtonClickListener onButtonClickListener) {
mOnButtonClickListener = onButtonClickListener;
}
public class ListViewHolder extends RecyclerView.ViewHolder {
private TextView nameTextView, idTextView;
private Button travelButton, travellingButton, workButton, stopButton;
public ListViewHolder(#NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.name_text_view);
idTextView = itemView.findViewById(R.id.id_text_view);
travelButton = itemView.findViewById(R.id.travel_button);
travellingButton = itemView.findViewById(R.id.travelling_button);
workButton = itemView.findViewById(R.id.work_button);
stopButton = itemView.findViewById(R.id.stop_button);
}
}
#NonNull
#Override
public ListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
return new ListViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ListAdapter.ListViewHolder holder, final int position) {
final Task task = tasks.get(position);
holder.nameTextView.setText(task.getName());
holder.idTextView.setText(String.valueOf(task.getId()));
holder.travelButton.setText(R.string.start_travel);
holder.travellingButton.setText(R.string.travelling);
if (travelButtonVisibility <= 0) {
holder.travelButton.setVisibility(View.VISIBLE);
}
else if (position == travelButtonVisibility){
holder.travelButton.setVisibility(View.GONE);
holder.travellingButton.setVisibility(View.VISIBLE);
holder.workButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFECB3"));
}
holder.travelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mOnButtonClickListener.onTravelButtonClick();
travelButtonVisibility = position;
holder.travelButton.setVisibility(View.GONE);
}
});
holder.workButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.workButton.setText(R.string.working);
holder.travellingButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFCDD2"));
}
});
holder.stopButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.travelButton.setText(R.string.start_travel);
holder.travellingButton.setVisibility(View.GONE);
holder.workButton.setText(R.string.work);
holder.workButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.GONE);
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
travelButtonVisibility = -1;
notifyDataSetChanged();
}
});
}
#Override
public int getItemCount() {
return tasks == null ? 0 : tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
public interface OnButtonClickListener {
void onTravelButtonClick();
}
}
MainActvity:
private void initList() {
listAdapter = new ListAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(listAdapter);
recyclerView.setHasFixedSize(true);
listAdapter.setTasks(tasks);
listAdapter.setOnButtonClickListener(new ListAdapter.OnButtonClickListener() {
#Override
public void onTravelButtonClick() {
recyclerView.setAdapter(listAdapter);
Log.e("MainActivity", "Travel Button");
}
});
}
add this to onBindViewHolder:
holder.travellingButton.setVisibility(visible? View.VISIBLE : View.GONE);
and after changing "visible" call
notifyItemRangeChanged(0, getItemCount())
Try something like this instead:
public class ListViewHolder extends RecyclerView.ViewHolder {
private optionSelected = 0
public ListViewHolder(#NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.name_text_view);
idTextView = itemView.findViewById(R.id.id_text_view);
travelButton = itemView.findViewById(R.id.travel_button);
travellingButton = itemView.findViewById(R.id.travelling_button);
workButton = itemView.findViewById(R.id.work_button);
stopButton = itemView.findViewById(R.id.stop_button);
if (visible == true) {
travelButton.setVisibility(View.GONE);
}
else {
travelButton.setVisibility(View.VISIBLE);
}
}
}
#NonNull
#Override
public ListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
return new ListViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ListAdapter.ListViewHolder holder, final int position) {
Task task = tasks.get(position);
if(option == 0 && position == 0){
holder.nameTextView.setText(task.getName());
holder.idTextView.setText(String.valueOf(task.getId()));
holder.travelButton.setVisibility(View.VISIBLE);
holder.travelButton.setText(R.string.start_travel);
holder.travellingButton.setText(R.string.travelling);
} else if(option == 1 && position == 1){
// do Something with your view
} else {
// do something with your view
}
holder.travelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
option = 1
notifyDataChanged()
}
});
holder.workButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
option = 2
notifyDataChanged()
}
});
holder.stopButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
option = 3
notifyDataChanged()
}
});
}
#Override
public int getItemCount() {
return tasks == null ? 0 : tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
}
You're setting different visibility depending on which button is clicked. I recommend using an enum instead of a boolean to store the state of your list and update the state on each button click.
Define an enum in your Adapter and create a member variable using it:
private List<Task> task;
private ListState state = ListState.showButton1; // Set a default value
// I used "showButton" but this can be any set a values that represent your various states
private enum ListState{
showButton1, showButton2, showButton3
}
Inside of your onBindViewHolder:
// Set the state of the view holder
switch(state){
case showButton1:
// Set your corresponding visibility changes on your view holder here
break;
case showButton2:
// Set your corresponding visibility changes on your view holder here
break;
case showButton3:
// Set your corresponding visibility changes on your view holder here
break;
}
// Make sure to update the adapter whenever a button is clicked
holder.button1.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
// Set the state and notify the adapter
state = ListState.showButton2;
notifyDataSetChanged();
}
})
// Add click listeners for your remaining buttons
// ...
Please try below code, it will initially display all travellingButton and when you click travellingButton of one of the list item, travellingButton of other items will hide.
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ListViewHolder> {
private List<Task> tasks;
private int travelButtonVisibility=-1;
public class ListViewHolder extends RecyclerView.ViewHolder {
private TextView nameTextView, idTextView;
private Button travelButton, travellingButton, workButton, stopButton;
public ListViewHolder(#NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.name_text_view);
idTextView = itemView.findViewById(R.id.id_text_view);
travelButton = itemView.findViewById(R.id.travel_button);
travellingButton = itemView.findViewById(R.id.travelling_button);
workButton = itemView.findViewById(R.id.work_button);
stopButton = itemView.findViewById(R.id.stop_button);
}
}
#NonNull
#Override
public ListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
return new ListViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ListAdapter.ListViewHolder holder, final int position) {
Task task = tasks.get(position);
holder.nameTextView.setText(task.getName());
holder.idTextView.setText(String.valueOf(task.getId()));
holder.travelButton.setText(R.string.start_travel);
holder.travellingButton.setText(R.string.travelling);
if(travelButtonVisibility==-1 || travelButtonVisibility==position){
{
holder.travelButton.setVisibility(View.VISIBLE);
}else{
holder.travelButton.setVisibility(View.GONE);
}
holder.travelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.travelButton.setVisibility(View.GONE);
holder.travellingButton.setVisibility(View.VISIBLE);
holder.workButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFECB3"));
travelButtonVisibility=position
notifyDataSetChanged();
}
});
holder.workButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.workButton.setText(R.string.working);
holder.travelButton.setVisibility(View.GONE);
holder.travellingButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(Color.parseColor("#FFCDD2"));
}
});
holder.stopButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.travelButton.setText(R.string.start_travel);
holder.travelButton.setVisibility(View.VISIBLE);
holder.travellingButton.setVisibility(View.GONE);
holder.workButton.setText(R.string.work);
holder.workButton.setVisibility(View.GONE);
holder.stopButton.setVisibility(View.GONE);
holder.itemView.setBackgroundColor(Color.TRANSPARENT);
visible = false;
}
});
}
#Override
public int getItemCount() {
return tasks == null ? 0 : tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
}
Hope this will help.
The problem is in onBindViewHolder, where I am trying to set the text of my buttons depending on a condition.
Whatever I try it seems that only the first if statement is evaluated (none of the else-if or else ones) and I only get the buttons that match it, all other button texts are set to null.
Here is my custom RecyclerViewAdapter
public class ParallelRecyclerViewAdapter
extends RecyclerView.Adapter<ParallelRecyclerViewAdapter.ParallelViewHolder> {
private int listItemLayout;
private ArrayList<ParallelClusterItem> itemListForAdapter;
public ParallelRecyclerViewAdapter(int layoutId, ArrayList<ParallelClusterItem> inputList) {
listItemLayout = layoutId;
itemListForAdapter = inputList;
}
#NonNull
#Override
public ParallelViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(listItemLayout, parent, false);
return new ParallelViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ParallelViewHolder holder, int position) {
Button button = holder.year;
String oldPhotoYear = itemListForAdapter.get(position).getmOldPhotoYear();
String relativeYearFrom = itemListForAdapter.get(position).getmRelativeYearFrom();
if (oldPhotoYear != null) {
button.setText(oldPhotoYear);
} else if (relativeYearFrom != null) {
button.setText(relativeYearFrom);
} else {
button.setText("unknown");
}
}
#Override
public int getItemCount() {
return itemListForAdapter == null ? 0 : itemListForAdapter.size();
}
public class ParallelViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
Button year;
ParallelViewHolder(View itemView) {
super(itemView);
year = itemView.findViewById(R.id.row_item);
year.setOnClickListener(this);
}
#Override
public void onClick(View v) {
int pos = getAdapterPosition();
ParallelClusterItem item = itemListForAdapter.get(pos);
Intent intent = new Intent(ParallelMapFragmentActivity.instance, ImageSliderActivity.class);
intent.putExtra("path", item.getPath());
ParallelMapFragmentActivity.instance.startActivity(intent);
}
}
}
i'm trying to find how can i change my RecyclerView adapter textViews from Activity, in my activity i have two widgets such as increment_text_size and decrement_text_size which they must change adapter textviews,
for achieve to that, i create simple listener on activity to manage them:
Activity:
public interface IonChangeBookContentTextSize {
void incrementTextSize();
void decrementTextSize();
}
public static void setIonChangeBookContentTextSize(IonChangeBookContentTextSize l) {
ionChangeBookContentTextSize = l;
}
and after click on widgets i use this listener on adapter
Activity:
#OnClick(R.id.decrement_text_size)
public void decrement_text_size(View view) {
if (ionChangeBookContentTextSize != null) {
ionChangeBookContentTextSize.decrementTextSize();
}
}
#OnClick(R.id.increment_text_size)
public void increment_text_size(View view) {
if (ionChangeBookContentTextSize != null) {
ionChangeBookContentTextSize.incrementTextSize();
}
}
now in adapter i'm using this listener
public class ShowBookContentsAdapter extends RecyclerView.Adapter<ShowBookContentsAdapter.ShowBookContentsViewHolder> {
private List<Contents> list;
private Context context;
private static final int NOTE = 1;
public static IonChangeBottomViewVisibility ionChangeBottomViewvisibility;
private ShowBookContentsViewHolder holder;
private View view;
public ShowBookContentsAdapter(List<Contents> items, Context mContext, IonChangeBottomViewVisibility mOnChangeBottomViewVisibility) {
list = items;
context = mContext;
ionChangeBottomViewvisibility = mOnChangeBottomViewVisibility;
}
#Override
public ShowBookContentsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int layout = -1;
switch (viewType) {
case 0:
layout = R.layout.item_book_content_paragraph;
break;
case 1:
layout = R.layout.item_book_content_heading_one;
break;
}
view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
holder = new ShowBookContentsViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(ShowBookContentsViewHolder holder, final int position) {
switch (list.get(position).getContentType()) {
case 0:
implementingHeadingParagraphView(holder, position);
break;
case 1:
implementingHeadingOneView(holder, position);
break;
}
}
private void implementingHeadingParagraphView(final ShowBookContentsViewHolder holder, final int position) {
Utils.overrideFonts(context, holder.book_content_paragraph, PersianFontType.SHABNAM);
holder.book_content_paragraph.setText(Html.fromHtml(list.get(position).getContent()));
ActivityShowBookContent.setIonChangeBookContentTextSize(new ActivityShowBookContent.IonChangeBookContentTextSize() {
#Override
public void incrementTextSize() {
holder.book_content_paragraph.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
}
#Override
public void decrementTextSize() {
holder.book_content_paragraph.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
}
});
}
#Override
public int getItemViewType(int position) {
return list.get(position).getContentType();
}
#Override
public int getItemCount() {
return list.size();
}
public int getItemPosition(int itemId) {
return itemPositions.get(itemId);
}
public class ShowBookContentsViewHolder extends RecyclerView.ViewHolder {
#Nullable
#BindView(R.id.book_content_paragraph)
TextView book_content_paragraph;
#Nullable
#BindView(R.id.book_content_heading_one)
TextView book_content_heading_one;
public ShowBookContentsViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
}
implementing this listener as :
ActivityShowBookContent.setIonChangeBookContentTextSize(new ActivityShowBookContent.IonChangeBookContentTextSize() {
#Override
public void incrementTextSize() {
holder.book_content_paragraph.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
}
#Override
public void decrementTextSize() {
holder.book_content_paragraph.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
}
});
on implementingHeadingParagraphView method work for current position, not for all rows on recyclerview adapter, how can i fix this problem?
You do not have to create a listener for this purpose. You should hold a field named textSize in your adapter. Then, set this whenever you want from your activity.
public class ShowBookContentsAdapter extends RecyclerView.Adapter<ShowBookContentsAdapter.ShowBookContentsViewHolder> {
private int textSize;
// constructor etc.
#Override
public ShowBookContentsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_book_content_paragraph, parent, false);
final ShowBookContentsViewHolder holder new ShowBookContentsViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(ShowBookContentsViewHolder holder, final int position) {
implementingHeadingParagraphView(holder, position);
}
private void implementingHeadingParagraphView(final ShowBookContentsViewHolder holder, final int position) {
Utils.overrideFonts(context, holder.book_content_paragraph, PersianFontType.SHABNAM);
holder.book_content_paragraph.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
holder.book_content_paragraph.setText(Html.fromHtml(list.get(position).getContent()));
}
public void setTextSizes(int textSize) {
this.textSize = textSize;
notifyDataSetChanged();
}
//... other adapter methods
public class ShowBookContentsViewHolder extends RecyclerView.ViewHolder {
#Nullable
#BindView(R.id.book_content_paragraph)
TextView book_content_paragraph;
#Nullable
#BindView(R.id.book_content_heading_one)
TextView book_content_heading_one;
public ShowBookContentsViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
call this from your activity
showBookContentsAdapter.setTextSizes(18);
You have to call notifydatasetchanged from you activity
1.First, save the font size on constant variable if temporary or use shared preferences if need in whole life cycle of app
Make a method in your activity to save font size
private void saveFontSize(boolean isFont){
IS_LARGE_FONT= isFont;
recyclerView.post(new Runnable(){
adapter.notifyDataSetChanged();
});
}
In your adapter class just check that value in bindholder
if(IS_LARGE_FONT)
{
//set large font
}
else{
// set small font
}
I want to click an item in recyclerView programmatically, I found a way do that:
recyclerView.findViewHolderForAdapterPosition(0).itemView.performClick();
but it doesn't work for me, the findViewHolderForAdapterPosition just returns null.
Did I miss something in my code?
HistoryListAdapter:
public class HistoryListAdapter extends RecyclerView.Adapter<HistoryListAdapter.ViewHolder> {
private static ArrayList<RecordItem> recordItems;
private static FragmentActivity activity;
private static RecordList recordList;
public HistoryListAdapter(ArrayList<RecordItem> recordItems, FragmentActivity FA, RecordList FRL) {
this.recordItems = recordItems;
this.activity = FA;
this.recordList = FRL;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.recorditem, parent, false);
return new ViewHolder(itemLayoutView);
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
// TextView setText ...
}
// Return the size of the itemsData (invoked by the layout manager)
#Override
public int getItemCount() {
return recordItems.size();
}
// inner class to hold a reference to each item of RecyclerView
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView result, datetime;
public ViewHolder(View itemLayoutView) {
super(itemLayoutView);
itemLayoutView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (activity != null) {
// do something...
}
}
});
result = (TextView) itemLayoutView.findViewById(R.id.result);
heartrate = (TextView) itemLayoutView.findViewById(R.id.heartrate);
datetime = (TextView) itemLayoutView.findViewById(R.id.datetime);
}
}
}
RecordList:
RecyclerView listView = (RecyclerView) layoutView.findViewById(R.id.listView);
HistoryListAdapter listadapter = new HistoryListAdapter(itemsToShow, getActivity(), RecordList.this);
listView.swapAdapter(listadapter, false);
listView.findViewHolderForAdapterPosition(0).itemView.performClick();
I omitted some code, But it should not affect the overall structure of my code.
According to the official documentation:
if notifyDataSetChanged() has been called but the new layout has not
been calculated yet, this method will return null since the new
positions of views are unknown until the layout is calculated.
It's not safe to use findViewHolderForAdapterPosition().
While you call this method after listView.swapAdapter(listadapter, false); you'll always get a null as result because notifyDataSetChanged() will be called.
you can do this:
listView.postDelayed(new Runnable()
{
#Override
public void run()
{
if(listView.findViewHolderForAdapterPosition(0)!=null )
{
listView.findViewHolderForAdapterPosition(0).itemView.performClick();
}
}
},50);
I know that is very late, but maybe is helpful for other people in the Kotlin era:
binding.recycler.post {
val view = binding.recycler.findViewHolderForAdapterPosition(position)?.itemView?.performClick()
}
you can do this:
postAndNotifyAdapter(new Handler(),mRecyclerView);
protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView) {
handler.post(new Runnable() {
#Override
public void run() {
if ( recyclerView.findViewHolderForLayoutPosition(0)!=null) {
// This will call first item by calling "performClick()" of view.
recyclerView.findViewHolderForLayoutPosition(0).itemView.performClick();
} else {
//
postAndNotifyAdapter(handler, recyclerView);
}
}
});
}
Hey I've solved the problem this way:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private TextView mTotalSum;
protected int mSum;
protected ArrayList<BasketRVAdapter.ViewHolder> mViewHolders;
public BasketRVAdapter(JSONArray recyclerItems, Context context, TextView totalSum) {
super(recyclerItems, context);
mTotalSum = totalSum;
mViewHolders = new ArrayList<>();
}
#Override
public RecyclerView.ViewHolder onCreateSwipeViewHolder(ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.view_basket_rv_item, parent, true);
return new ViewHolder(view);
}
#Override
public void onBindSwipeViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
final BasketRVAdapter.ViewHolder holder = (BasketRVAdapter.ViewHolder) viewHolder;
JSONObject object;
try {
object = mRecyclerItems.getJSONObject(position);
final int priceValue = object.getInt("price");
final int quantityValue = object.getInt("quantity");
holder.setId(object.getInt("id"));
holder.title.setText(object.getString("title"));
holder.price.setText(String.format(mContext.getString(R.string.price), priceValue));
holder.quantity.setText(String.valueOf(quantityValue));
holder.sum.setText(String.valueOf(priceValue * quantityValue));
holder.quantity.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
String value = holder.quantity.getText().toString();
if (value.equals("")) {
return;
}
if (value.equals("0")) {
Toast.makeText(mContext, R.string.null_value_error, Toast.LENGTH_SHORT).show();
return;
}
holder.sum.setText(String.valueOf(priceValue * Integer.valueOf(value)));
setTotalSum();
}
});
mViewHolders.add(holder);
mSum += priceValue * quantityValue;
if (position == getItemCount() - 1) {
mTotalSum.setText(String.format(mContext.getString(R.string.total_sum), mSum));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public SwipeConfiguration onCreateSwipeConfiguration(Context context, int i) {
return new SwipeConfiguration.Builder(context)
.setLeftSwipeBehaviour(SwipeConfiguration.SwipeBehaviour.NORMAL_SWIPE)
.setRightSwipeBehaviour(SwipeConfiguration.SwipeBehaviour.NO_SWIPE)
.setLeftBackgroundColorResource(R.color.dt_basket_deleted)
.setLeftUndoDescription(R.string.deleted)
.setDescriptionTextColorResource(R.color.dt_white)
.setLeftUndoable(true)
.build();
}
#Override
public void onSwipe(int i, int i1, RecyclerView.ViewHolder holder) {
try {
if (i1 == SWIPE_LEFT) {
remove(i);
notifyItemRemoved(i);
mViewHolders.remove(i);
setTotalSum();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
public int getSum() {
return mSum;
}
public void setTotalSum() {
int totalSum = 0;
for (BasketRVAdapter.ViewHolder holder : mViewHolders) {
totalSum += Integer.valueOf(holder.sum.getText().toString());
}
mSum = totalSum;
mTotalSum.setText(String.format(mContext.getString(R.string.total_sum), totalSum));
}
public static class ViewHolder extends RecyclerView.ViewHolder {
#Bind(R.id.trade_title)
public TextView title;
#Bind(R.id.trade_price)
public TextView price;
#Bind(R.id.trade_quantity)
public EditText quantity;
#Bind(R.id.sum)
public TextView sum;
private int mId;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public int getId() {
return mId;
}
public void setId(int id) {
mId = id;
}
}}
Added ArrayList mViewholders field inside class
added each viewholder instance to mViewHolders arraylist inside onBindSwipeViewHolder method
used mViewholders arraylist instead of findViewHolderForadapterposition method inside setTotalSum method
I wouldnt recommend using post delay. Although it works the timing is arbitrary.
Using OnPreDrawListener on the recyclerview, timing will be aligned with the next draw cycle and visible items are accessible.