RecyclerView is messed up when scrolling - java

As you can see in the screenshot, my project contains a RecyclerView (for categories of food) which contains more RecyclerViews (for the ingredients). But iv'e got a problem, my RecyclerView is messing up the order. I debuged the project and the parameters are just fine but the RecyclerView is displaying them wrong. As you can see in the picture, Fruits ingredients are displayed in the Dairy category.
IngredientSectionAdapter.Java
(the main adapter,which contain more RecyclerViews)
class SectionViewHolder extends RecyclerView.ViewHolder {
private TextView sectionBtn;
private RecyclerView itemRecyclerView;
public SectionViewHolder(View itemView) {
super(itemView);
sectionBtn = (TextView) itemView.findViewById(R.id.text_category);
itemRecyclerView = (RecyclerView) itemView.findViewById(R.id.ingredientsRecycler);
}
}
private Context context;
private ArrayList<IngredientSectionModel> sectionModelArrayList;
ArrayList<IngredientItemAdapter> adapters;
public IngredientSectionAdapter(Context context, ArrayList<IngredientSectionModel> sectionModelArrayList) {
this.context = context;
this.sectionModelArrayList = sectionModelArrayList;
adapters = new ArrayList<IngredientItemAdapter>();
}
#Override
public SectionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.parent_list,null);
return new SectionViewHolder(v);
}
#Override
public void onBindViewHolder(SectionViewHolder holder, int position) {
final IngredientSectionModel sectionModel = sectionModelArrayList.get(position);
holder.itemRecyclerView.setTag(holder.itemRecyclerView.getVisibility());
final RecyclerView sectionList = holder.itemRecyclerView;
holder.sectionBtn.setText(sectionModel.getSectionLabel());
//recycler view for items
holder.itemRecyclerView.setHasFixedSize(true);
holder.itemRecyclerView.setNestedScrollingEnabled(false);
/* set layout manager on basis of recyclerview enum type */
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3,1);
adapters.add(new IngredientItemAdapter(context, sectionModel.getItemArrayList()));
int resId = R.anim.grid_layout_animation_from_bottom;
//LayoutAnimationController animation = AnimationUtils.loadLayoutAnimation(context, resId);
holder.itemRecyclerView.setLayoutManager(staggeredGridLayoutManager);
holder.itemRecyclerView.setAdapter(adapters.get(position));
//holder.itemRecyclerView.setLayoutAnimation(animation);
//toggle visibilty of inner RecyclerView (ingredients, not categories)
holder.sectionBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (sectionList.getVisibility() == View.VISIBLE){
sectionList.setVisibility(View.GONE);
}
else
{
sectionList.setVisibility(View.VISIBLE);
}
}
});
}
What can cause this?

Every time onBindViewHolder is called you create a new IngredientItemAdapter and add it to your adapters, and then you call holder.itemRecyclerView.setAdapter(adapters.get(position)). However, adapters.get(position) is not the adapter you just created. Your adapter will get bigger and bigger. Try this
IngredientItemAdapter adapter = adapters.get(position);
adapter.setIngredients(sectionModel.getItemArrayList());
holder.itemRecyclerView.setAdapter(adapter);

Related

How to avoid RadioGroup checked twice in RecyclerView

I've noticed RecyclerView "recycle" the views in the adapter but i want stop RecyclerView adapter duplicate the checked action in another item of the RecyclerView.
I have 10 items drawed into a RecyclerView, each one of them have a RadioGroup with 2 RadioButton within, but when i fired the check in the first item, for example, the number ten item have a checked too.
I was reading this question but i could'nt got it work.
Using Radio button with recyclerview in android
How to avoid this?
My adapter:
...
public class PreguntaAdapter extends RecyclerView.Adapter<PreguntaAdapter.ViewHolder> {
private Context context;
private ArrayList<VistaEncuestaActivity.PreguntasSiNo> preguntas;
public ArrayList<Respuestas> respuestas = new ArrayList<Respuestas>();
public PreguntaAdapter(Context context, ArrayList<VistaEncuestaActivity.PreguntasSiNo> preguntas) {
this.context = context;
this.preguntas = preguntas;
}
public ArrayList<Respuestas> getCheckedItems() {
return respuestas;
}
#NonNull
#Override
public PreguntaAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
Context context = viewGroup.getContext();
LayoutInflater layoutInflater = LayoutInflater.from(context);
View v = layoutInflater.inflate(R.layout.item_pregunta_sino, viewGroup, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull final PreguntaAdapter.ViewHolder viewHolder, int i) {
final VistaEncuestaActivity.PreguntasSiNo currentPregunta = preguntas.get(i);
//viewHolder.pregunta.setText(currentEncuesta.getString_pregunta());
viewHolder.pregunta.setText(currentPregunta.getQuestion());
viewHolder.myLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
viewHolder.setIsRecyclable(false);
}
#Override
public int getItemCount() {
return preguntas.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public SharedPreferences prefs;
private LinearLayout myLinearLayout;
public TextView pregunta;
public RadioGroup radio_group;
public ViewHolder(#NonNull View itemView) {
super(itemView);
pregunta = (TextView) itemView.findViewById(R.id.pregunta);
radio_group = (RadioGroup) itemView.findViewById(R.id.radio_group_pregunta);
prefs = (SharedPreferences) context.getSharedPreferences("logged", Context.MODE_PRIVATE);
myLinearLayout = (LinearLayout) itemView.findViewById(R.id.myLinearLayout);
}
}
}
Thanks all of you for your responses. Unfortunally any response helped me to solve this issue, i used this function and i added the getItemViewType function to my adapter and it's working well now:
#Override
public int getItemViewType(int position) {
return position;
}
And going to the official google docs i could find the next information:
Return the view type of the item at position for the purposes of view recycling. The default implementation of this method returns 0, making the assumption of a single view type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter
This make sense at the time RecyclerView "recycle" the views but when this is use it in the adapter this have a different behavior treating each view as unique.
Thank you to all of you.
RadioGroup.getCheckedRadioButtonId() is the way to go. In conjunction with a SparseArray<Integer>.
In onBindViewHolder():
On each RadioGroup, set a RadioGroup.OnCheckedChangeListener(). Add the checked state to the a SparseArray or Map<Integer,Integer> mapping the index of the item position to the updated value in RadioGroup.OnCheckedChangeListener() onCheckedChanged.
So your onBindViewHolder would look something like this:
private SparseArray<Integer> mMapping = new SparseArray<>();
public void onBindViewHolder(#NonNull final PreguntaAdapter.ViewHolder viewHolder,final int position) {
final VistaEncuestaActivity.PreguntasSiNo currentPregunta = preguntas.get(i);
//viewHolder.pregunta.setText(currentEncuesta.getString_pregunta());
viewHolder.pregunta.setText(currentPregunta.getQuestion());
viewHolder.myLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
viewHolder.radio_group.setOnCheckedChangeListener(new OnCheckedChangeListener(){
void onCheckedChanged(RadioGroup group,int checked) {
mMapping.put(position,checked);
}
}};
// Check to see if a previous checked value exists and restore.
if(mMapping.get(position) != null && mMapping.get(position) != -1){
radio_group.check(mMapping.get(position));
}
viewHolder.setIsRecyclable(false);
}
onCheckedChanged(RadioGroup group, int checkedId)
You should give each RadioButton a unique ID, like radioButton_1, radioButton_2 in your layout file.

Android RecyclerView only displays items after closing and restarting the app

I have a strange effect with my RecyclerView after I updated my App to API 28. I have a search button that triggers a search of Wifi devices in my area. The items are no longer displayed directly in the list. I have to close and reopen the app to get the correct display.
I have a toast that should be displayed when the button is pressed. The toast message is also only displayed after closing and opening the app.
Activity.kt
private fun setFloatingSearchButton() {
floating_button_discover_devices.setOnClickListener { view ->
Snackbar.make(view, "Search for new devices ...",
Snackbar.LENGTH_SHORT).setAction("Action", null).show()
discoverDevices()
registerReceiver(mWifiReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
mWifiManager!!.startScan()
}
}
WifiAdapter.java
public class WifiAdapter extends RecyclerView.Adapter<WifiAdapter.ViewHolder> {
private final int CONFIGURE_DEVICE_REQUEST_CODE = 0x00000001;
private static final String TAG = "WifiAdapter";
private List<DeviceWifiTo> wifis;
private Context context;
private Activity activity;
public WifiAdapter(List<DeviceWifiTo> wifis) {
this.wifis = wifis;
}
#Override
public WifiAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
activity = (Activity) context;
LayoutInflater inflater = LayoutInflater.from(context);
View wifiView = inflater.inflate(R.layout.item_wifi, parent, false);
return new WifiAdapter.ViewHolder(wifiView);
}
#Override
public void onBindViewHolder(WifiAdapter.ViewHolder holder, int position) {
DeviceWifiTo wifi = wifis.get(position);
Log.d(TAG, wifi.getSsid());
TextView wifiName = holder.wifiName;
wifiName.setText(wifi.getSsid());
CardView cardView = holder.cardView;
cardView.setOnClickListener(view -> {
Intent intent = new Intent(context, ConfigureDeviceActivity.class);
intent.putExtra("ssid", wifi.getSsid());
activity.startActivityForResult(intent, CONFIGURE_DEVICE_REQUEST_CODE);
});
}
#Override
public int getItemCount() {
return wifis.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView wifiName;
CardView cardView;
ViewHolder(View itemView) {
super(itemView);
wifiName = itemView.findViewById(R.id.text_wifi_name);
cardView = itemView.findViewById(R.id.cardView_device);
}
}
}
UcDiscoverDevicesImpl.java
private void createRecyclerView(List<DeviceWifiTo> list) {
RecyclerView recyclerView = activity.findViewById(R.id.recycler_view_wifi);
WifiAdapter adapter = new WifiAdapter(list);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(activity.getApplicationContext()));
}
Set layout manager before set adapter to recycler view, Update like below
private void createRecyclerView(List<DeviceWifiTo> list) {
RecyclerView recyclerView = activity.findViewById(R.id.recycler_view_wifi);
recyclerView.setLayoutManager(new LinearLayoutManager(activity.getApplicationContext()));
WifiAdapter adapter = new WifiAdapter(list);
recyclerView.setAdapter(adapter);
}
Hope it helps you.
I guess you want to show your result in recyclerView. Do you have any callback methods for Your search?
You need to call notifyDataSetChanged() in that callback method (set result then call notifyDataSetChanged() )
Try setting up layoutmanager before setting the adapter
I had similar issue in kotlin just because of not adding the notifyDataSetChanged() where I am setting the data.

Integrating recyclerview with database

#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
Collections.swap(queuee, viewHolder.getAdapterPosition(), target.getAdapterPosition());
// and notify the adapter that its dataset has changed
adapterr.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
With the above code i m able to swap the items in the recyclerview ,but now the problem is i also want to make the same respective changes to database.
How to go about?
queuee receives the all the data from the database
queuee is a arraylist which is used to set the recyclerview adapter
Regards
Thanks
Adapter:
public class QueueAdapter extends RecyclerView.Adapter<QueueAdapter.MyViewHolder> {
private Context mContext;
public ArrayList<Music> queue;
MediaPlayer mPlayer = new MediaPlayer();
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView title,tt,artist;
public Button play,stop;
public ImageButton option;
public MyViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.songtitle1);
artist = (TextView)view.findViewById(R.id.artist1);
// play = (Button) view.findViewById(R.id.play);
// stop = (Button) view.findViewById(R.id.stop);
// tt = (TextView) view.findViewById(R.id.song_name);
option = (ImageButton) view.findViewById(R.id.option1);
}
}
public QueueAdapter(Context mContext, ArrayList<Music> queue) {
this.mContext = mContext;
this.queue = queue;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.queue_card, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
Music mu1 = queue.get(position);
holder.title.setText(mu1.getTitle());
holder.artist.setText(mu1.getArtist());
final String link =mu1.getUrl();
final String SongName = mu1.getTitle();
}
#Override
public int getItemCount() {
return queue.size();
}
public void removeItem(int position) {
queue.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, queue.size());
}
}
activity:
recyclerView = (RecyclerView) findViewById(R.id.recyclerqueue);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
adapterr = new QueueAdapter(this,queuee);
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
// adapterr.
recyclerView.setAdapter(adapterr);
initSwipe();
In my opinion, you can do it on OnDestroy method in your Activity. Specifically speaking, firstly, you can get all your data from your adapter, and then delete datas in your database, at last, you can save your data obtained from your adapter again. In this moment, the sequence of your datas is what you want, and this way is easy to code. I'd rather you don't
update your database during onMove, because it's too frequently during your drag, and operates all data is easier than two datas in database.

Handle recyclerview click in a fragment instead of holder class

I have a fragment that displays a RecyclerView. I am currently handling the onclick event in my holder class like this:
public class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageView categoryImageView;
public TextView categoryNameTextView;
public TextView categoryAmountTextView;
ArrayList<Category> categoriesArrayList = new ArrayList<>();
Context context;
public CategoryHolder(View itemView, Context context, ArrayList<Category> categories) {
super(itemView);
this.categoriesArrayList = categories;
this.context = context;
itemView.setOnClickListener(this);
this.categoryImageView = (ImageView) itemView.findViewById(R.id.categoryImageView);
this.categoryNameTextView = (TextView) itemView.findViewById(R.id.categoryNameTextView);
this.categoryAmountTextView = (TextView) itemView.findViewById(R.id.categoryAmountTextView);
}
#Override
public void onClick(View v) {
int position = getAdapterPosition();
Category category = this.categoriesArrayList.get(position);
Toast.makeText(context, ""+category.getCategoryName() + position,
}
}
As you can see I have implemented OnClickListener in the holder class and then setTheOnItemClickListner in the Holder.
In my Adapter I pass the holder class the the arraylist with the data like this:
Context context;
ArrayList<Category> categories;
public CategoriesAdapter(Context context, ArrayList<Category> categories) {
this.context = context;
this.categories = categories;
}
#Override
public CategoryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.categorylist_singlecell, parent, false);
CategoryHolder holder = new CategoryHolder(v, context, categories);
return holder;
}
I am passing the data when creating the new holder.
This works fine and I can toast the position and any of the data from the ArrayList that is used for the RecyclerView.
What I want to do though is use that in the main fragment where I initialise the RecyclerView and adapter.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_nested_youtube_video_selection, container, false);
//////////////////////////// CATEGORIES RECYCLER /////////////////////////////////////
// Initialise the categories Recylerview
LinearLayoutManager categoriesLayoutManger = new LinearLayoutManager(getActivity());
categoriesLayoutManger.setOrientation(LinearLayoutManager.HORIZONTAL);
categoriesRecyclerView = (RecyclerView) view.findViewById(R.id.youtubeAlarmCategoryRecyclerView);
categoriesRecyclerView.setLayoutManager(categoriesLayoutManger);
// Get the data for the categories RecyclerView
categoryArraylist = CategoryCollection.getCategoryArrayList();
// Initialse the categories RecyclerView Adapter
categoriesAdapter = new CategoriesAdapter(getActivity(), categoryArraylist);
// Bind the categories Adapter to the categories RecyclerView
categoriesRecyclerView.setAdapter(categoriesAdapter);
I am thinking I need an interface but I am not sure how to do that or if that is even the best way to do it.
Following should work:
create an interface
create an instance of this interface in your fragment and pass it on to the adapter
let the adapter pass this interface on to the view holder
let the view holder define an onClickListener that passes the event on to the interface
Here's an example:
Interface
public interface ItemClickListener {
void onItemClicked(ViewHolder vh, Object item, int pos);
}
public interface GenericItemClickListener<T, VH extends ViewHolder> {
void onItemClicked(VH vh, T item, int pos);
}
ViewHolder
public class CategoryHolder extends RecyclerView.ViewHolder {
public CategoryHolder(View itemView, Context context) {
super(itemView);
this.context = context;
this.categoryImageView = (ImageView) itemView.findViewById(R.id.categoryImageView);
this.categoryNameTextView = (TextView) itemView.findViewById(R.id.categoryNameTextView);
this.categoryAmountTextView = (TextView) itemView.findViewById(R.id.categoryAmountTextView);
}
}
Adapter
Context context;
ArrayList<Category> categories;
ItemClickListener itemClickListener;
public CategoriesAdapter(Context context, ArrayList<Category> categories, ItemClickListener itemClickListener) {
this.context = context;
this.categories = categories;
this.itemClickListener = itemClickListener;
}
// DON'T PASS YOUR DATA HERE, just create a ViewHolder
#Override
public CategoryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.categorylist_singlecell, parent, false);
CategoryHolder holder = new CategoryHolder(v, context);
return holder;
}
//HERE you bind one item of your list to the view holder
#Override
public void onBindViewHolder(final CategoryHolder vh, final int i) {
final Category category = categories.get(i);
// set click listener
vh.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
itemClickListener.onItemClicked(vh, category, i);
}
});
// update images and texts...
}
Edit
Updated the code to show you, that you should not pass a array of items to every view holder... The view holder gets created and reused, only update the view holder in onBindViewHolder and bind the view holder instance to the correct data there...
Btw, I would make a generic interface instead, that's even more beautiful... My example is just a simple solution...
Edt 2 - How to create the interface instance
ItemClickListener listener = new ItemClickListener(){
#Override
public void onItemClicked(RecyclerView.ViewHolder vh, Object item, int pos){
Toast.makeText(getActivity(), "Item clicked: " + pos, Toast.LENGTH_SHORT).show();
}
};
abstract YOURAdapter class then add method like
void onLikeClicked(Object object, int position);
void onItemClicked();
call it in bindView as simply like
holder.btnLike.setOnclickListner(new OnClickListner){
onLikeClicked(list.get(position), position);
}
In Activity Class
now it will automatically implements all methods of adapter
YOURAdapter adapter=new YOURAdapter(){
void onLikeClicked(Object object, int position){
}
void onItemClicked(){
}
}

Android Recycler View select only one image and show tick mark on selected image

I need a solution of how to do the following thing using Android Recycler View.
I have Multiple Images and i need to select only one image like how the radio button works. Now i can select all the images that i have, but i need to restrict the current working behavior to work like the radio button (E.g.) If one is selected the other images that i have should not be selected.
I have tried with the below code but no luck for me. Can anyone rectify the mistake and make the code workable as per my need.
public class StarCountAdapter extends RecyclerView.Adapter<StarCountAdapter.StarCountHolder> {
Context context;
LayoutInflater inflater;
List<StarCount> starCounts = new ArrayList<>();
public StarCountAdapter(Context context, List<StarCount> starCounts) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.starCounts = starCounts;
}
#Override
public StarCountHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.star_count_row,parent,false);
return new StarCountHolder(view);
}
#Override
public void onBindViewHolder(StarCountHolder holder, int position) {
StarCount model = starCounts.get(position);
Picasso.with(context)
.load("http://"+model.getImagePath())
.into(holder.starImage);
holder.actorName.setText(model.getName());
holder.counts.setText(""+model.getCount());
}
#Override
public int getItemCount() {
return starCounts.size();
}
public class StarCountHolder extends RecyclerView.ViewHolder {
ImageView starImage;
TextView actorName,counts;
StarCount modelCount;
public StarCountHolder(View itemView) {
super(itemView);
starImage = (ImageView) itemView.findViewById(R.id.starCountIv);
actorName = (TextView) itemView.findViewById(R.id.acterName);
counts = (TextView) itemView.findViewById(R.id.counts);
}
}
}
help needed to solve this issue since i am struck up for more than a Day. Share thoughts to rectify my Error in the code and rectify my error.
public int selectedPosition = -1
public class StarCountHolder extends RecyclerView.ViewHolder {
ImageView starImage;
TextView actorName,counts;
StarCount modelCount;
public StarCountHolder(View itemView) {
super(itemView);
starImage = (ImageView) itemView.findViewById(R.id.starCountIv);
actorName = (TextView) itemView.findViewById(R.id.acterName);
counts = (TextView) itemView.findViewById(R.id.counts);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectedPosition = getLayoutPosition());
notifyDatasetChanged();
}
});
}
}
Now you are done with the selected item position, change it in your binder
#Override
public void onBindViewHolder(StarCountHolder holder, int position) {
StarCount model = starCounts.get(position);
Picasso.with(context)
.load("http://"+model.getImagePath())
.into(holder.starImage);
holder.actorName.setText(model.getName());
holder.counts.setText(""+model.getCount());
if(selectedPosition == position){
// do whatever you want to do to make it selected.
}
}
Now to get the selected item in your activity, you can do something like this...
inside activity
StartCount startC = starCounts.get(adapter.selectedPosition);
hope it helps

Categories

Resources