Short delay when replacing Image with Glide - java

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.

Related

RecyclerView items showing wrong info on tiles [duplicate]

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.

Loading images from phone to my app using Fresco library android / java

Hello I am new to Fresco and I am trying to load all the images in my phone to the app
when I use this URI: /storage/emulated/0/DCIM/Camera/20200206_222309.jpg
with this code:
ControllerListener listener = new BaseControllerListener();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(xuri)
.setTapToRetryEnabled(true)
.setControllerListener(listener)
.build();
photosFragmentRecycler.setController(controller);
only the photo with the name 20200206_222309.jpg is loaded
how can I load all the images?
thanks in Advance
Your question doesn't depend on the image library used. You basically want to get a list of all camera images.
Something like this should get you the list of all URIs:
fun getMediaStoreUris(context: Context): List<Uri> {
val uris = mutableListOf<Uri>()
context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
null,
null,
null)?.use {
val dataIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while (it.moveToNext()) {
uris.add(
ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
it.getLong(dataIndex)))
}
}
return uris
}
Then, you can just create a simple RecyclerView adapter and ViewHolder and load images with Fresco:
public class SimpleAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
private List<Uri> mUris;
SimpleAdapter(List<Uri> uris) {
mUris = uris;
setHasStableIds(true);
}
#Override
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.drawee_recycler_item, parent, false);
return new SimpleViewHolder(itemView);
}
#Override
public void onBindViewHolder(SimpleViewHolder holder, int position) {
holder.mSimpleDraweeView.setImageURI(mUris.get(position));
}
#Override
public int getItemCount() {
return mUris.size();
}
#Override
public long getItemId(int position) {
return mUris.get(position).hashCode();
}
public void setData(List<Uri> uris) {
mUris = uris;
notifyDataSetChanged();
}
}
static class SimpleViewHolder extends RecyclerView.ViewHolder {
private final SimpleDraweeView mSimpleDraweeView;
SimpleViewHolder(View itemView) {
super(itemView);
mSimpleDraweeView = itemView.findViewById(R.id.drawee_view);
}
}
and use the adapter:
final SimpleAdapter adapter = new SimpleAdapter(getMediaStoreUris(context));
recyclerView.setAdapter(adapter);
Fresco has a full sample in their Showcase sample application here: https://github.com/facebook/fresco/blob/9fde53f963d07495118c61ea2d9a3fce575afeb7/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/drawee/DraweeRecyclerViewFragment.java
This is going to work if you allow permission to access local storage, there's a guide here or to test it, by simply going to "App Info" for your app and manually allowing it.
This is a very simple solution and in practice, you probably want to implement pagination etc. for the media store. But this should guide you in the right direction.

Unable to get favicon from url when mobile does not have any internet or is it possible to save at internal storage and then to read from there

I have a parsed XML which I have read data from there. I have manually declared some data and the other data will be saved by the user.
I have declared with glide but it is not getting the right icon or when there is no internet the icons are hidden.
The declared the xml they have the icons at the drawable and when I parse they are readable and I can show.
What I want is there any option to take the favicon and save to drawable or to an internal storage or cache so I can read even if I dont have internet.
The items which are declared manually and parsed for them take the icon from drawable but for the others take the favicon from url.
Here is what I have tried so far.
The Pojo.class
public class Bookmark implements Parcelable, Comparable {
String name, id, nativeUrl, searchUrl;
long db_id;
int icon;
int viewType;
// Constructor, getters, setters & other default functions are omitted for simplicity.
}
The Adapter.class
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
ArrayList<Bookmark> arrayList;
BookmarkDB bookmarkDB;
public static final int ITEM_TYPE_ONE = 0;
public static final int ITEM_TYPE_TWO = 1;
boolean connected = false;
String BASE_URL = "https://besticon-demo.herokuapp.com/icon?url=";
public MyAdapter(Context context, ArrayList<Bookmark> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
private boolean switchOnOff;
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = null;
if (viewType == ITEM_TYPE_ONE) {
view = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false);
return new ViewHolder(view);
} else if (viewType == ITEM_TYPE_TWO) {
view = LayoutInflater.from(context).inflate(R.layout.add_bookmark, parent, false);
return new ButtonViewHolder(view);
} else {
return null;
}
}
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, final int position) {
final int itemType = getItemViewType(position);
if (itemType == ITEM_TYPE_ONE) {
final ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.tvName.setText(arrayList.get(position).getName());
RequestOptions requestOptions = new RequestOptions();
bookmarkDB = new BookmarkDB(context);
viewHolder.tvIcon.setImageResource(arrayList.get(position).getIcon());
Glide.with(context)
.load(BASE_URL+arrayList.get(position).getSearchUrl() + "&size=32;")
.apply(requestOptions)
.into(viewHolder.tvIcon);
viewHolder.tvIcon.setImageResource(arrayList.get(position).getIcon());
viewHolder.tvId.setText(arrayList.get(position).getId());
viewHolder.tvSearchUrl.setText(arrayList.get(position).getSearchUrl());
} else if (itemType == ITEM_TYPE_TWO) {
ButtonViewHolder buttonViewHolder = (ButtonViewHolder) holder;
buttonViewHolder.imgButton.setImageResource(arrayList.get(position).getIcon());
}
}
#Override
public int getItemViewType(int position) {
// Based on you list you will return the ViewType
if (arrayList.get(position).getViewType() == 0) return ITEM_TYPE_ONE;
else return ITEM_TYPE_TWO;
}
}
Ass you can see at the Adapter.class
// This get the icon from drawable and it sets to parsed array.
viewHolder.tvIcon.setImageResource(arrayList.get(position).getIcon());
// This gets the icon from URL and sets it to the parsed array but only when the user has internet.
Glide.with(context)
.load(BASE_URL+arrayList.get(position).getSearchUrl() + &size=32;")
.apply(requestOptions)
.into(viewHolder.tvIcon);
This is the declared XML which I have parsed, the file it is declared under res/xml/bookmarks.xml
<Bookmarks>
<Bookmark name="Bing" hidden="" icon="bing" id="0" nativeUrl="" searchUrl="https://www.bing.com" />
<Bookmark name="Google" hidden="" icon="google" id="1" nativeUrl="" searchUrl="https://www.google.com" />
<Bookmark name="Youtube" hidden="" icon="youtube" id="2" nativeUrl="" searchUrl="http://m.youtube.com" />
</Bookmarks>
So if I have understood correctly, you have two problems.
The default icon is not showing (i.e. hidden) when there is no internet.
You want to show the images once loaded even if there is no internet.
Both of these two can be achieved using Glide with minimal change in your code. Just change the image loading portion of your code using Glide like the following.
String imageUrl = BASE_URL + arrayList.get(position).getSearchUrl() + "&size=32";
Glide.with(context)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.placeholder)
.into(viewHolder.tvIcon);
DiskCacheStrategy.ALL manages the caching of your images and should load the images even if the device is offline. Setting the placeholder using the .placeholder(R.drawable.placeholder) can be used, when the images are not loaded for internet problem and hence you want to show a default image instead.
If you want to read more about the caching strategies, this link can help as well.
Update 1
Based on your comment, I think you should have an if statement here. When the drawable is available locally, then you will use the setImageResource function, else use Glide to load the image.
Update 2
You do not have to save the images locally if you are using Glide with DiskStrategy.ALL. It will cache the images and will show automatically when the internet connection is not available. I would like to suggest you to remove the manual setup.
Update 3
If you want to use some of the icons from your own drawable, then you might consider putting something like this.
if (theIconIsAvailableInDrawable) showThatFromDrawable();
else useGlideToShowTheIconFromUrlUsingDiskCacheStrategy();
Hope you get the idea.

Get to know device screen size programmatically in Android?

Please have a look at related Question. I got to know some hint from comments and now putting it in a new way.
I am using a RecyclerView with StaggeredGridView adapter to display the data. How do I get to know that how many items needed to be loaded for current devices to fit the whole screen?
process
Determine the screen size
Determine how many items needed to be loaded to fit full screen
Fetch data from Server with number of items
Display them
When user scroll down device fetch same amount of items and so on.
Question
I am not able to understand How to get done with first two points.
Code for Adapter
public class StaggeredGridAdapter extends RecyclerView.Adapter<StaggeredGridAdapter.StaggeredGridView> {
private Context context;
private String INTENT_VALUE = "warehouse";
private List<Warehouse> warehouses = new ArrayList<Warehouse>();
private AppDelegate app;
int size;
public StaggeredGridAdapter(Context context) {
this.context = context;
app = (AppDelegate) context;
}
public void addItems(List<Warehouse> response) {
size = response.size();
warehouses = response;
}
#Override
public StaggeredGridView onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.grid_item, parent, false);
StaggeredGridView staggeredGridView = new StaggeredGridView(layoutView);
return staggeredGridView;
}
#Override
public void onBindViewHolder(StaggeredGridView holder, int position) {
holder.textView.setText(warehouses.get(position).getFace());
}
#Override
public int getItemCount() {
return size;
}
class StaggeredGridView extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textView;
public StaggeredGridView(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.img_name);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Intent intent = new Intent(context, WareHouseDetailActivity.class);
intent.putExtra(INTENT_VALUE, warehouses.get(getAdapterPosition()));
v.getContext().startActivity(intent);
}
}
}
code for filling data into adapter
mRecyclerView = (RecyclerView) mActivity.findViewById(R.id.staggering_grid);
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
mAdapter = new StaggeredGridAdapter(mContext);
mAdapter.addItems(response);
mRecyclerView.setAdapter(mAdapter);
As to find the Height and Width, you can use the DisplayMetrics.
final DisplayMetrics displayMetrics = this.getApplicationContext().getResources().getDisplayMetrics();
This displayMetrics will give Height, Width and Density. Use the height of the single View (or row) and the total height to calculate the number of items.
Why do you want the Exact number of items, if it's a server call you are making, you could just make a rough estimate on the number of items based on the device size. Also, I think it's better to keep the number of items sent by server a constant and not dependent on the device height. If you received more than the number of items you can fit in the screen, well, the user can scroll to see them anyways.
Rather than trying to work out how much you'll need to load, you could set it up to automatically load more when you're close to the end of the list.
Consider this method:
#Override
public void onBindViewHolder(StaggeredGridView holder, int position) {
holder.textView.setText(warehouses.get(position).getFace());
}
This is called when the item is ready to be displayed on the screen. By doing something like if(position > getItemCount() - 5{ // load more } then you could be automatically loading more as and when they're required.

Retrieving Image from internet

I've got the following code from "Android in Practice" book. It's implementation of custom adapter which downloads images from the internet. It uses private class RetrieveImageTask to retrieve pictures.
Can someone explain me why the first thing the adapter class does is to get image from the cache instead of downloading it ? I understand it in a way that, the first time it displays
default image which was set in the beginning of getView(), then sets downloaded image, but does it mean that view is being refreshed constantly by calling getView() ?
And why author sets tag of image to item ID in getView() and then sets it to null in onPostExecute() ?
DealsAdapter
private class DealsAdapter extends ArrayAdapter<Item> {
public DealsAdapter(List<Item> items) {
super(DealList.this, R.layout.list_item, items);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false);
}
// use ViewHolder here to prevent multiple calls to findViewById (if you have a large collection)
TextView text = (TextView) convertView.findViewById(R.id.deal_title);
ImageView image = (ImageView) convertView.findViewById(R.id.deal_img);
image.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ddicon));
Item item = getItem(position);
if (item != null) {
text.setText(item.getTitle());
Bitmap bitmap = app.getImageCache().get(item.getItemId()); //<------HERE
if (bitmap != null) {
image.setImageBitmap(bitmap);
} else {
// put item ID on image as TAG for use in task
image.setTag(item.getItemId());
// separate thread/via task, for retrieving each image
// (note that this is brittle as is, should stop all threads in onPause)
new RetrieveImageTask(image).execute(item.getSmallPicUrl());
}
}
return convertView;
}
}
RetriveImageTask
private class RetrieveImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public RetrieveImageTask(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected Bitmap doInBackground(String... args) {
Bitmap bitmap = app.retrieveBitmap(args[0]);
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
app.getImageCache().put((Long) imageView.getTag(), bitmap);
imageView.setTag(null);
}
}
}
Its called lazy loading. well images take time to download from net so by that some dummy image is set. As the downloading completes it will replaced with dummy image. Basically is matter of user experience with application.
the tag has something to do with how the cache mechanism of your code works- the key of the items is their number in this sample , meaning it is used to identify which image was downloaded so that you could load it from the cache instead of from the internet.
i agree that it's weird, as it could simply put the url of the image instead. using the url is more logical .
the sample isn't so efficient as it doesn't use the viewHolder design pattern (you can learn about it via the lecture "the world of listView") and doesn't have downsampling in mind (you can check out this post about it).
the image that is shown before showing the correct image is for showing the user that it's being prepared (like a placeholder saying "downloading...").
it's just a sample for you to learn from.

Categories

Resources