I am loading images into ArrayList and then setting it to RecylerView with GridLayout. The images are loaded and everything looks good. But when I scroll the RecylerView it starts lagging.Though images are loaded completely there is a lag while scrolling.
Some answers I came across were about lazy loading the images or using Glide or Picasso libraries. But how can I implement it?
Here's how I am adding images.
MainActivity:
int imageArray[] = new int[]{
R.drawable.one,
R.drawable.two, R.drawable.three,
R.drawable.four,R.drawable.five,...
};
private ArrayList prepareData(){
ArrayList arrayList = new ArrayList();
for(int i=0;i<names.length;i++){
MyModel myModel = new MyModel();
myModel.setDrawable(imageArray[i]);
arrayList.add(myModel);
}
return arrayList;
}
Here's my Adapter code:
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.images_card, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.tv_logo.setImageResource(mArrayList.get(i).getDrawable());
}
If you are using Glide then use following code to load your drawable image into imageview using Glide
Glide.with(context).load(R.drawable.yourImage).into(imageView);
Where "context" is your activity context
and "imageView" is your object of ImageView
You can also use the native code of your network calling library that show images, and also if there is a sort of lagging, you can add progress bar in your image view for good user experience and also try to use AppCompatImageView instead of ImageView in your layout.
It's better to use Glide or Picasso for image loading.
Try this
Glide.with(yourContext).load(imageArray[position]).into(viewHolder.tv_logo);
Image loading would be smooth and fast.
You can try Picasso also.
Happy Coding :)
Related
I have a RecyclerView adapter that looks like this:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
private static Context context;
private List<Message> mDataset;
public RecyclerAdapter(Context context, List<Message> myDataset) {
this.context = context;
this.mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
public TextView title;
public LinearLayout placeholder;
public ViewHolder(View view) {
super(view);
view.setOnCreateContextMenuListener(this);
title = (TextView) view.findViewById(R.id.title);
placeholder = (LinearLayout) view.findViewById(R.id.placeholder);
}
}
#Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
ViewHolder vh = new ViewHolder((LinearLayout) view);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
int numImages = item.getImages().size();
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
}
}
#Override
public int getItemCount() {
return mDataset.size();
}
}
However, some of the items in the RecyclerView are showing images when they shouldn't be. How can I stop this from happening?
I do the check if (numImages > 0) { in onBindViewHolder(), but that's still not stopping it from showing images for items that shouldn't have images.
You should set imageView.setImageDrawable (null)
In onBindViewHolder() before setting the image using glide.
Setting image drawable to null fix the issue.
Hope it helps!
The problem is in onBindViewHolder, here:
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
}
If numImages is equal to 0, you're simply allowing the previously started load into the view you're reusing to continue. When it finishes, it will still load the old image into your view. To prevent this, tell Glide to cancel the previous load by calling clear:
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
} else {
Glide.clear(image);
}
When you call into(), Glide handles canceling the old load for you. If you're not going to call into(), you must call clear() yourself.
Every call to onBindViewHolder must include either a load() call or a clear() call.
I also had issues with RecyclerView showing wrong images. This happens because RecyclerView is not inflating view for every new list item: instead list items are being recycled.
By recycling views we can ruffly understand cloning views. A cloned view might have an image set from the previous interaction.
This is especially fair if your are using Picasso, Glide, or some other lib for async loading. These libs hold reference to an ImageView, and set an image on that refference when image is loaded.
By the time the image gets loaded, the item view might have gotten cloned, and the image is going to be set to the wrong clone.
To make a long story short, I solved this problem by restricting RecyclerView from cloning my item views:
setIsRecyclable(false)in ViewHolder constructor.
Now RecyclerView is working a bit slower, but at least the images are set right.
Or else cansel loading image in onViewRecycled(ViewHolder holde)
The issue here is that, as you are working with views that are going to be recycled, you'll need to handle all the possible scenarios at the time your binding your view.
For example, if you're adding the ImageView to the LinearLayout on position 0 of the data source, then, if position 4 doesn't met the condition, its view will most likely have the ImageView added when binding position 0.
You can add the content of R.layout.images content inside your
R.layout.message_layout layout's R.id.placeholder and showing/hiding the placeholder depending on the case.
So, your onBindViewHolder method would be something like:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
int numImages = item.getImages().size();
if (numImages > 0) {
holder.placeholder.setVisivility(View.VISIBLE);
ImageView image = (ImageView)holder.placeholder.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
}else{
holder.placeholder.setVisibility(View.INVISIBLE);
}
}
Sometimes when using RecyclerView, a View may be re-used and retain the size from a previous position that will be changed for the current position. To handle those cases, you can create a new [ViewTarget and pass in true for waitForLayout]:
#Override
public void onBindViewHolder(VH holder, int position) {
Glide.with(fragment)
.load(urls.get(position))
.into(new DrawableImageViewTarget(holder.imageView,/*waitForLayout=*/ true));
https://bumptech.github.io/glide/doc/targets.html
I also had the same problem and ended with below solution and it working fine for me..
Have your hands on this solution might be work for you too (Put below code in your adapter class)-
If you are using Kotlin -
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
If you are using JAVA -
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
This works for me in onBindViewHolder!
if(!m.getPicture().isEmpty())
{
holder.setIsRecyclable(false);
Picasso.with(holder.profile_pic.getContext()).load(m.getPicture()).placeholder(R.mipmap.ic_launcher_round).into(holder.profile_pic);
Animation fadeOut = new AlphaAnimation(0, 1);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setDuration(1000);
holder.profile_pic.startAnimation(fadeOut);
}
else
{
holder.setIsRecyclable(true);
}
I was having same issue I solved by writing holder.setIsRecyclable(false).Worked for me.
#Override
public void onBindViewHolder(#NonNull RecylerViewHolder holder, int position) {
NewsFeed currentFeed = newsFeeds.get(position);
holder.textView.setText(currentFeed.getNewsTitle());
holder.sectionView.setText(currentFeed.getNewsSection());
if(currentFeed.getImageId() == "NOIMG") {
holder.setIsRecyclable(false);
Log.v("ImageLoad","Image not loaded");
} else {
Picasso.get().load(currentFeed.getImageId()).into(holder.imageView);
Log.v("ImageLoad","Image id "+ currentFeed.getImageId());
}
holder.dateView.setText(getModifiedDate(currentFeed.getDate()));
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
This Works for Me
I Had the same issue and i fixed it like this:
GOAL : onViewAttachedToWindow
#Override
public void onViewAttachedToWindow(Holder holder) {
super.onViewAttachedToWindow(holder);
StructAllItems sfi = mArrayList.get(position);
if (!sfi.getPicHayatParking().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicHayatParking() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicSleepRoom().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSleepRoom() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicSalonPazirayi().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSalonPazirayi() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicNamayeStruct().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicNamayeStruct() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
}
I had a similar issue when getting pictures from the photo gallery and putting them in a recyclerview with GridLayoutManager(never had the issue with Glide). So in the adapter onBindViewHolder use a HashMap or SparseIntArray to put the current hashcode(this is the common thing that the recycled views have in common) and adapter position inside it. Then call your background task and then once it's done and before you set the image, check to see if the hashcode key - which will always have the current adapter position as the value - still has the same value (adapter position) as when you first called the background task.
(Global variable)
private SparseIntArray hashMap = new SparseIntArray();
onBindViewHolder(ViewHolder holder, int position){
holder.imageView.setImageResource(R.drawable.grey_square);
hashMap.put(holder.hashCode(), position);
yourBackgroundTask(ViewHolder holder, int position);
}
yourBackGroundTask(ViewHolder holder, int holderPosition){
do some stuff in the background.....
*if you want to stop to image from downloading / or in my case
fetching the image from MediaStore then do -
if(hashMap.get(holder.hashCode())!=(holderPos)){
return null;
}
- in the background task, before the call to get the
image
onPostExecute{
if(hashMap.get(holder.hashCode())==(holderPosition)){
holder.imageView.setImageBitmap(result);
}
}
}
So i am just providing an extension to this answer since there is not much space to leave it as comment.
After trying out like mentioned in one of above solutions i found out that, the real issue can still be addressed even if you are using a static resource(is not being downloaded and is available locally)
So basically on onBindViewHolder event i just converted the resource to drawable and added it like below :
imageView.setImageDrawable(ContextCompat.getDrawable(context,R.drawable.album_art_unknown));
this way you wont have an empty space on the view while glide/async downloader is loading the actual image from network.
plus looking at that being reloaded every time i also added below code while calling the recycler adapter class;
recyclerView.setItemViewCacheSize(10);
recyclerView.setDrawingCacheEnabled(true);
so by using above way you wont need to set setIsRecyclable(false) which is degrading if you have larger datasets.
By doing this i you will have a flicker free loading of recyclerview of course except for the initial loads.
I would like to say that if you send the ImageView and any load-async command (for instance loading from S3), the recycler view does get confused.
I did set the bitmap null in the onViewRecycled and tested with attach and detach views etc. the issue never went away.
The issue is that if a holderView gets used for image-1, image-10 and stops at the scroll with image-19, what the user sees is image-1, then image-10 and then image-19.
One method that worked for me is to keep a hash_map that helps know what is the latest image that needs to be displayed on that ImageView.
Remember, the holder is recycled, so the hash for that view is persistent.
1- Create this map for storing what image should be displayed,
public static HashMap<Integer, String> VIEW_SYNCHER = new HashMap<Integer, String>();
2- In your Adapter, onBindViewHolder,
String thumbnailCacheKey = "img-url";
GLOBALS.VIEW_SYNCHER.put(holder.thumbnailImage.hashCode(), thumbnailCacheKey);
3- Then you have some async call to make the network call and load the image in the view right ?
In that code after loading the image from S3, you test to make sure what goes into the View,
// The ImageView in the network data loader, get its hash.
int viewCode = iim.imView[0].hashCode();
if (GLOBALS.VIEW_SYNCHER.containsKey(viewCode))
if (GLOBALS.VIEW_SYNCHER.get(viewCode).equals(bitmapKey))
iim.imView[0].setImageBitmap(GLOBALS.BITMAP_CACHE.get(bitmapKey).bitmapData);
So essentially, you make sure what is the last image key that should go into a view, then when you download the image you check to make sure that's the last image URL that goes in that view.
This solution worked for me.
I wan't to simply change an image from one to another image when the user clicks on it. The problem here is that there is a short delay for about 100 milliseconds when the first image gets replaced. It is pretty annoying because you can clearly see that the image dissapears for a short time when Glide loads Image2.
Here is how I preload the Image:
Glide
.with(mContext)
.load(imageURL)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.preload();
And here is how I display the image:
Glide
.with(mContext)
.load(imageURL)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(holder.itemImageView);
And here is my full RecyclerViewAdapter class so you can see how I preload and create the image:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<myItems> mMyItems;
Context mContext;
public MyAdapter(Context context, ArrayList<Item> myItems){
mContext = context;
mMyItems = myItems;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.my_item, parent, false);
return new MyViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, int position) {
MyItem currentItem = mMyItems.get(position);
final String image1Url = currentItem.getImage1();
final String image2Url = currentItem.getImage2();
//Preload image1
Glide.with(mContext)
.load(image1Url)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.preload();
//Preload image2
Glide.with(mContext)
.load(image2Url)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.preload();
//Display image1
Glide.with(mContext)
.load(image1Url)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(holder.itemImageView);
//item onClickListener
holder.itemImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Display image2
Glide.with(mContext)
.load(image2Url)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(holder.itemImageView);
});
}
#Override
public int getItemCount() {
return mMyItems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
ImageView itemImageView;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
itemImageView = itemView.findViewById(R.id.imageViewItem);
}
}
}
The delay only occurs when clicking on the button for the first time, after that there is no delay, seems like the image is then cached. Does anyone know how to fix that?
The images are stored on my website as .webp files, I also tried .png but it doesn't work either.
When I call the images from the drawable folder in my project it works but that is not an option for me.
How can I fix this small delay when clicking the image the first time?
Do I preload the image wrong?
EDIT
Here is a new created project to demonstrate the issue so you can test it yourself (Delay only appears on the first click, restart app to reset the issue)
https://github.com/DaFaack/GlideDelayBugDemonstration
Maybe you need to add fade animation or if you want to change image sync, you need to get the bitmap. I think it's not a good way.
Glide V3
Bitmap myBitmap = Glide.with(applicationContext)
.load(yourUrl)
.asBitmap()
.into(500, 500)
.get();
imageView.setBitmap(myBitmap);
Glide V4
FutureTarget<Bitmap> futureBitmap = Glide.with(applicationContext)
.asBitmap()
.load(yourURL)
.submit();
Bitmap myBitmap = futureBitmap.get();
imageView.setBitmap(myBitmap);
Note: This code needs to be run in the background if you open strictMode.
I'm trying to create a RecyclerView that is populated by ImageViews in each cell and each image corresponds to an image in Firebase Storage.
I have a list of Strings that is passed into my RecyclerView adapter and each one represents a URL to an image in Firebase Storage. I load each image inside the onBindViewHolder().
What i get in return is a very VERY slow loading of a few images (around 5-see picture) and then it takes around 4 minutes to load another 5 and it never seems to load any other images after these.
I've read multiple posts on StackOverflow but most of them just tell you to use fitCenter() or centerCrop() but that doesn't change anything in my case. I also read in Glide's documentation that Glide will automatically downsample your images so i shouldn't need to do it manually, right? Any ideas what i could be doing wrong here? The Url Strings are successfully retrieved from Firebase and the queries are resolved almost instantly so i don't think there is any issue there.
UPDATE:
I've made some modifications in the onBindViewHolder() method in order to explicitly request caching of the images from Glide and i also used the thumbnail API to download lower resolutions of the images. Now more images are loading but each one still takes around 7 seconds to load which obviously is too long. If you have any suggestions let me know please.
Here's how the RecyclerView is set up in my main activity:
iconsRCV = findViewById(R.id.cardIconsRCV)
iconsRCV.layoutManager = GridLayoutManager(this,5) // set the layout manager for the rcv
val iconUrls : ArrayList<String> = ArrayList() // initialize the data with an empty array list
val adapter = CardIconAdapter(this,iconUrls) // initialize the adapter for the recyclerview
iconsRCV.adapter = adapter // set the adapter
Note that i get new data when certain queries are done and then i call adapter.notifyDataSetChanged() to pass new data to the RecyclerView.
CardIconAdapter.java:
public class CardIconAdapter extends RecyclerView.Adapter<CardIconAdapter.ViewHolder> {
private RequestOptions requestOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).centerCrop().error(R.drawable.applogotmp);
private List<String> urlsList;
private Context context;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView iconImg;
ViewHolder(#NonNull View view) {
super(view);
iconImg = view.findViewById(R.id.cardIcon);
}
}
public CardIconAdapter(Context cntxt, List<String> data) {
context = cntxt;
urlsList = data;
}
#NonNull
#Override
public CardIconAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_icons_rcv_item,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CardIconAdapter.ViewHolder holder, int position) {
GlideApp.with(context).load(urlsList.get(position)).apply(requestOptions).into(holder.iconImg);
}
#Override
public int getItemCount() {
return urlsList.size();
}
}
P.S. The image sizes in Firebase are mostly udner 200KB but with a small few reaching 4MB. Also, the ImageView in the R.layout.card_icons_rcv_item layout is 75x75 in size.
Hope you have used latest version of glide.
There are few ways for better image loading and caching,
credit goes to this nice article .
1. Enable Disk Cache
val requestOptions = RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL)
Glide.with(context).load(url).apply(requestOptions).into(imageView)
2. List item
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
Glide.with(context).load(url).apply(requestOptions).into(imageView)
3. Override Image Size (Optional)
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
.override(100, 100) // resize does not respect aspect ratio
Glide.with(context).load(url).apply(requestOptions).into(imageView)
4. Add Thumbnail Url
// With thumbnail url
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(thumbUrl))
.apply(requestOptions).into(imageView)
// Without thumbnail url
// If you know thumbnail size
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(url).apply(RequestOptions().override(thumbSize)))
.apply(requestOptions).into(imageView)
// With size multiplier
Glide.with(context).load(url)
.thumbnail(0.25f)
.apply(requestOptions).into(imageView)
5. Setup Monthly Schedule for Cleaning
// This method must be called on the main thread.
Glide.get(context).clearMemory()
Thread(Runnable {
// This method must be called on a background thread.
Glide.get(context).clearDiskCache()
}).start()
6. To Transform bitmap
// TODO remove after transformation is done
.diskCacheStrategy(SOURCE) // override default RESULT cache and apply transform always
.skipMemoryCache(true) // do not reuse the transformed result while running
.diskCacheStrategy(DiskCacheStrategy.ALL) // It will cache your image after loaded for first time
.format(DecodeFormat.PREFER_ARGB_8888) //for better image quality
.dontTransform() // to load image faster just skip transform
.placeholder(R.drawable.placeholder) // use place holder while image is being load
This is my first post...im a beginner trying to make an app for android.
im trying to make a simple match game using a deck of playing cards. Ive been able to get open source svg and png playing cards online. They either come in individual cards images (in png or svg formats) or one image (svg or png) with every card spaced nicely with a contrast background.
With all of the research ive done, i try to utilize these svg or png files but run into either out of memory issues or very slow UX performance.
My goal is to get a gridview to show all 52 cards that swipe horizontally. and if i click on a card, and match its face...then i get a point.
heres my layout activty_play_board.xml
just have one item...a gridview:
<GridView
android:id="#+id/gridView"
android:layout_width="match_parent"
android:layout_height="540dp"
android:horizontalSpacing="2dp"
android:numColumns="4"
android:stretchMode="columnWidth"
android:verticalSpacing="2dp" />
I use this public class that i got from googles dev website. it allows me to fill in the gridview with the svg or png cards
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
// if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(200, 270));
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setPadding(4,4, 4, 4);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
private Integer[] mThumbIds = {
R.drawable.two_of_clubs,
R.drawable.two_of_diamonds,
R.drawable.two_of_hearts,
R.drawable.two_of_spades,
R.drawable.three_of_clubs,
R.drawable.three_of_diamonds,
R.drawable.three_of_hearts,
R.drawable.three_of_spades,
R.drawable.four_of_clubs,
R.drawable.four_of_diamonds,
R.drawable.four_of_hearts,
R.drawable.four_of_spades,
R.drawable.five_of_clubs,
R.drawable.five_of_diamonds,
R.drawable.five_of_hearts,
R.drawable.five_of_spades,
R.drawable.six_of_diamonds,
R.drawable.six_of_hearts,
R.drawable.six_of_spades,
R.drawable.six_of_clubs,
R.drawable.seven_of_diamonds,
R.drawable.seven_of_hearts,
R.drawable.seven_of_spades,
R.drawable.seven_of_clubs,
R.drawable.eight_of_diamonds,
R.drawable.eight_of_hearts,
R.drawable.eight_of_clubs,
R.drawable.eight_of_spades,
R.drawable.nine_of_diamonds,
R.drawable.nine_of_hearts,
R.drawable.nine_of_spades,
R.drawable.nine_of_clubs,
R.drawable.ten_of_clubs,
R.drawable.ten_of_diamonds,
R.drawable.ten_of_hearts,
R.drawable.ten_of_spades,
R.drawable.jack_of_clubs,
R.drawable.jack_of_diamonds,
R.drawable.jack_of_hearts,
R.drawable.jack_of_spades,
R.drawable.queen_of_clubs,
R.drawable.queen_of_diamonds,
R.drawable.queen_of_hearts,
R.drawable.queen_of_spades,
R.drawable.king_of_spades,
R.drawable.king_of_clubs,
R.drawable.king_of_diamonds,
R.drawable.king_of_hearts,
R.drawable.ace_of_spades,
R.drawable.ace_of_diamonds,
R.drawable.ace_of_hearts,
R.drawable.ace_of_clubs
};}
heres my onCreate:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid_board);
Button goBackOnClick = (Button) findViewById(R.id.playBoardButton);
GridView gridview = (GridView) findViewById(R.id.gridView);
gridview.setAdapter(new ImageAdapter(this));
gridview.setOnItemClickListener(newAdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Toast.makeText(GridBoard.this, "Spot: " + position, Toast.LENGTH_LONG).show();
}
});
I have several questions.
How do i utilize the SVG file? am i calling the one big svg file and then referencing a specific location on the file when i call it for an imageview within my gridview?
i dont know how to use the one big SVG file, so ive tried using the individual svg images. but when i do that, my device is very laggy and slow. also sometimes the app crashes due to out of memory errors. Is there a special syntax to use when trying to reference a specific card within the large SVG file? Or do i have to manually edit the big svg file and get the smaller cards?
Is there a better way to do this than using a gridview?
any other general guidance would be greatly appreciated.
Thank you!
Heres my card sources:
one big palette
https://upload.wikimedia.org/wikipedia/commons/8/81/English_pattern_playing_cards_deck.svg
individual cards and one big palette
http://byronknoll.blogspot.com/2011/03/vector-playing-cards.html
So I recently wanted to try out the caching feature of the Picasso library, & I got into this confusing situation:
I retrieve the images' file names & paths from my web server (using Retrofit2), & I store them into ImageComponent objects (model):
public class ImageComponent {
private int id; // 'id' in database
private String filename; // image name
private String path; // image path in server storage
private Bitmap bitmap;
// Overloaded constructor
// Getters & setters
}
So now that the loading is successful, I populate a RecyclerView with these images using Picasso. The loading and inflation process is successful, but it gets a little tricky when caching the images.
Case1: using android.util.LruCache
(For convenience, I will post the entire code of the Recyclerview's adapter. I will try to be concise)
// imports
import android.util.LruCache;
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private Context mContext; // Activity's context
private List<ImageComponent> mImages; // The imageComponents to display
// The contreversial, infamous cache
private LruCache<Integer, Bitmap> mImageCache;
public ImageAdapter(Context context, List<ImageComponent> images) {
mContext = context;
mImages = images;
// Provide 1/8 of available memory to the cache
final int maxMemory = (int)(Runtime.getRuntime().maxMemory() /1024);
final int cacheSize = maxMemory / 8;
mImageCache = new LruCache<>(cacheSize);
}
#Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Nothing special
}
#Override
public void onBindViewHolder(final ImageAdapter.ViewHolder holder, final int position) {
// Current ImageComponent
ImageComponent imageComponent = mImages.get(position);
// Full image path in server storage
String imagePath = Constants.SERVER_IP_ADDRESS + Constants.UPLOADS_DIRECTORY
+ imageComponent.getPath();
// Display the file's name
holder.text.setText(imageComponent.getFilename());
final ImageView imageView = holder.image;
// Get bitmap from cache, check if it exists or not
Bitmap bitmap = mImageCache.get(imageComponent.getId());
if (bitmap != null) {
Log.i("ADAPTER", "BITMAP IS NOT NULL - ID = " + imageComponent.getId());
// Image does exist in cache
holder.image.setImageBitmap(imageComponent.getBitmap());
}
else {
Log.i("ADAPTER", "BITMAP IS NULL");
// Callback to retrieve image, cache it & display it
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
ImageComponent img = mImages.get(position);
// Display image
holder.image.setImageBitmap(bitmap);
// Cache the image
img.setBitmap(bitmap);
mImages.set(position, img);
mImageCache.put(img.getId(), bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
// Tag the target to the view, to keep a strong reference to it
imageView.setTag(target);
// Magic
Picasso.with(mContext)
.load(imagePath)
.into(target);
}
}
#Override
public int getItemCount() {
return mImages.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
// Constructor & view binding, not that special
}
}
RESULT1
(Notice those 2 last images, & how they show other previous images before displaying the correct one)
A few notes:
I ran across a problem, where the images weren't displayed at all. After some research, I found this answer which suggested binding the target to the ImageView. (worked)
I didn't quite understand how Picasso caches the images. Is it an automatic or manual process ? This answer states that Picasso handles this task for you. But when I actually tried it out (without the android Lrucache), no caching seemed to be done : The images were getting reloaded every time I scroll back & forth.
Actually I was going to post a second use case where things went even more wrong, using the Picasso's Lrucache (images were being shown randomly , & change with every scroll), but I think this post is already long enough.
My questions are:
Why do I get that weird behavior ? (as shown in the attached GIF)
How does this whole caching process work ? Should I (or could I) use a Lrucache when making use of Picasso ?
What's the difference between the Lrucache that comes with the SDK & Picasso's ? (Performance, best use case scenarios, etc...)
I think using both LRU cache and Picasso is causing the weird behaviour. I have used Picasso to cache Image to an Adapter, which works completely fine. you can check in here
Picasso cache Image automatically when used with adapter, it will cache like this, if the child item of list/Recycler view is not visible it will stop caching the image for the respective child.So it's better to use Picasso alone with Adapter.
The main usage of Picasso over LRU cache is that, Picasso is easy to use.
ex : specifying Memory cache Size in Picasso.
Picasso picasso = new Picasso.Builder(context)
.memoryCache(new LruCache(250))
.build();
Picasso also allow you to notify user with an Image when there is an error in downloading, a default holder for Imageview before loading the complete image.
Hope it helps.