I was using recycler view to List Images but it was getting laggy after even caching and all. So I Decided to use Glide library but still laggy as hell. I finally thought to check with just a single drawable and still it's laggy. I don't get why. Please help. Here is the code. There is not much code still. You can see the code I was using for my Image Viewing commented out.
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageHolder>
{
private File file;
private String logging=getClass().getSimpleName();
private int size;
private MemCache memCache;
private Context context;
private Bitmap bitmap;
public ImageAdapter(File file,int Size,MemCache memCache,Context context)
{
this.file = file;
size=Size;
this.memCache=memCache;
this.context=context;
bitmap=BitmapFactory.decodeResource(context.getResources(),R.drawable.empty_photo);
}
#Override
public ImageHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
return new ImageHolder(new ImageView(context),size);
}
#Override
public void onBindViewHolder(ImageHolder holder, int position)
{
Glide.with(context).load(R.drawable.empty_photo).into(holder.getImageView());
// if (memCache.get(file.listFiles()[position].toString())!=null)
// {
// holder.getImageView().setImageBitmap(memCache.get(file.listFiles()[position].toString()));
// }
// else
// {
// ImageSyncTask imageSyncTask = new ImageSyncTask(size, holder.getImageView(),memCache);
// imageSyncTask.executeOnExecutor(imageSyncTask.THREAD_POOL_EXECUTOR, file.listFiles()[position]);
// }
// Glide.with(context).load(file.listFiles()[position]).crossFade().into(holder.getImageView());
}
#Override
public int getItemCount()
{
if (file!=null&&file.listFiles()!=null) return file.listFiles().length;
else return 0;
}
public static class ImageHolder extends RecyclerView.ViewHolder
{
private ImageView imageView;
public ImageHolder(View itemView,int size)
{
super(itemView);
imageView=(ImageView)itemView;
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setMinimumWidth(size);
imageView.setMinimumHeight(size);
}
public ImageView getImageView()
{
return imageView;
}
public void clearImage()
{
imageView.setImageResource(android.R.color.transparent);
}
}
}
I know there is one loading Bitmap but still its just one. That shouldn't make that much lag. And yes I have used the typical setImageBitmap instead of Glide but still laggy.
There is the view holder. Just Simple function. And I was previously using a proper layout for it but it was not working as well. In this I have just used New ImageView() and setParamaters just to make sure if there was a problem in Layout.
Please Help. I don't get why the typical Adapter is creating Lag.
Original MainActivity
public class MainActivity extends AppCompatActivity
{
RecyclerView recyclerView;
int cnum;
private MemCache memCache;
private String logging=getClass().getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics displayMetrics=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int maxmem=(int)Runtime.getRuntime().maxMemory()/1024;
int cache_mem=maxmem/10;
memCache=new MemCache(cache_mem);
int orientation=getResources().getConfiguration().orientation;
if (orientation== Configuration.ORIENTATION_PORTRAIT)
cnum=3;
else cnum=5;
recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(),cnum));
recyclerView.setAdapter(new ImageAdapter(getGallery(),displayMetrics.widthPixels/cnum,memCache,getApplicationContext()));
}
private File getGallery()
{
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(),"Trial");
}
}
I have also tried and removed maximum of calls which can be removed to make sure there is not much task on UI thread.
Doing disk I/O on the main application thread is a common source of "jank", as that I/O may take a bit of time. StrictMode can help you determine where you are doing disk I/O on the main application thread.
In general, you want to load your model data (e.g., a list of files) on a background thread, then work off of the in-memory representation of that model data on the main application thread.
Related
I'm trying to get JSON data to display in a RecyclerView list, but whenever I try to make the call it seems that the RecyclerView comes up empty. I have it set up so that I get the API service/manager in onCreate prior to the configViews() method which I wrote. Am I making the call too early? I thought the problem would be to create the views/adapter prior to the call, but it doesn't seem to be making a difference.
This is the code for the Retrofit call:
listCall.enqueue(new Callback<List<Character>>() {
#Override
public void onResponse(Call<List<Character>> call, Response<List<Character>> response) {
if(response.isSuccessful()){
List<Character> characterList = response.body();
Log.v(TAG, response.toString());
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
configViews();
characterAdapter.notifyDataSetChanged();
}
else {
int sc = response.code();
}
}
#Override
public void onFailure(Call<List<Character>> call, Throwable t) {
}
});
And that configViews() method is here, albeit simpler than when I first started (I have been moving bits around to test whether they will affect inflation of the RecyclerView):
private void configViews() {
characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
EDIT: Thank you all for your replies! As requested here is the Adapter class:
public class CharacterAdapter extends
RecyclerView.Adapter<CharacterAdapter.Holder> {
private static final String TAG = CharacterAdapter.class.getSimpleName();
private final CharacterClickListener clickListener;
private List<Character> characters;
//Constructor for CharacterAdapter.
public CharacterAdapter(CharacterClickListener listener){
characters = new ArrayList<>();
clickListener = listener;
}
//Inflates CardView layout
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View row =
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, parent,
false);
return new Holder(row);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
Character currentCharacter = characters.get(position);
holder.name.setText(currentCharacter.getName());
holder.description.setText(currentCharacter.getDescription());
//Picasso loads image from URL
Picasso.with(holder.itemView.getContext())
.load("http://gateway.marvel.com/"+
currentCharacter.getThumbnail()).into(holder.thumbnail);
}
#Override
public int getItemCount() {
return characters.size();
}
public void addCharacter(Character character) {
//Log.d(TAG, character.getThumbnail());
characters.add(character);
notifyDataSetChanged();
}
public Character getSelectedCharacter(int position) {
return characters.get(position);
}
public class Holder extends RecyclerView.ViewHolder implements
View.OnClickListener{
//Holder class created to be implemented by adapter.
private ImageView thumbnail;
private TextView name, description;
public Holder(View itemView) {
super(itemView);
thumbnail = (ImageView)
itemView.findViewById(R.id.character_thumbnail);
name = (TextView) itemView.findViewById(R.id.character_name);
description = (TextView)
itemView.findViewById(R.id.character_description);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
clickListener.onClick(getLayoutPosition());
}
}
public interface CharacterClickListener {
void onClick(int position);
}
}
In principle you have done the right thing. You can instantiate the views before and fire a fetch request. Once you receive the response, you can notify the adapter that the datatset has changed and the adapter will refresh your views.
The only correction here is, you need to call your configViews(); before the for loop. So your code should be like -
configViews();
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
characterAdapter.notifyDataSetChanged();
Because right now what is happening is you add all your data using add character and then after that you again call config views which reinitialises your adapter at
characterAdapter = new CharacterAdapter(this);
Hence the empty views.
PS: I don't know your adapter's addCharacter method but i am hoping it is doing the right job. If it still doesn't work let me know and then add your addCharacter code as well.
Based on code the snippets, in configViews() method you are creating new instance of CharacterAdapter. The following code snippets will work.
private void configViews() {
//characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
You should leave the call configViews() call in onCreate but move the rest of it into onResume. It'll achieve following:
Ensure views are ready to display data
Refresh data if your app was brought to background and re-opened
Hi I got frustrated while searching solution for my problem.My problem is that i have a gridview to show all images which i selected from gallery.I want to display progressbar on each images in gridview.and while uploading images to server using multipart i want too update progressbar..
I displayed progressbar on each imageview but i am unable to show progress on each progressbar.
SO please help me to show how to show progress bar and their respective process on each imageview.
thanks in advance
Create a interface for an observer:
interface ProgressListener {
void onProgressUpdate(String imagePath, int progress);
}
Let the view holder implement that observer and know the image path:
public class ViewHolder implements ProgressListener {
ImageView imgQueue;
ProgressBar pb;
TextView tv;
String imagePath; //set this in getView!
void onProgressUpdate(String imagePath, int progress) {
if (!this.imagePath.equals(imagePath)) {
//was not for this view
return;
}
pb.post(new Runnable() {
pb.setProgress(progress);
});
}
//your other code
}
The adapter shall hold an map of observers for a particular image path/uri whatever and have an method that is called by the upload/download task. Also add methods to add and remove observer:
public class SelectedAdapter_Test extends BaseAdapter {
private Map<String, ProgressListener> mProgressListener = new HashMap<>();
//your other code
synchronized void addProgressObserver(String imagePath, ProgressListener listener) {
this.mListener.put(imagePath, listener);
}
synchronized void removeProgressObserver(String imagePath) {
this.mListener.remove(imagePath);
}
synchronized void updateProgress(String imagePath, int progress) {
ProgressListener l = this.mListener.get(imagePath);
if (l != null) {
l.onProgressUpdate(imagePath, progress);
}
}
//other code
}
In getView of the adapter register the view holder as an observer:
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
holder.imagePath = data.get(i).getSdcardPath();
this.addProgressObserver(holder.imagePath, holder);
return convertView;
}
The problem right now is, that we register the observer but don't unregister. So let the adapter implement the View.addOnAttachStateChangeListener:
public class SelectedAdapter_Test extends BaseAdapter implements View.addOnAttachStateChangeListener {
//other code
void onViewAttachedToWindow(View v) {
//We ignore this
}
void onViewDetachedFromWindow(View v) {
//View is not visible anymore unregister observer
ViewHolder holder = (ViewHolder) v.getTag();
this.removeProgressObserver(holder.imagePath);
}
//other code
}
Register that observer when you return the view.
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
convertView.addOnAttachStateChangeListener(this);
return convertView;
}
Finally you are able to tell the views what the progress is:
#Override
public void transferred(long num) {
int progress = (int) ((num / (float) totalSize) * 100);
selectedAdapter.onProgressUpdate(listOfPhotos.get(i).getSdcardPath(), progress);
}
One final problem remains, what if the activity is gone while the upload is in progress? You need to check if the activity is still alive. Maybe setting a flag in the adapter to true in onCreate and to false in onDestroy would do the trick. Then the last code fragment could check that flag and not notify the adapter on changes anymore.
So thats basically the idea of how to solve this. Does it work? I don't know I wrote it from scratch without any testing. And even if it does, you still have to manage the states when the progress is 0 or 100. But I leave that to you. Also you might want to change the BaseAdapter for an recyclerView so that we can get rid of the View.addOnAttachStateChangeListener.
add boolean in adapter class
public SelectedAdapter_Test(Context c, ArrayList<CustomGallery> data, boolean showProgress) {
mContext = c;
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.data = data;
this.showProgress = showProgress;
}
changes in Adapter class getView
holder.pb = (ProgressBar) convertView.findViewById(R.id.progressbar);
if (showProgress)
holder.pb.setVisibility(View.VISIBLE);
else
holder.pb.setVisibility(View.GONE);
make changes in doFileUpload
private void doFileUpload(View v) {
View vi = v;
for (i = 0; i < listOfPhotos.size(); i++) {
<--your task-->
}
//**important**
SelectedAdapter_Test mTest = new SelectedAdapter_Test(context,data,false);
// set above adapter object respectively;
mList.setadapter(mTest);
}
FYI. pass showProgress value as true for the first time when you set adapter.
I have a RecyclerView. In it, the items have a standard layout - one TextView and one ProgressBar.
Items are added to the recyclerview at runtime.
Whenever an Item is added, an AsyncTask is started which updates the ProgressBar.
The AsynTask holds a reference to the ProgressBar object from the RecyclerView Adapter.
The problem occurs when there are too many items in the recycler view.
I know the RecyclerView recycles any old views and thus want a way around that atleast for the progressbars.
What would be the ideal way to implement this?
Following is an excerpt from the Adapter
public class RecViewAdapter extends
RecyclerView.Adapter<RecViewAdapter.ViewHolder> {
Context mContext;
List<String> mRunns;
static ExecutorService mExec;
static HashSet<Integer> mProcessed = new HashSet<>();
public RecViewAdapter(Context context, List<String> runns) {
mContext = context;
mRunns = runns;
mExec = Executors.newFixedThreadPool(1);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.runnabel_item, viewGroup,
false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.runnName.setText(mRunns.get(position));
if (!mProcessed.contains(position)) {
new ProgressTask(holder.pBar, position).executeOnExecutor(mExec, null);
mProcessed.add(position);
}
}
#Override
public int getItemCount() {
return mRunns.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView runnName;
ProgressBar pBar;
public ViewHolder(View view) {
super(view);
runnName = (TextView) view.findViewById(R.id.textView);
pBar = (ProgressBar) view.findViewById(R.id.progressBar);
pBar.setIndeterminate(false);
pBar.setMax(100);
pBar.setProgress(0);
}
}
}
Also, I'm adding items to the RecyclerView using notifydatasetchanged.
You can simply use "findViewHolderForAdapterPosition" method of recycler view and you will get a viewHolder object from that then typecast that viewHolder into your adapter viewholder, so you can directly access your viewholder's views, ( in this case we access progressBar)
following is the sample code for kotlin
/**
* update progress bar in recycler view
* get viewHolder from position and progress bar from that viewHolder
* we are rapidly updating progressbar so we did't use notify method as it always update whole row instead of only progress bar
* #param position : position of list cell
* #param progress : new progress value
*/
private fun updateDownloadProgressBar(position :Int, progress:Int)
{
val viewHolder = recyclerViewDownload.findViewHolderForAdapterPosition(position)
(viewHolder as ViewHolderDownload).progressBarDownload.progress=progress
}
A little late, but I found a way to get it working.
My recyclerview contains a large number viewholders and only one of the viewholders have a progress bar. I have an sqlite database in which I maintain identifiers which I use to sync between my service and activity (to identify which views in the recyclerview need updating).
Depending on your implementation, you will have to find a way to identify which broadcast event corresponds to which adapter item. I have given a simplified version of what I have done below.
Model for Progress Bar:
class ProgressModel{
String progressId;
int progress = 0;
}
public int getProgress(){
return progress;
}
ViewHolder:
public class ProgressViewHolder extends RecyclerView.ViewHolder {
private ProgressBar mProgressBar;
public ProgressViewHolder(View itemView) {
mProgressBar = (ProgressBar) itemView.findViewById(R.id.mProgressBar);
}
public ProgressBar getProgressBar() {
return mProgressBar;
}
}
In the recyclerview adapter,
#Override
public void onBindViewHolder(ProgressViewHolder holder, int position) {
ProgressModel item = mData.get(position);
int progress = item.getProgress();
if (progress > 0) {
ProgressBar downloadProgress = holder.getProgressBar();
if (downloadProgress.isIndeterminate()) {
downloadProgress.setIndeterminate(false);
}
downloadProgress.setProgress(progress);
}
}
public void refresh(position, ProgressModel item){
mData.set(position, item);
notifyItemChanged(position);
}
In the Activity which implements populates the view, create a static instance of itself and pass it to the BroadcastReceiver. It took me quite a while to figure out that the static instance is required, otherwise the view doesn't get changed even though I call notifyItemchanged().
public class MainActivity extends Activity{
private static MainActivity instance;
private MyReceiver mReceiver;
private MyAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
mReceiver = new MyReceiver(instance);
//TODO: Initialize adapter with data and set to recyclerview
}
public void update(Intent intent){
ProgressModel model = new ProgressModel ();
//TODO: set progress detail from intent to model and get position from progressId
instance.mAdapter.refresh(position,model);
}
private static class MyReceiver extends BroadcastReceiver {
MainActivity activity;
public DownloadReceiver(MainActivity activity) {
this.activity = activity;
}
#Override
public void onReceive(Context context, Intent intent) {
//pass intent with progress details to activity
activity.update(intent);
}
}
}
Hope this helps.
Those who are new to Android dev and only know java might get confused of above codes given by #Mayank Sharma Which works excellent, I am giving the java version of his answer:
void updateProgress(int position, int progress)
{
MyAdapter.MyHolder mHolder =
(MyAdapter.MyHolder) myRecyclerView.findViewHolderForAdapterPosition(position);
mHolder.customProgress.setProgress(progress);
}
create this above method inside your activity where you have recycler view that contains progress bar and call this method whenever you want some changes in your progress bar.
I am using Recyclerview Gridlayout and I am facing lag while scroll.
Since I am using RecyclerView.Adapter ViewHolder is already there.
Other than that there are no conditions in onBindViewHolder i.e. onCreateView method.
Also I am making sure new objects are not created.
I am using Universal imageloader and I am pausing recyclerview imageloader on scroll as well.
I found that Image size on disk memory varies from 50 to 300 kb aprox is that have something to do with scroll speed?
public class DealsRecyclerAdapter extends RecyclerView.Adapter<DealsRecyclerAdapter.ViewHolder> {
public ArrayList<DealItem> items = new ArrayList<DealItem>();
Context context;
DisplayImageOptions options;
protected ImageLoader imageLoader;
DealItemOnClickListener dealItemOnClickListener;
private static final String RESULTS = "results";
int layoutId;
DealItem item;
String storeName;
public static final String RS = "Rs.";
public static final String AVAIL_FROM = "available from ";
public static final String OFF = "% off";
static class ViewHolder extends RecyclerView.ViewHolder {
protected TextView itemName;
protected TextView itemPriceOrig;
protected TextView itemDisc;
protected TextView itemPrice;
protected ImageView itemImage;
protected TextView itemStore;
protected TextView itemSpecialText;
public ViewHolder(View itemView) {
super(itemView);
itemImage = (ImageView) itemView
.findViewById(R.id.logo);
itemName = ((CustomTextView) itemView
.findViewById(R.id.title_product_search));
itemPriceOrig = ((CustomTextView) itemView
.findViewById(R.id.price_product_orig));
itemDisc = ((CustomTextView) itemView
.findViewById(R.id.product_disc));
itemPrice = ((CustomTextView) itemView
.findViewById(R.id.price_product));
itemStore = (TextView) itemView
.findViewById(R.id.prod_store);
itemSpecialText = (TextView) itemView
.findViewById(R.id.tvspecial);
itemPriceOrig.setPaintFlags(itemPriceOrig
.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
}
public DealsRecyclerAdapter(Context context, JSONObject jsonObject, int layoutId) {
try {
this.layoutId = layoutId;
this.context = context;
dealItemOnClickListener = (DealItemOnClickListener) (context);
options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.load)
.showImageForEmptyUri(R.drawable.load)
.showImageOnFail(R.drawable.notfound)
.cacheInMemory(true)
.cacheOnDisk(true)
.considerExifParams(true)
.resetViewBeforeLoading(true)
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.bitmapConfig(Bitmap.Config.RGB_565)
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
.build();
imageLoader = ImageLoader.getInstance();
JSONArray jsonArray = jsonObject.getJSONArray(RESULTS);
for (int i = 0; i < jsonArray.length(); i++) {
try {
items.add(Utils.getdealIemFromJson(jsonArray.getJSONObject(i)));
} catch (Exception e) {
}
}
} catch (Exception e) {
}
}
public void add(DealItem item) {
items.add(item);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (dealItemOnClickListener != null)
dealItemOnClickListener.dealItemClicked(view);
}
});
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
item = (DealItem) items.get(position);
holder.itemDisc.setText("(" + (int) Math.abs(item.discount) + OFF + ")");
holder.itemPriceOrig.setText(RS + item.originalPrice);
holder.itemPriceOrig.setVisibility(item.priceVisiblity);
holder.itemDisc.setVisibility(item.discountVisiblity);
holder.itemPrice.setText(RS + item.dealPrice);
storeName = AVAIL_FROM + "<b>" + item.store + "</b>";
holder.itemStore.setText(Html.fromHtml(storeName));
holder.itemName.setText(item.title);
holder.itemSpecialText.setText(item.specialText);
holder.itemSpecialText.setVisibility(item.specialTextVisibility);
imageLoader.displayImage(item.imageUrl, holder.itemImage, options);
holder.itemView.setTag(item);
}
#Override
public long getItemId(int arg0) {
return 0;
}
public interface DealItemOnClickListener {
void dealItemClicked(View view);
}
public void setLayoutId(int layoutId) {
this.layoutId = layoutId;
}
}
Both runtime scaling and image size can have an impact.
runtime scaling is usually discouraged and image sizes directly impacts how much could be cached at any one time.
I would also join #Alireza's question and would add a question; if you comment off the image loading is it ok?
I would also refer you to what i believe are better 3rd party image loading libraries such as Fresco, Glide and Picasso who i have a lot of experience with and can vouch for their efficiency.
And last but certainly not least i would invite you to view a session given by a friend of mine called Ran Nachmany where he explains how to analyze such issues with overdraw, systrace and other tools here.
Goodluck.
I read your code carefully, The only line that seems to make problem is this
imageLoader.displayImage(item.imageUrl, holder.itemImage, options);
I didn't work with UniversalImageLoader. Does it handles image loading in seprate thread?
any way you can simply make sure if this line is making problem by putting this exact images in resource directory and using imageView.setImageResource to see if you still have problem or not.
In case i'm right and UniversalImageLoader makes your app slow you can create an AsyncTask and run this line in separate thread or perhaps using another image loading library like Picaso.
The next candidate to cause problem is images. 300K may be low but if you sum up images sizes you get magabytes of memory usage which is enough to cause problem in even best android devices. (Android memory monitor is a really useful tool here!) and also some image formats like .jpg use compression to reduce the size and this makes decompressing file slower. Solution for that is photoshop, simply create new photo and copy and past the existing one and save. default options are ok most of the times!
And one final thought about unrelated matter! I see your handling jsonObject in adapter which is not a good practice. Puting is code inside your activity/fragment or better in some Network utility class would be a lot better.
Sorry if i can't be any more help. good luck
So I've decided to try out the new Volley library as shown on Google IO 2013.
I've tried it out while using the easy solution of NetworkImageView to show multiple images on a GridView.
It works nice and shows images, but if I let it download the images and then I turn off the WiFi during the download, it doesn't show an error as if everything still loads. Not only that, but if I restore the connection, it doesn't resume the loading.
Why does it occur, and how can I fix it? Maybe it's actually a bug?
Here's my sample code, if anyone wishes to try it out (BitmapCacheLru code here):
public class MainActivity extends Activity {
private static final int COLUMNS_COUNT = 4;
private RequestQueue _requestQueue;
private ImageLoader _imageLoader;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_requestQueue=Volley.newRequestQueue(this);
_imageLoader=new ImageLoader(_requestQueue, new BitmapLruCache());
final GridView gridView = new GridView(this);
gridView.setNumColumns(COLUMNS_COUNT);
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
gridView.setAdapter(new BaseAdapter() {
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
NetworkImageView rootView = (NetworkImageView) convertView;
if (rootView == null) {
rootView = new NetworkImageView(MainActivity.this);
rootView.setLayoutParams(new AbsListView.LayoutParams(screenWidth / COLUMNS_COUNT, screenWidth / COLUMNS_COUNT));
rootView.setScaleType(ScaleType.CENTER_CROP);
rootView.setDefaultImageResId(android.R.drawable.sym_def_app_icon);
rootView.setErrorImageResId(android.R.drawable.ic_dialog_alert);
}
final String url = getItem(position);
rootView.setImageUrl(url, _imageLoader);
return rootView;
}
#Override
public long getItemId(final int position) {
return 0;
}
#Override
public String getItem(final int position) {
return Images.imageThumbUrls[position];
}
#Override
public int getCount() {
return Images.imageThumbUrls.length;
}
});
setContentView(gridView);
}
#Override
protected void onStop() {
_requestQueue.cancelAll(this);
super.onStop();
}
}
P.S. If you want to see the code of NetworkImageView, I think it's available here .
I think the problem is that the volley does not help you to reload the image.
A quick inspection shows that the NetworkImageView only loads data when onLayout method is called and the method loadImageIfNecessary will queue the network request if necessary.
When there is no Internet connection, the error callback will be called and there is no further action once the Internet get itself connected.
However, since you have the NetworkImage in a list, when you scroll the list, I suppose you will reuse the cell view and call setImageURL once again. If the Internet connection is available, the image will be loaded automatically. Alternatively, once the Internet connection is up, you can refresh the list view and so that the image will be loaded automatically.