I have RecyclerView with items. For that RecyclerView I'm creating my custom adapter. When I select one row I change background color of that view. The problem is when RecyclerView have lot of items, when I click on one row, the application change background color of that view, but it also change background color on another row.
My adapter code:
public class NarackiAdapter extends RecyclerView.Adapter<NarackiAdapter.MyViewHolder> {
private LayoutInflater inflater;
private List<TableItems> items = new ArrayList<>();
private List<TableItems> selected = new ArrayList<>();
public NarackiAdapter(Context context, List<TableItems> items) {
inflater = LayoutInflater.from(context);
this.items = items;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.one_item, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final TableItems item = items.get(position);
holder.tvName.setText(item.getQuantity() + " " + item.getProducts().getName());
}
#Override
public int getItemCount() {
return items.size();
}
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView tvName;
public MyViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tvName);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (getBackgroundColor(view) == R.color.primary) {
view.setBackgroundResoucre(R.color.light_gray);
selected.remove(items.get(getAdapterPosition()));
} else {
view.setBackgroundResoucre(R.color.primary);
selected.add(items.get(getAdapterPosition()));
}
}
}
}
Hello you have do the logic of changing color acording if selected or not in your onBindViewHolder() also because the recycler reuse the same view and you have to update it there. When scrolling the onBindViewHolder() will be triggered so you will need to check if view in current position is selected or not.
EDIT
Something like this:
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final TableItems item = items.get(position);
//checkIfSelected(item) should check in your selected items array if this item exist there
if(checkIfSelected(item)){
//This will be the view that you want to update background
view.setBackgroundResoucre(R.color.primary);
}else{
//This will be the view that you want to update background
view.setBackgroundResoucre(R.color.light_gray);
}
holder.tvName.setText(item.getQuantity() + " " + item.getProducts().getName());
}
RecyclerView reuses views! Therefore if you change the backgroundColor of one view, when RecyclerView reuses that view, the reuse will have the same backgroundcolor.
The way around this buggy behaviour is implementing holder changes in the bindViewHolder method because it is called every time a view is reused.
Therefore, the bindViewHolder method should evaluate data and decide how the view.
And your onClick function should edit this data and call adapter.notifyItemChanged(itemPosition) or adapter.NotifyDataChanged()
Related
Due to the fact that the ListView is not optimized enough, I decided that I would switch to the Recycler View. The first problem that hit me was this one.
My RecyclerView adapter:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private List<String> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
MyRecyclerViewAdapter(Context context, List<String> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.adapter_box, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each row
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String animal = mData.get(position);
holder.myTextView.setText(animal);
}
// total number of rows
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView myTextView;
ViewHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.text_adapter);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mData.get(id);
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
Using ListView I could do like this:
ListView listView = findViewById(R.id.testL);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (parent.getItemAtPosition(position).equals("hello")) {
TextView details = word_dialog.findViewById(R.id.word_edit_desc);
details.setText("hello");
}
}
});
How can I achieve the same result, but only with the Recycle view?:
#Override
public void onItemClick(View view, int position) {
}
I will be very grateful if you can help me!
I want to be able to click on the recycler view items in MainActivity.java, I already did it, now I need to be able to do my own actions on each line sorted using equals
ArrayList<String> animalNames = new ArrayList<>();
animalNames.add("Dog");
animalNames.add("Cow");
animalNames.add("Camel");
animalNames.add("Sheep");
animalNames.add("Goat");
// set up the RecyclerView
recyclerView = findViewById(R.id.myList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyRecyclerViewAdapter(this, animalNames);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
if (somecode.equals("Dog")){
soundPlay(MediaPlayer.create(getBaseContext(), R.raw.star));
}
if (somecode.equals("Camel")){
soundPlay(MediaPlayer.create(getBaseContext(), R.raw.tick));
}
You can define an interface in your adapter, like below
public interface ClickListener{
void onClick();
}
Implement in fragment or activity that your adapter at:
ClickListener listener = () ->{
TextView details = word_dialog.findViewById(R.id.word_edit_desc);
details.setText("hello");
}
then pass interface to adapter use constructor or setter, and you can use interface in your viewHolder when bind like below:
itemView.setOnClickListener(()->{
if (your_list.get(getAdapterPosition()).equals("hello")) {
interface_var_name.onClick()
}
});
This is Inbuild Click event of Recyclerview and another way to use Interface
holder.itemView.setOnClickListener(v ->
if (animal.equals("Your animal String"){
//Your code
}
);
if want to use interface then this is reference link
I want to populate recyclerview items when user click anywhere on the screen one by one. Eg on first touch load first item on second touch load second item on third touch load third item and so on. I have no idea how to implement this.
Here is my adapter code
Adapter
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder>{
List<MessageModel> list;
Context context;
public MessageAdapter(List<MessageModel> list, Context context) {
this.list = list;
this.context = context;
}
#NonNull
#Override
public MessageAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(context)
.inflate(R.layout.chatbubble, viewGroup, false);
ViewHolder myHolder = new ViewHolder(view);
return myHolder;
}
#Override
public void onBindViewHolder(#NonNull MessageAdapter.ViewHolder viewHolder, int i) {
MessageModel model = list.get(i);
if(model.getSender().equals("left")){
viewHolder.left.setText(model.getText());
viewHolder.right.setVisibility(View.GONE);
viewHolder.middle.setVisibility(View.GONE);
} else if(model.getSender().equals("right")){
viewHolder.right.setText(model.getText());
viewHolder.middle.setVisibility(View.GONE);
viewHolder.left.setVisibility(View.GONE);
} else {
viewHolder.middle.setText(model.getText());
viewHolder.left.setVisibility(View.GONE);
viewHolder.right.setVisibility(View.GONE);
}
}
#Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView left, right, middle;
public ViewHolder(#NonNull View itemView) {
super(itemView);
left = itemView.findViewById(R.id.message_incoming);
right = itemView.findViewById(R.id.message_outgoing);
middle = itemView.findViewById(R.id.message_middle);
}
}
}
In the Activity which holds your RecyclerView, do the following code changes.
Add an onClickListener or onTouchListener whichever you prefer to your RecyclerView.
recyclerView.setOnClickListener() or recyclerView.setOnTouchListener()
Add the code to add the items into your RecyclerView in above event listeners.
yourList.add("Your Item");
adapter.notifyItemInserted(int positionAtWhichItemWasInserted);
adapter.notifyDatasetChanged();
//For some reason I'm unable to format the above code snippet.
Make sure you call notifyItemInserted() OR notifyDatasetChanged() after you add the item in your RecyclerView list.
How can I set two text blocks in the listView, of which the first is on the left, the other on the right? I am tried to create a new layout with two textViews. But I don't know how I can connect textViews with listView and how I can set texts on textViews. May anybody help me?
I would like to have a list like this
Assuming you have the list and your layout with those 2 textview is ready just use this adapter and set this adapter to recycler view of the activity. let me know if you face any issues
public class CountryCodeAdapter extends RecyclerView.Adapter<CountryCodeAdapter.ViewHolder> {
private CountryCodeActivity activity;
ArrayList<CountryCodeModel> list = new ArrayList<>();
int selected_pos = 0;
public CountryCodeAdapter(CountryCodeActivity activity, ArrayList<CountryCodeModel> list) {
this.activity = activity;
this.list = list;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_country_listing, parent, false);
return new ViewHolder(rootView);
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, final int position) {
holder.tv_row_CountryCodeActivity_countrycode.setText(list.get(holder.getAdapterPosition()).getDial());
holder.tv_row_CountryCodeActivity_countryname.setText(list.get(holder.getAdapterPosition()).getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("country_code", list.get(holder.getAdapterPosition()).getDial());
activity.setResult(activity.RESULT_OK, intent);
activity.finish();
}
});
}
#Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tv_row_CountryCodeActivity_countryname,
tv_row_CountryCodeActivity_countrycode;
public ViewHolder(View itemView) {
super(itemView);
tv_row_CountryCodeActivity_countryname = itemView.findViewById(R.id.tv_row_CountryCodeActivity_countryname);
tv_row_CountryCodeActivity_countrycode = itemView.findViewById(R.id.tv_row_CountryCodeActivity_countrycode);
tv_row_CountryCodeActivity_countryname.setTypeface(AppClass.Lato_Regular);
tv_row_CountryCodeActivity_countrycode.setTypeface(AppClass.Lato_Regular);
}
}
}
1)You should use RecyclerView insteaad of listView, and for recyclerView you can achieve what you want with item decorator.
2)But if you have to use ListView(which is less possible case) you can do this by checking list item position and set the corresponding margin to the layout which is not recomended.
3)Also there is another way to achieve this, which is to use different layout resource xml files, but I would not use the last two variants. I would prefer the first.
I have a simple mp3 player based on RecyclerView.
There is an Adapter and ViewHolder for handling tracks in playlist.
public class TracksAdapter extends RecyclerView.Adapter<TracksAdapter.ViewHolder> {
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cell_playlist, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.trackName.setText("Track " + position);
}
#Override
public int getItemCount() {
return tracks.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
final TextView trackName;
ViewHolder(View itemView) {
super(itemView);
trackName = itemView.findViewById(R.id.trackName);
}
}
}
When I click on track I want to change background color of selected track in playlist. Problem is when I set background color to one item it's will be affected to every tenth cell in playlist.
For example if there are 100 tracks in the playlist, 10 tracks will be highlighted including the selected.
Method findViewHolderForAdapterPosition(position) returns ViewHolder with selected track.
private void setTrackColor(int position) {
RecyclerView.ViewHolder holder =
recyclerView.findViewHolderForAdapterPosition(position);
if (holder == null) return;
View item = holder.itemView;
item.setBackgroundColor(ContextCompat.getColor(main, R.color.playing));
}
You can't change the color of a view direcly. In a RecyclerView, all the Views are re-used. So, if you change the color in a position, you may indirecly change the color in other positions because same view will be re-used.
You must save the position of the currently playing track separately. This way, during onBindViewHolder, you check if current view being bind is the track currently playing. If it is the same track, apply some color. If it is not the same color, restore default color
public class TracksAdapter extends RecyclerView.Adapter<TracksAdapter.ViewHolder> {
private int mTrackPlaying = -1;
public void setTrackPlaying(int position) {
mTrackPlaying = position;
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.trackName.setText("Track " + position);
if(position == mTrackPlaying) {
holder.itemView.setBackgroundColor(ContextCompat.getColor(main, R.color.playing));
} else {
// Here, you must restore the color because the view is reused.. so, you may receive a reused view with wrong colors
holder.itemView.setBackgroundColor(ContextCompat.getColor(main, R.color.NOT_playing));
}
}
}
And then
private void setTrackColor(int position) {
TracksAdapter adapter = (TracksAdapter) recyclerView.getAdapter();
adapter.setTrackPlaying(position);
// Line below will `RecyclerView` to re-draw that position.. in other words, it will triggers a call to `onBindViewHolder`
adapter.notifyItemChanged(position);
// Reset the color of song previously playing..
adapter.notifyItemChanged(oldPosition);
}
I have a RecyclerView which for each item contains a button. I want to handle the click event for this button in the activity holding the RecyclerView instead of inside the adapter as I have heard this is the best approach.
Here is my adapter class:
public class SearchResultAdapter extends RecyclerView.Adapter<SearchResultAdapter.ViewHolder> {
private List<User> results;
public SearchResultAdapter(List<User> results) {
this.results = results;
}
#Override
public SearchResultAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_search_results_recycler_view, parent, false);
return new SearchResultAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(SearchResultAdapter.ViewHolder holder, int position) {
String fullName = results.get(position).getFirstName() +
" " + results.get(position).getLastName();
holder.mFullNameTextView.setText(fullName);
}
#Override
public int getItemCount() {
return results.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.search_full_name_text_view)
TextView mFullNameTextView;
#BindView(R.id.send_request_button)
Button mSendRequestButton;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
How should I handle the click event of the mSendRequestButton with the corresponding User object from the results list to the activity? I can think of using an interface but maybe there is a cleaner way.