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);
}
Related
I have a dataset I use to create a recycler view and the elements in it have a "solved" Boolean value. When the recyclerview is initialized everything displays correctly. When I scroll through slowly for the most part the corresponding image is displayed correctly but a few of the images blink in and out of view. If I scroll very fast then everything essentially gets messed up.
private class RegularCrimeHolder extends CrimeHolder implements View.OnClickListener{
private Crime mCrime;
private TextView mTitleTextView;
private TextView mDateTextView;
public RegularCrimeHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_crime, parent, false));
mTitleTextView = this.itemView.findViewById(R.id.crime_title);
mDateTextView = this.itemView.findViewById(R.id.crime_date);
mSolved = this.itemView.findViewById(R.id.solved);
itemView.setOnClickListener(this);
}
//Optional
public void bind(Crime crime){
mCrime = crime;
mTitleTextView.setText(mCrime.getTitle());
mDateTextView.setText(mCrime.getDate().toString());
mSolved.setVisibility(crime.isSolved()? View.VISIBLE : View.GONE);
}
#Override
public void onClick(View view) {
//Toast toast = Toast.makeText(getActivity(), mCrime.getTitle() + " clicked!", Toast.LENGTH_SHORT);
//toast.show();
Intent intent = CrimeViewPagerActivity.newIntent(mCrime.getID(), getContext(), CrimeLab.GetIndex(mCrime));
startActivityForResult(intent, REQUEST_CRIME);
}
}
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder>{
private List<Crime> mCrimes;
public CrimeAdapter(List<Crime> crimes){
mCrimes = crimes;
this.setHasStableIds(true);
}
#Override
public long getItemId(int position){
return position;
}
#Override
public CrimeHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
if(viewType == 0)
return new RegularCrimeHolder(layoutInflater, viewGroup);
else
return new BadCrimeHolder(layoutInflater, viewGroup);
}
#Override
public int getItemViewType(int position) {
if(mCrimes.get(position).RequiresPolice())
return 1;
else
return 0;
}
#Override
public void onBindViewHolder(#NonNull CrimeHolder crimeHolder, int i) {
Crime crime = mCrimes.get(i);
crimeHolder.bind(crime);
}
#Override
public int getItemCount() {
return mCrimes.size();
}
}
Don't worry! Cool! In your adapter, just do the below things
Kotlin
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
Java
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Your sample code lacks detail. Post the full adapter viewholder and item layout code.
But if I had to guess here, your layout is messed up and your onViewRecycled isn't cleaning up correctly.
For example, say a viewholder was just used and the visibility was set to visible. Now when this viewholder is recycled and re bound, what you'll see is a flicker. Because the view in question is visible from that last bind and it needs to hide itself in this bind.
What you want to do is inside your onViewRecycled, you want to set the visibility set to gone so that when viewholders are reused, there are no flickers.
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.
I am using the following code in Adapter class to show data in a RecyclerView, but now I would like to show data from some other ArrayList in a same RecyclerView (at some positions like: 1st position and 6th position) using different layout.
That different layout (assume: another_layout.xml) contains 2 TextViews and an Image, also want to implement click on listener for that layout too..
#Override
public PlaylistCardAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// inflate a card layout
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.youtube_video_card, parent, false);
// populate the viewholder
ViewHolder vh = new ViewHolder(v);
return vh;
}
RecyclerView for more than one layout
1. Override getItemViewType(int position) method
e.g I have two layouts layout1 and layout2.I want layout1 at the top and then layout2. So getItemViewType would be something like this
#Override
public int getItemViewType(int position) {
if(position == 0){ //for layout1
return 1;
}else{
return 2; //for layout2
}
}
2. Different viewholder for each layout like this
class ViewHolder_LayoutOne extends RecyclerView.ViewHolder{
TextView name;
//Constructor
}
class ViewHolder_LayoutTwo extends RecyclerView.ViewHolder{
TextView name;
//Constructor
}
3. Inflate different layouts according to the position
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh = null;
if(viewType == 1){
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_layout_one,parent,false);
vh = new MyViewHolder_LayoutOne(view);
}else if(viewType == 2){
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_layout_two,parent,false);
vh = new MyViewHolder_LayoutTwo(view);
}
return vh;
}
4. Bind your views as per the position
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch(getItemViewType(position)){
case 1: //for layout 1
((ViewHolder_LayoutOne)holder).name.setText("");
break;
case 2:// for layout 2
((ViewHolder_LayoutTwo)holder).name.setText("");
break;
}
}
5.Now Most important getItemCount() method //return the number of views
#Override
public int getItemCount() {
return toptitles.length + 1;
// as i have only layout at the top and the remaining size equals the length of the array toptitles.So the overall length would be
//number of views of layout1 + number of views of layout2
}
Hope this helps!!!
you can override getItemViewType, and use different ViewHolder for different layout.
You need to override getItemViewType like this :
#Override
public int getItemViewType(int position) {
Add your condition and return viewType(Constant)
}
Now, Check for viewType and retrun viewHolder accordingly.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEWHOLDER1) {
View v = LayoutInflater.from(activity).inflate(R.layout.layout1, parent, false);
return new ViewHolder1(v);
}else if(viewType == VIEWHOLDER2){
View v = LayoutInflater.from(activity).inflate(R.layout.layout2, parent, false);
return new ViewHolder2(v);
}
}
Now, Check for instance of viewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder1) {
Show values in UI accordingly.
}else if(holder instanceof ViewHolder2){
Show values in UI accordingly.
}
}
You can set the type you want in getItemViewType(int position) with the layout you want. And onCreateViewHolder you can check viewType and inflate the row accordingly.
here is an example.
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()
I'm setting a RecyclerView behaving like a list, I want a button in the bottom of the list that when clicked adds more views, I'm thinking the easier way to do it is to make the position 0 as the first one in the bottom, and increasing the position to the top, so I can add views when the the view in position 0 is clicked.
If there is a better aproach for this problem do share.
Here is my adapter:
public class AddEventsAdapter extends RecyclerView.Adapter<AddEventsAdapter.ViewHolder> {
public List<String> items = new ArrayList<>();
public void addItem(String name) {
notifyItemInserted(items.size() - 1);
items.add(name);
}
public void removeItem(int position) {
items.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, items.size());
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.add_event_item, parent, false);
return new ViewHolder(view);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setData(position);
holder.eventName.setText(i + "");
if(position == 0)
{
holder.theLayout.setBackgroundColor(Color.parseColor("#7F9099"));
holder.eventName.setText("Add");
}
}
static int i;
class ViewHolder extends RecyclerView.ViewHolder{
public TextView eventName;
public RelativeLayout theLayout;
public ViewHolder(final View itemView) {
super(itemView);
eventName = (TextView)itemView.findViewById(R.id.eventName);
theLayout = (RelativeLayout)itemView.findViewById(R.id.backgroundevent);
theLayout.setId(++i);
}
public void setData(final int position) {
theLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (position == items.size() - 1) {
addItem("");
} else {
removeItem(position);
}
}
});
}
}
}
You may notice some errors in that, I've been over it for the last 10 hours and I'm having a logic breakdown
It's solved by addind this line to the LayoutManager .setReverseLayout(true);
you can add a footer view at the end of the list and inside that you can add your button. This is the link to create a footer in recycler view https://github.com/u3breeze/android-RecyclerView-WithHeaderAndFooter. You can add the views in the normal way