NetworkImageView set image from file - java

I have a NetworkImageView which loads its content from a URL, but in a specific case I also want to be able to load an image from the user's Gallery (or even capture one with the camera).
I'm using Image Chooser Library to load the image from the gallery, and after choosing, it allows me to get the file path of the image. This path is something like /mnt/sdcard/bimagechooser/IMG_20140811_155007906.jpg
If I try to load the image directly from this path, Volley will raise an exception stating:
NetworkDispatcher.run: Unhandled exception java.lang.RuntimeException: Bad URL /mnt/sdcard/bimagechooser/IMG_20140811_155007906.jpg
I also tried setting the drawable:
Drawable newImage = Drawable.createFromPath(imagePath);
mNetworkImageView.setImageDrawable(newImage);
Nothing happens when these lines run, the view remains empty/unchanged.
What is the correct way to set the NetworkImageView content without a URL?

The reason that setImageDrawable does not work is because in onLayout and setImageUrl call the private method loadImageIfNecessary, which will call another private method setDefaultImageOrNull if there was no image url provided. This wipes out whatever you set via setImageDrawable.
Volley's NetworkImageView code...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
void loadImageIfNecessary(final boolean isInLayoutPass) {
...
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
...
}
private void setDefaultImageOrNull() {
if(mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
else {
setImageBitmap(null);
}
}
You can do as #mmlooloo suggests and manually add your image to the image cache OR you create your own "NetworkImageView" based off of Volley's and have onLayout NOT call loadImageIfNecessary when no url is provided. You can then override the ImageView setters setImageDrawable and setImageURI to set the url to null. You can then use setImageDrawable and setImageURI as you normally would with ImageView.
NOTE: You may run into issues if you override setImageBitmap or setImageResource methods as these are called within loadImageIfNecessary. I have not run into issues yet, however I have not done much testing.
Your class...
public class MyNetworkImageView extends ImageView {
//Copy the code from Volley's NetworkImageView and change onLayout as below.
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!TextUtils.isEmpty(mUrl)) {
loadImageIfNecessary(true);
}else{
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
}
}
//Change the other ImageView image setters, for example...
#Override
public void setImageDrawable(Drawable drawable) {
mUrl = null;
super.setImageDrawable(drawable);
}
#Override
public void setImageURI(Uri uri) {
mUrl = null;
super.setImageURI(uri);
}
}

I am not sure but it worth trying:
when you create memory cache for your mImageLoader keep it somewhere, now create cache key for your
image by this code:
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
after that put your image into the cache by calling :
mCache.putBitmap(cacheKey, response);
then again send your url to NetworkImageView, Volley will find your url in his cache and return it to you.
I hope it helps!!

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.

How can I fingerpaint with touch over an image loaded by picasso

I am creating an app that pulls images from urls and puts them into a recyclerview. The user can then access those images and view it fullscreen. This is achieved with Picasso. I would now like the ability to fingerpaint over the image loaded with Picasso with an onTouchEvent or something but not sure how to do it.
This class sets the image to a map_edit_gallery.xml loaded with Picasso:
public class EditMapImage extends AppCompatActivity {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_edit_gallery);
checkIntent();
//Find savebutton
ImageButton saveMapButton = findViewById(R.id.saveEditImagebutton);
saveMapButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(),"Saved",Toast.LENGTH_SHORT).show();
}
});
}
//This will check to see if the intent extras exist and if they do get the extra
private void checkIntent(){
if(getIntent().hasExtra("image_url") && getIntent().hasExtra("name_url")){
String imageUrl = getIntent().getStringExtra("image_url");
String nameUrl = getIntent().getStringExtra("name_url");
setMapImage(imageUrl, nameUrl);
}
}
private void setMapImage(String imageUrl, String nameUrl){
//Set the Text view
TextView name = findViewById(R.id.mapNameEditor);
name.setText(nameUrl);
//Set the Image
ImageView imageView = findViewById(R.id.mapEditScreen);
Picasso.get().load(imageUrl).into(imageView);
Picasso picasso = Picasso.get();
DrawToImage myTransformation = new DrawToImage();
picasso.load(imageUrl).transform(myTransformation).into(imageView);
}
}
EDIT:
This class has allowed me to draw over the loaded image using canvas but cannot figure out how to use touch to draw:
public class DrawToImage implements Transformation {
#Override
public String key() {
// TODO Auto-generated method stub
return "drawline";
}
public Bitmap transform(Bitmap bitmap) {
// TODO Auto-generated method stub
synchronized (DrawToImage.class) {
if(bitmap == null) {
return null;
}
Bitmap resultBitmap = bitmap.copy(bitmap.getConfig(), true);
Canvas canvas = new Canvas(resultBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
canvas.drawLine(0, resultBitmap.getHeight()/2, resultBitmap.getWidth(), resultBitmap.getHeight()/2, paint);
bitmap.recycle();
return resultBitmap;
}
}
}
Try using the image selected by the user to set it in a canvas object and draw on the canvas object itself, as opposed to the image. There are plenty of tutorials out there to help you with how to draw on a canvas.
This process isn't connected with the Picasso Image Library in any way so I would recommend first getting the image through Picasso, then sending the image into your custom canvas implementation, then returning a bitmap/drawable which you could set into Picasso after editing.
There's also plenty of tutorials on how to export an image from the canvas to get your edited image when you need it.
I hope this helped, Panos.

How to check if an ImageView was loaded with a picture image from a URL

I am trying to check whether an ImageView was loaded with an image, and that the URL actually had an image resource.
ImageView dynamicImageView = new ImageView(context)
Picasso.get()
.load(myURL)
.resize(myWidth, myWidth)
.centerCrop()
.into(dynamicImageView, new Callback() {
#Override
public void onSuccess() {
Drawable drawable = dynamicImageView.getDrawable();
boolean hasImage = (drawable != null);
if (hasImage && (drawable instanceof BitmapDrawable)) {
hasImage = ((BitmapDrawable)drawable).getBitmap() != null;
}
}
#Override
public void onError(Exception e) {
}
});
This always returns true since Picasso automatically fills the default drawable placeholder into the dynamicImageView.
How should I go about it so that I always get a "truthful" result where the URL had no image resource?
Thank you all in advance.
You can go with Target callback:
private Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
}
#Override
public void onBitmapFailed() {
}
}
And while loading image, you need to write:
Picasso.with(this).load(myURL).into(target);
Just for the information:
onBitmapLoaded() mostly used to perform image operations before we load into the view actually.
Picasso.LoadedFrom describes where the image was loaded from, whether it's MEMORY, DISK or NETWORK.

Picasso image downloading & caching behavior

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.

Why doesn't android ImageAdapter load images lazily?

I have this ImageAdapter for android's listView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
RelativeLayout relativeLayout = null;
Offer currentOffer = mOffersList.get(position);
if (convertView == null) { // create a new view if no recycling
// available
// Make up a new view
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.offer_list_item, null);
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
} else {
view = (View) convertView;
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
setBackgroundDrawable(relativeLayout, null);
}
String imageUrl = "";
imageUrl = currentOffer.getImageUrl().toString();
Bitmap bitmap = imageCache.get(imageUrl);
if (bitmap != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bitmap);
setBackgroundDrawable(relativeLayout, dr);
} else {
if (!downloadingImageUrls.contains(imageUrl)) {
downloadingImageUrls.add(imageUrl);
new DownloadImageAsyncTask().execute(imageUrl);
}
}
return view;
}
and this:
class DownloadImageAsyncTask extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
String imageUrl = params[0];
try {
Bitmap bitmap = BitmapFactory
.decodeStream((InputStream) new URL(imageUrl)
.getContent());
imageCache.put(imageUrl, bitmap);
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
downloadingImageUrls.remove(imageUrl);
return null;
}
#Override
protected void onPostExecute(Void result) {
notifyDataSetChanged();
}
}
why do all of the list items loaded together? it's don' asynchronously but yet not one by one. All together.
how can i load it lazily?
and why is this code more efficient?
// better
public class DownloadImageAsyncTask2 extends
AsyncTask<String, Void, Bitmap> {
private final ImageView imageView;
public DownloadImageAsyncTask2(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected void onPreExecute() {
Log.i("DownloadImageAsyncTask", "Starting image download task...");
}
#Override
protected Bitmap doInBackground(String... params) {
try {
return BitmapFactory.decodeStream((InputStream) new URL(
params[0]).getContent());
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
To answer your initial question, any adapter you write is exactly as it's called; it's an adapter. It lets you translate your data into Views displayed in an AdapterView. It would not make sense to force all views to be "loaded lazily" and, quite frankly, should not do that for a few reasons.
Maybe you have prefetched all your images and don't need lazy loading. Maybe they even just come from resources and don't need to be handled in a special way at all.
How would it know from where to lazily load your images (database, memory map, file, network resource, other android service, etc.)? That's your job when writing the adapter.
You might argue it would be nice for Adapter subclasses to have a loadLazily(Uri image, ImageView view) function. If so why not subclass AbsAdapter yourself and add it or submit a patch to the AOSP. I, however, doubt you will find agreement that even a function like that should be included as part of an Adapter. It's really your job to manage your data in an efficient manner, despite how normal it is to lean on the system for data management in Android.
To answer your other questions about the two methods of lazy loading you proposed, the difference is that the first method causes the AdapterView to reload all of its Views whereas in the second method you are simply invalidating the view into which you are loading the image.
I don't actually agree the second method is "better" on a whole because every time your configuration is changed or process dies and Views need to be reloaded you need to make new calls over the network or to the file system to load your images. If you cache them as you do in the first method then you can at least avoid needing to reload all the images through configuration changes. If you wrote a separate image loading service that ran in its own process (not something I'm recommending) you could also avoid the second case (of your :default process getting killed).
Point is, you're responsible for loading your images, not Android. If you want it done for you take a look at these great image loading or otherwise general resource acquisition libraries for android: Local image caching solution for Android: Square Picasso vs Universal Image Loader . I've personally used Universal-Image-Loader before and admit it works as advertised. However, it caches all your images on external storage which might not be an option for you.
The listview can call getView for all items in its onMeasure (cf. measureHeightOfChildren in listView.java).
So, with this getView, it loads all images.
To load lazily, you need to load images elsewhere: let the views ask the image when needed (onDraw) to a manager (which will load them asynchronously).
notifyDataSetChanged() with update all the listview (meaning recycling views, calling getView ...), imageView.setImageBitmap will update only the ImageView so it's a better idea.
use this imageloader it works IMAGELOADER
You can use this library https://github.com/thest1/LazyList. It's very easy to use.

Categories

Resources