I am using ion library to load images in recyclerview. But i am facing with a problem. RecyclerView is repeating items. When the image is not loaded in new item it shows previously loaded images. I need to show ProgressBar if image is not loaded. How to achieve this.
Here is my Adapter class:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private String[] links;
private Context context;
private int screen_width;
public RecyclerViewAdapter(Context context, String[] links, int screen_width) {
this.links = links;
this.context = context;
this.screen_width = screen_width;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int i) {
loadImage(i, viewHolder);
}
private void loadImage(final int pos, final ViewHolder holder) {
Ion.with(context)
.load(links[pos])
.withBitmap()
.placeholder(R.drawable.icon_download_active)
.error(R.drawable.icon_could_not_download)
.fadeIn(true)
.asBitmap()
.setCallback(new FutureCallback<Bitmap>() {
#SuppressLint("SetTextI18n")
#Override
public void onCompleted(Exception e, Bitmap result) {
if ( e != null ) {
Log.i("IMAGE_LOADER_EV","Failed to load image \n Error:"+e.toString());
} else {
int h = (screen_width * result.getHeight()) / result.getWidth();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(screen_width,h);
params.setMargins(0,0,0,dpToPx(context));
holder.image.setLayoutParams(params);
holder.image.setImageBitmap(result);
}
}
});
}
private static int dpToPx(Context context) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) 5, context.getResources().getDisplayMetrics());
}
#Override
public int getItemCount() {
return links.length;
}
class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView dimens;
ProgressBar progressBar;
ViewHolder(View view) {
super(view);
image = view.findViewById(R.id.image);
dimens = view.findViewById(R.id.dimens);
progressBar = view.findViewById(R.id.progress_bar);
}
}
}
Recycler shows previous items when the new image is not loaded.
So how to solve this problem?
Finally found solution. Just add following codes to Adapter class:
#Override
public long getItemId(int position) {
return (long) (position);
}
#Override
public int getItemViewType(int position) {
return position;
}
Use:
Ion.with(imageView)
Then remove where you manually set the image in the ImageView in the callback.
.setCallback(...)
Since you are using the callback to manage populating the ImageView, you're creating a race condition where the ImageView is loaded by multiple different items as it is recycled.
Using Ion.with(imageView) allows Ion to track the ImageViews in your RecyclerView and cancel image loads as the ImageViews get recycled.
See sample:
https://github.com/koush/ion#load-an-image-into-an-imageview
Related
My Recyclerview is filled with a list containing links to images. But the images can change (let's say the user can draw something on them), but the link to the image itself does not change. I need to make it so that when the image changes, it is also drawn again in the recycler.
notifyDataSetChanged() does not work in this case, apparently because it reacts to changes in the sheet, and in fact there are no changes in the sheet itself, the links are the same in it. But the images are changed by links.
Maybe somehow you can push the recycler so that it is updated or draws items again?
My Activity:
public class MyDrawing extends Fragment {
private RecyclerView recyclerView;
private RecyclerViewAdapter recyclerViewAdapter;
private View root;
private TextView text;
public MyDrawing() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = inflater.inflate(R.layout.fragment_my_drawing, container, false);
recyclerView = root.findViewById(R.id.recyclerViewMyDrawing);
text = root.findViewById(R.id.text);
List<CacheImageModel> cacheImageModels = getCacheImageByState();
recyclerViewAdapter = new RecyclerViewAdapter(cacheImageModels, getContext());
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
recyclerView.setAdapter(recyclerViewAdapter);
recyclerViewAdapter.notifyDataSetChanged();
return root;
}
My Adapter:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private List<CacheImageModel> cacheImageModels;
private Context context;
private String imgUrl;
private int imageKey;
private String category;
private String nameImage;
private ProgressBar progressBarItem;
public RecyclerViewAdapter(List<CacheImageModel> cacheImageModels, Context context) {
this.cacheImageModels = cacheImageModels;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
imgUrl = cacheImageModels.get(position).getImageCacheUrl();
nameImage = cacheImageModels.get(position).getName();
category = cacheImageModels.get(position).getCategory();
imageKey = cacheImageModels.get(position).getImageKey();
holder.progressBarItem.setVisibility(View.VISIBLE);
holder.relativeLayout.setLayoutParams(new FrameLayout.LayoutParams(MyApp.getScreenWidth(context) / 2,
MyApp.getScreenWidth(context) / 2));
Glide.with(context)
.load(imgUrl)
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
holder.progressBarItem.setVisibility(View.GONE);
return false;
}
})
.into(holder.image);
holder.image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int posit = holder.getAdapterPosition();
if (posit < 0 || posit >= cacheImageModels.size()) {
Log.d("POSITION", "Number postition " + posit + " its error");
} else {
String urlImagePosition = cacheImageModels.get(posit).getImageCacheUrl();
String namePosition = cacheImageModels.get(posit).getName();
String categoryPosition = cacheImageModels.get(posit).getCategory();
int imageKeyPosition = cacheImageModels.get(posit).getImageKey();
Uri uri = ContentUris.withAppendedId(MCDataContract.CONTENT_URI, imageKeyPosition);
Intent intent = new Intent(v.getContext(),
ColoringActivity.class);
intent.putExtra("urlImagePosition", urlImagePosition);
intent.putExtra("nameImage", namePosition);
intent.putExtra("keyPosition", imageKeyPosition);
intent.putExtra("categoryPosition", categoryPosition);
intent.setData(uri);
v.getContext().startActivity(intent);
}
}
});
}
#Override
public long getItemId(int position) {
return super.getItemId(position);
}
#Override
public int getItemCount() {
return cacheImageModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private ImageView image;
private ProgressBar progressBarItem;
private RelativeLayout relativeLayout;
private CardView cardView;
public ViewHolder(#NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.imageView);
progressBarItem = itemView.findViewById(R.id.progressBarItem);
relativeLayout = itemView.findViewById(R.id.parentItem);
cardView = itemView.findViewById(R.id.cardView);
}
}
}
notifyDataSetChanged() is most likely working as planned. You can set a breakpoint in your view holder binding code to check this.
It is more likely that Glide is pulling the cached versions of the images. You will need a way to get Glide to ignore what's in cache and reload from the URL.
This post addresses the same issue and may be of help to you.
I have a RecyclerView in which I have an ImageView for each item in the form of sendEmail icon. Initially, the ImageView is visible but once I click on it, I hide it using setVisibility(View.GONE) and update the Adapter.
Now, when I click on the sendEmail icon, I hide the icon in that position instantly using reportitems.get(position).setStatus("emailsent");. Now, if, before the search operation, the second item had the ImageView visible and first item did not, then after search if the second item were to be the only item that is relevant, then the ImageView does not show up there in the first position. I am using dynamic search where upon inputting a character, the adapter refreshes instantly and shows the updated RecyclerView. How can I fix this issue?
After search, even though SQ 322 should have the sendEmail icon, it does not show up
Activity code
mAdapter.setOnItemClickListener(new ReportAdapter.OnItemClickListener() {
public void onSendEmailClick(int position){
flightNumber=reportitems.get(position).getFlightNumber();
departureDate=reportitems.get(position).getDepartureDate();
FlightClosureStatus flightClosureStatus=new FlightClosureStatus(flightNumber,departureDate,"emailsent");
flightViewModel.updateFlightClosureStatus(flightClosureStatus);
reportitems.get(position).setStatus("emailsent");
mAdapter.notifyDataSetChanged();
}
}
Adapter Code
public class ReportAdapter extends RecyclerView.Adapter<ReportAdapter.ReportViewHolder> {
private ArrayList<ReportItem> reportlist;
private OnItemClickListener mListener;
private Context mContext;
public ReportAdapter(ArrayList<ReportItem> reportlist, Context context) {
this.reportlist = reportlist;
this.mContext = context;
}
public interface OnItemClickListener {
void onSendEmailClick(int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
public static class ReportViewHolder extends RecyclerView.ViewHolder {
public TextView departureDate;
public TextView flightNumber;
public ImageView emailView;
public ReportViewHolder(#NonNull View itemView, OnItemClickListener listener, Context context) {
super(itemView);
departureDate = itemView.findViewById(R.id.departureDaterecyclerview);
flightNumber = itemView.findViewById(R.id.flightnumberrecyclerview);
emailView = itemView.findViewById(R.id.sendemailIcon);
emailView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(listener != null) {
int position = getAdapterPosition();
if(position != RecyclerView.NO_POSITION) {
listener.onSendEmailClick(position);
}
}
}
});
}
}
#NonNull
#Override
public ReportViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.report_listing_item, parent, false);
ReportViewHolder rvh= new ReportViewHolder(v,mListener,mContext);
return rvh;
}
#SuppressLint("ResourceAsColor")
#Override
public void onBindViewHolder(#NonNull ReportViewHolder holder, int position) {
ReportItem currentItem = reportlist.get(position);
//here i am setting the visibility of the imageview to gone
if(currentItem.getStatus().contentEquals("emailsent")){
holder.emailView.setVisibility(View.GONE);
}
holder.flightNumber.setText(currentItem.getFlightNumber());
holder.departureDate.setText((currentItem.getDepartureDate()));
}
public List<ReportItem> getList() {
return reportlist;
}
}
Replace your onBindViewHolder with below, basically recycler uses items from pool when available, and once the item visibility is GONE and it is never set to VISIBLE again unless you do it
#SuppressLint("ResourceAsColor")
#Override
public void onBindViewHolder(#NonNull ReportViewHolder holder, int position) {
ReportItem currentItem = reportlist.get(position);
//here i am setting the visibility of the imageview to gone
if(currentItem.getStatus().contentEquals("emailsent")){
holder.emailView.setVisibility(View.GONE);
}else{
holder.emailView.setVisibility(View.VISIBLE);
}
holder.flightNumber.setText(currentItem.getFlightNumber());
holder.departureDate.setText((currentItem.getDepartureDate()));
}
I have built a note-taking app with Room, ViewModel, LiveData, and RecyclerView using this tutorial. Everything is working very well. However, when I began to implement the ability to select an image for each note, the RecyclerView became really laggy and slow as more notes with images were added.
After some research, I discovered that using image loading frameworks like Glide and Picasso can make loading faster. So, I tried using Glide to load images from a Uri into the RecyclerView items.
NoteAdapter.java
public class NoteAdapter extends ListAdapter<Note, NoteAdapter.NoteHolder> {
Context context;
public NoteAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Note> DIFF_CALLBACK = new DiffUtil.ItemCallback<Note>() {
#Override
public boolean areItemsTheSame(#NonNull Note note, #NonNull Note t1) {
return note.getId() == t1.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Note note, #NonNull Note t1) {
return note.getTitle().equals(t1.getTitle()) && note.getDescription().equals(t1.getDescription()) && note.getImageUri().equals(t1.getImageUri());
}
};
private OnItemClickListener listener;
#NonNull
#Override
public NoteHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.note_item, viewGroup, false);
return new NoteHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull NoteHolder noteHolder, int i) {
Note currentNote = getItem(i);
noteHolder.textViewTitle.setText(currentNote.getTitle());
noteHolder.textViewDescription.setText(currentNote.getDescription());
Glide.with(context)
.load(Uri.parse(currentNote.getImageUri()))
.fit()
.centerCrop()
.into(noteHolder.image);
}
public Note getNoteAt(int position) {
return getItem(position);
}
class NoteHolder extends RecyclerView.ViewHolder {
private TextView textViewTitle;
private TextView textViewDescription;
private ImageView image;
public NoteHolder(#NonNull View itemView) {
super(itemView);
textViewTitle = itemView.findViewById(R.id.text_view_title);
textViewDescription = itemView.findViewById(R.id.text_view_description);
image = itemView.findViewById(R.id.image_view);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(getItem(position));
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Note note);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
However, Android Studio gives me the following error - Cannot resolve symbol 'context'. My question is, what context should I be passing in for this argument? Also, any other suggestions to make the RecyclerView faster when loading images would be greatly appreciated. Thanks!
PS - I am not a professional whatsoever, and only program as a hobby, so I apologize for any misused terms.
you need to define global variable
Context context ;
and change your code to the following :
public class NoteAdapter extends ListAdapter<Note, NoteAdapter.NoteHolder> {
....
Context context;
#NonNull
#Override
public NoteHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
context = viewGroup.getContext();
View itemView = LayoutInflater.from(context).inflate(R.layout.note_item, viewGroup, false);
return new NoteHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull NoteHolder noteHolder, int i) {
Note currentNote = getItem(i);
noteHolder.textViewTitle.setText(currentNote.getTitle());
noteHolder.textViewDescription.setText(currentNote.getDescription());
Glide.with(context)
.load(Uri.parse(currentNote.getImageUri()))
.fit()
.centerCrop()
.into(noteHolder.image);
}
....
}
Well i tried to implement the recyclerview into Android Studio like this :
public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.ViewHolder> {
public String[] dataSet;
public Context context;
//---------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------//
public class ViewHolder extends RecyclerView.ViewHolder{
public ImageView image;
public TextView name;
public ViewHolder(View itemView) {
super(itemView);
this.image = itemView.findViewById(R.id.image);
this.name = itemView.findViewById(R.id.name);
}
public void bindData(Object data){
String extractedData = (String)data;
this.name.setText("");
this.name.setText(extractedData);
this.image.getLayoutParams().height = getRandomIntInRange(250, 100);
}
}
//---------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------//
public RecycleViewAdapter(Context context, String[] dataSet){
this.context = context;
this.dataSet = dataSet;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.food_view,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
// Resolves the messed up views after recycling.
viewHolder.setIsRecyclable(false);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.bindData(dataSet[position]);
}
#Override
public int getItemCount() {
return dataSet.length;
}
// Custom method to get a random number between a range
protected int getRandomIntInRange(int max, int min){
Random rdm = new Random();
return rdm.nextInt((max-min)+min)+min;
}
}
Somehow i noticed that once i scroll up and down the recycled views get messed up...
Heres a Picture without Scrolling :
normal
And here is one after Scrolling... as you can see totally messed up :
messed up
Why does this happen and how can i prevent this ?
Does anyone got a solution ?
Well
Adapter is calling onBindViewHolder() all times when he must recreate view on the screen. You need to call getRandomIntInRange() outside of onBindViewHolder()
You could see it:
I have few images in the json API, and I managed to fetch those images using volley Library. I used recyclerview with an image adapter to display image views vertically in two columns, but I want to make one image big and display it as the first image that user can click on. Also that image will be changed each interval of time. Basically, the API will do all the backend task like setting the time and telling which image to be displayed on the top of the recyclerview if not all images must have the same size and be shown in 2 columns vertically.
I have square layout class for recyclerview. I just want to know how I can do this. Even the concept will be fine.
you will have to create two view one for small item other for the header item and in onBindViewHolder have to bind those accordingly the following code is an example of a news article app and should help.
public class AdapterNewsArticlesListWithHeader extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<NewsArticles> items = new ArrayList<>();
private Context ctx;
private NewsArticles header;
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private OnItemClickListener mOnItemClickListener;
public interface OnItemClickListener {
void onItemClick(View view, NewsArticles obj, int position);
}
public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mOnItemClickListener = mItemClickListener;
}
// Provide a suitable constructor (depends on the kind of dataset)
public AdapterNewsArticlesListWithHeader(Context context, NewsArticles header, List<NewsArticles> items) {
this.items = items;
this.header = header;
ctx = context;
}
public class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView title;
public TextView short_content;
public TextView date;
public ImageView image;
public LinearLayout lyt_parent;
public ViewHolder(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
short_content = (TextView) v.findViewById(R.id.short_content);
date = (TextView) v.findViewById(R.id.date);
image = (ImageView) v.findViewById(R.id.image);
lyt_parent = (LinearLayout) v.findViewById(R.id.lyt_parent);
}
}
class ViewHolderHeader extends RecyclerView.ViewHolder {
public TextView title;
public TextView date;
public ImageView image;
public LinearLayout lyt_parent;
public ViewHolderHeader(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
date = (TextView) v.findViewById(R.id.date);
image = (ImageView) v.findViewById(R.id.image);
lyt_parent = (LinearLayout) v.findViewById(R.id.lyt_parent);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_header, parent, false);
return new ViewHolderHeader(v);
} else if (viewType == TYPE_ITEM) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_news, parent, false);
return new ViewHolder(v);
}
return null;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof ViewHolderHeader) {
ViewHolderHeader vHeader = (ViewHolderHeader) holder;
vHeader.title.setText(header.getTitle());
vHeader.date.setText(header.getDate());
Picasso.with(ctx).load(header.getImage()).into(vHeader.image);
vHeader.lyt_parent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//TODO: do your thing
}
});
} else if (holder instanceof ViewHolder) {
final NewsArticles c = items.get(position);
ViewHolder vItem = (ViewHolder) holder;
vItem.title.setText(c.getTitle());
vItem.short_content.setText(c.getShort_content());
vItem.date.setText(c.getDate());
Picasso.with(ctx).load(c.getImage()).into(vItem.image);
vItem.lyt_parent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//TODO: do your thing
}
});
}
}
// need to override this method
#Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_HEADER;
}
return TYPE_ITEM;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
public NewsArticles getItem(int position) {
return items.get(position);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return items.size();
}
}