I have a custom adapter added to listview. Data is call logs from phone. I reduce list by show only records from 3 days. Problem is that when I try to scroll listview from top to bottom I have a huge lags. My Scroll isn't smooth. Is there any way to make listview scroll smoother?
Here is my custom adapter:
public class CallListAdapter extends ArrayAdapter<CallList> {
Activity activity;
public CallListAdapter(Context context, ArrayList<CallList> calls, Activity activity) {
super(context, 0, calls);
this.activity = activity;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final CallList callList = getItem(position);
int actualPosition = 0;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.call_list, parent, false);
}
final TextView call1 = convertView.findViewById(R.id.callNumber);
final TextView call2 = convertView.findViewById(R.id.callDate);
final TextView call3 = convertView.findViewById(R.id.conversationTime);
final TextView call4 = convertView.findViewById(R.id.callType);
final Button callView = convertView.findViewById(R.id.getViewName);
final ImageView bio = convertView.findViewById(R.id.lookBio);
final ImageView edit = convertView.findViewById(R.id.edit_call);
final ImageView block = convertView.findViewById(R.id.blockCall);
final ImageView call = convertView.findViewById(R.id.callUser);
final TextView bioLabel = convertView.findViewById(R.id.BioLabelSug);
final TextView editLabel = convertView.findViewById(R.id.NoteLabel);
final TextView blockLabel = convertView.findViewById(R.id.BlockLabelSug);
final TextView callLabel = convertView.findViewById(R.id.CallLabelSug);
final ConstraintLayout callContainer = convertView.findViewById(R.id.contact_container);
final ConstraintLayout bioContainer = convertView.findViewById(R.id.bio_container);
final ConstraintLayout blockContainer = convertView.findViewById(R.id.ignore_container);
final ConstraintLayout noteContainer = convertView.findViewById(R.id.note_container);
final TextView btnMarg = convertView.findViewById(R.id.buttonMargin);
final TextView callListNr2 = convertView.findViewById(R.id.callNumber2);
final LayoutInflater factory = activity.getLayoutInflater();
final View fullView = factory.inflate(R.layout.fragment_calls, null);
final RelativeLayout loading = fullView.findViewById(R.id.loadingBar);
String[] jsonData = new manageCalls().intentCallValues(position);
StringBuilder builder = new StringBuilder();
for (String s : jsonData) {
builder.append(s + "\n");
}
String str = builder.toString();
final String num = jsonData[0];
final String dat = jsonData[1];
final String typeCall = jsonData[2];
final String dur = jsonData[3];
final String authToken = SaveSharedPreferences.getPrefTokenName(getContext());
final Animation slideUp = AnimationUtils.loadAnimation(getContext(), R.anim.slideup);
final Animation slideDown = AnimationUtils.loadAnimation(getContext(), R.anim.slidedown);
final Handler handler = new Handler();
callView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (bioContainer.getVisibility() == View.GONE) {
callListNr2.setVisibility(View.GONE);
bio.setVisibility(View.VISIBLE);
bioLabel.setVisibility(View.VISIBLE);
edit.setVisibility(View.VISIBLE);
editLabel.setVisibility(View.VISIBLE);
} else if (bioContainer.getVisibility() == View.VISIBLE) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
bio.setVisibility(View.GONE);
callContainer.setVisibility(View.GONE);
bioContainer.setVisibility(View.GONE);
noteContainer.setVisibility(View.GONE);
blockContainer.setVisibility(View.GONE);
}
}, 300);
}
}
});
if (actualPosition != position) {
if (bioContainer.getVisibility() == View.VISIBLE) {
bioContainer.setVisibility(View.GONE);
callContainer.setVisibility(View.GONE);
noteContainer.setVisibility(View.GONE);
blockContainer.setVisibility(View.GONE);
}
actualPosition = position;
}
call.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
call.setEnabled(false);
loading.setVisibility(View.VISIBLE);
Intent intentCall = new Intent(view.getContext(), CallUserActivity.class);
intentCall.putExtra("number", num);
intentCall.putExtra("authToken", authToken);
intentCall.putExtra("Date", dat);
activity.startActivityForResult(intentCall, position);
handler.postDelayed(new Runnable() {
#Override
public void run() {
call.setEnabled(true);
loading.setVisibility(View.GONE);
}
}, 1000);
}
});
call2.setText(callList.callDate);
call3.setText(callList.conversationTime);
call4.setText(callList.callType);
return convertView;
}
}
Try use ViewHolder and use AsyncTask to load bitmap.
You can try this way.
private static class ViewHolder {
public TextView call1;
public TextView call2;
public TextView call3;
public TextView call4;
public Button callView;
public ImageView bio;
public ImageView edit;
public ImageView block;
public ImageView call;
public TextView bioLabel;
public TextView editLabel;
public TextView blockLabel;
public TextView callLabel;
public ConstraintLayout callContainer;
public ConstraintLayout bioContainer;
public ConstraintLayout blockContainer;
public ConstraintLayout noteContainer;
public TextView btnMarg;
public TextView callListNr2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// inflate the layout
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(layoutResourceId, parent, false);
holder = new ViewHolder();
holder.call1 = convertView.findViewById(R.id....);
holder.call2 = convertView.findViewById(R.id....);
//Same for all other views
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.call1.setText(....);
//Lazy load for bitmap
loadBitmap(yourFileName..., bio)
return convertView;
}
static class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
return decodeSampledBitmapFromResource(params[0], 300, 300);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public void loadBitmap(String fileName, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(fileName);
}
public static Bitmap decodeSampledBitmapFromResource(String fileName,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(fileName, options);
}
Your getview is HUGE.
Your (if convertview==null) has basically almost no effect as you're setting up the view again anyways.
What you need to do is refactor the getview to not be so slow. one thing you can do is create a class that has all the findviews done already for you and put that then in the .tag of the converted view. change your onclicks to use that as well, in a manner where you don't have to recreate them(other ways to do that exist as well).
ideally your code for if you have a converted view already should be just the .settexts().
depending on the size of your list, you could just get away with creating a view for each callist and avoid recycling the converted views alltogether, in such case you could just create them in advance.
also depending on the size of your list you could just get away with creating a just a simple linearlayout instead inside a scrollview. if your list isn't huge and it's not for some old phones, it works just fine as well (Don't knock on this as bad advice until you try on your phone how huge it can be before a listview starts making more sense).
Related
I'm currently building an android app to apply filter on a bmp. I'm using the gpuimage lib. How it's done is that the bmp is show in a ListView which contain 8 filters. When scrolling down/up, we request the filtering of the bmp (b&w, sepia...). As the rendering take times, I display in my listview the original bmp and it's replace by the filtered image once done
Here is how the activity do it.
private ListView mFiltersView;
private void FiltersPreview(final Bitmap mBmp) {
boolean mPreview = true;
mPreviewBitmap = resizeBitmap(mBmp);
mCameraImageFiltersAdapter = new CameraImageFiltersAdapter(this, mPreviewBitmap, mPreview);
mFiltersView.setAdapter(mCameraImageFiltersAdapter);
mFiltersView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
mCameraImageFiltersAdapter.cancel();
mFiltersView.cancelPendingInputEvents();
mFiltersView.setAdapter(null);
final CameraFiltersFactory effect = (CameraFiltersFactory) v.findViewById(R.id.filteredImage).getTag();
BufferAnimationDrawable Loading = new BufferAnimationDrawable(getBaseContext());
Loading.setPrimaryColor(0xfffb633e);
LoadingScreen.setImageDrawable(Loading);
LoadingScreen.setVisibility(View.VISIBLE);
mFiltersView.setVisibility(View.GONE);
getActionBar().hide();
if(mBmp == null) Log.d(TAG,"mBitmap is null");
effect.save(mBmp, position, new GPUImage.OnPictureSavedListener() {
#Override
public void onPictureSaved(Uri uri) {
final Intent previewIntent = new Intent(FiltersSelectionActivity.this, PicturePreviewActivity.class);
previewIntent.setData(uri);
previewIntent.setAction(mActionTypes.toString());
previewIntent.putExtra("Type", "Filtered");
startActivityForResult(previewIntent, 0);
}
});
}
});
}
The mCameraImageFiltersAdapter is defined as :
public CameraImageFiltersAdapter(/*Activity activity, */Context c, Bitmap current, boolean isPreview) {
mContext = c;
mPreview = isPreview;
mCurrentBitmap = current;
mFilterIds = CAMERA_IMAGE_FILTERS == null
|| CAMERA_IMAGE_FILTERS.length == 0 ?
mFilterIds : CAMERA_IMAGE_FILTERS;
mFakeBitmap = mCurrentBitmap;
mFakeBitmap.setDensity(0);
mExecutorService = Executors.newFixedThreadPool(5);
}
private final Handler handler = new Handler();// handler to display images
public int getCount() { return mFilterIds.length; }
public long getItemId(int position) { return 0; }
public Object getItem(int position) { return null; }
#Override public int getViewTypeCount() { return mFilterIds.length; }
#Override public int getItemViewType(int position) { return position; }
final int stub_id = R.drawable.filter_preview_stub;
public ImageView filteredImage = null;
public TextView filteredText = null;
#SuppressLint("InflateParams")
public View getView(int position, View convertView, ViewGroup parent) {
mPosition = position;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_filter, null);
filteredImage = (ImageView) convertView.findViewById(R.id.filteredImage);
filteredImage.setImageBitmap(mFakeBitmap);
filteredText = (TextView) convertView.findViewById(R.id.textview);
queueFiltered(filteredImage, mPosition, filteredText);
}
return convertView;
}
private void queueFiltered(final ImageView view, final int position, final TextView text) {
final CameraFiltersFactory holder = new CameraFiltersFactory(mContext, view, text);
if(holder != null)
mExecutorService.submit(new FilterLoader(holder, position));
}
public void cancel() {
if(mExecutorService != null) mExecutorService.shutdownNow();
}
The CameraFilterFactoy is just a easy to use class to access to the GPUImage
public class CameraFiltersFactory {
private static final String TAG = CameraFiltersFactory.class.getSimpleName();
private final ImageView mImageView;
private final GPUImage mCameraImage;
private Bitmap mFilteredBitmap;
private int mCurrentEffect;
private Context mContext;
private Activity mActivity = null;
private TextView mFiltersText;
public CameraFiltersFactory(Context c, ImageView filteredImage, TextView filteredText) {
mImageView = filteredImage;
mImageView.setTag(this);
mContext = c;
mCameraImage = new GPUImage(mContext);
if(filteredText != null) {
mFiltersText = filteredText;
mFiltersText.setVisibility(View.VISIBLE);
}
if(mImageView != null) mActivity = (Activity) mContext;
}
public void generateFilteredBitmap(Bitmap bmp, int filtertype, boolean isPreview) {
mCurrentEffect = filtertype;
switch (mCurrentEffect) {
case R.id.blackandwhite:
mCameraImage.setFilter(new GPUImagePlusGrayscaleFilter(isPreview));
break;
case R.id.cool:
mCameraImage.setFilter(new GPUImagePlusCoolFilter(isPreview));
break;
case R.id.cool2:
mCameraImage.setFilter(new GPUImagePlusCool2Filter(isPreview));
break;
case R.id.faded:
mCameraImage.setFilter(new GPUImagePlusFadedFilter(mContext, isPreview));
break;
case R.id.hipster:
mCameraImage.setFilter(new GPUImagePlusHipsterFilter(mContext, isPreview));
break;
case R.id.sepia:
mCameraImage.setFilter(new GPUImagePlusSepiaFilter(isPreview));
break;
case R.id.vivid:
mCameraImage.setFilter(new GPUImagePlusVividFilter(isPreview));
break;
case R.id.warm:
mCameraImage.setFilter(new GPUImagePlusWarmFilter(mContext, isPreview));
break;
default:
Log.d("NONE", "None FAIT CHIER");
break;
}
mCameraImage.deleteImage();
mCameraImage.setImage(bmp);
mFilteredBitmap = mCameraImage.getBitmapWithFilterApplied();
}
#SuppressLint("SimpleDateFormat")
public void save(Bitmap bitmap, int filter_id, GPUImage.OnPictureSavedListener ofsl) {
Log.d("NONE", "Save request with filter: "+filter_id);
generateFilteredBitmap(bitmap, filter_id, false);
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = timeStamp + ".jpg";
mCameraImage.saveToPictures(mFilteredBitmap, CameraSettings.CAMERA_ROLL_FOLDER, fileName, true, ofsl);
}
}
This code is working fine in the List view.
Once I click on a picture from the ListView, I get his position, stop Executor from the adapter and ask to the FilterFactory for a rendering.
If In the listview I wait that all the preview list image are showing the filter rendering, and then I click, the filter is correctly applied on the original bmp.
In case, I'm scrolling quickly down and the GPU is in progress to render the preview iamge and then click, the original bmp is not filtered. I have check that in both case, when I click the list view give the right filter position and that the case. It seems that if a rendering is in progress, I'm not able to cancel and ask for a new one.
Any idea why ? Any idea if I can cancel the current GPU rendering and start a new one. ?
Thanks
Use gpuImage.deleteImage(); method in your adapter class after getting the bitmap from the gpu image.
Before setting your image to the GPUImage, create your image thumbnail. So it will load fast.
GPUImaage gpuImage=new GPUImage(context);
gpuImage.setImage(your image);
gpuImage.setFilter(choose your filter);
gpuImage.requestRender();
imageView.setImageBitmap(gpuImage.getBitmapWithFilterApplied());
gpuImage.deleteImage();
I have static ImageView, and its content's changed randomly when I click a button.
How to make Image roll like this:
public void changeImage() {
int pickedNumber = FunctionUtils.randomInRange(arrImgList.size());
Bitmap bmImg = BitmapFactory.decodeFile(arrImgList.get(pickedNumber));
img.setImageBitmap(bmImg);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (count < 30) {
changeImage();
count++;
}
}
}, 50);
}
i want to add effect when changing image:
Bitmap bmImg = BitmapFactory.decodeFile(arrImgList.get(pickedNumber));
img.setImageBitmap(bmImg);
i using Android Wheel at:
https://code.google.com/p/android-wheel/
My adapter:
private class SlotMachineAdapter extends AbstractWheelAdapter {
// Layout inflater
private Context context;
/**
* Constructor
*/
public SlotMachineAdapter(Context context) {
this.context = context;
}
#Override
public int getItemsCount() {
return arrImgList.size();
}
// Layout params for image view
final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
ImageView img;
if (cachedView != null) {
img = (ImageView) cachedView;
} else {
img = new ImageView(context);
}
img.setLayoutParams(params);
Bitmap bitmap = BitmapFactory.decodeFile(arrImgList.get(index));
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, widthWheel, heightWheel, true);
bitmap.recycle();
img.setImageBitmap(scaled);
return img;
}
}
I am attempting to load picture files in the form of thumbnails from my internal storage to a list view. Currently, I am using a ViewHolder, but the loading is still choppy when scrolling so I am going to try to use an AsyncTask. However I can't get my head around how to structure the AsyncTask as most of the examples I've found deal with downloading from a website. I'm not even sure if I should subclass it in my BaseAdapter or in my MainActivity. I have added my baseadapter below with the unfinished AsyncTask at the bottom. How do I structure this to either: use the AsyncTask to assist the ViewHolder, or directly pass an image to AsyncTask and have it return the bitmap so the ListView will scroll smoothly?
public class ListViewAdapter extends BaseAdapter {
private static final int WIDTH = 250;
private static final int HEIGHT = 250;
private static final int ROTATION = 90;
private final static String TAG = "Pictures";
private final ArrayList<SelfieObject> mItems = new ArrayList<SelfieObject>();
private Context mContext;
private File mStorageDir;
private String mFilePrefix;
public ListViewAdapter(Context context, File storageDir, String filePrefix) {
mContext = context;
mStorageDir = storageDir;
mFilePrefix = filePrefix;
//get file list from storage to display
InitializeItemsFromStorage(storageDir, filePrefix);
}
//this method creates an array of files stored on the device or SD card.
private void InitializeItemsFromStorage(File storageDir, String prefix) {
log("in InitializeItemsFromStorage()");
mItems.clear();
File[] files = getFiles(storageDir, prefix);
for (File f : files) {
SelfieObject selfie = new SelfieObject(f);
mItems.add(selfie);
}
}
public void Update() {
log("in Update()");
InitializeItemsFromStorage(mStorageDir, mFilePrefix);
notifyDataSetChanged();
}
/*
* return the list of file objects of the given directory that begin with
* the prefix.
*/
private File[] getFiles(File storageDir, final String prefix) {
FileFilter fileFilter = new FileFilter() {
#Override
public boolean accept(File pathname) {
if (pathname.isFile() && pathname.getName().startsWith(prefix))
return true;
else
return false;
}
};
File[] result = storageDir.listFiles(fileFilter);
return result;
}
public int getCount() {
log("in getCount()");
return mItems.size();
}
public Object getItem(int position) {
log("in getItem()");
return mItems.get(position);
}
public long getItemId(int position) {
log("in getItemId()");
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.v(TAG, "in getView for position " + position +
", convertView is " +
((convertView == null)?"null":"being recycled"));
View newView = convertView;
ViewHolder holder;
if (null == convertView) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
newView = inflater.inflate(R.layout.single_item, null);
holder = new ViewHolder();
holder.description = (TextView) newView.findViewById(R.id.textView1);
holder.picture = (ImageView) newView.findViewById(R.id.imageView1);
newView.setTag(holder);
} else {
holder = (ViewHolder) newView.getTag();
}
holder.picture.setScaleType(ImageView.ScaleType.CENTER_CROP);
SelfieObject selfie = (SelfieObject) getItem(position);
setPic(holder.picture, new Point(WIDTH, HEIGHT), selfie.getPath());
TextView textView = (TextView) holder.description;
textView.setText(selfie.getName());
log("Exiting getView");
return newView;
}
static class ViewHolder {
ImageView picture;
TextView description;
}
public void add(SelfieObject listItem) {
mItems.add(listItem);
notifyDataSetChanged();
}
public ArrayList<SelfieObject> getList(){
return mItems;
}
public void removeAllViews(){
mItems.clear();
this.notifyDataSetChanged();
}
public static void setPic(ImageView imageView, Point requestedSize,
String pathName) {
// set the dimensions of the View
int targetW = requestedSize.x;
int targetH = requestedSize.y;
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW / targetW, photoH / targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(pathName, bmOptions);
imageView.setImageBitmap(bitmap);
imageView.setRotation(ROTATION);
}
//Automation logging tool
public void log(String s){
Log.i(TAG, s);
}
private class AsyncTaskLoadImage extends AsyncTask<Object, Void, Bitmap>{
private ImageView image;
private String path;
public AsyncTaskLoadImage(ImageView image){
this.image = image;
this.path = image.getTag().toString();
}
#Override
protected Bitmap doInBackground(Object... params) {
Bitmap bitmap = null;
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + path);
if(file.exists()){
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
}
return bitmap;
}
}
}
The AsyncTask should do whatever is too slow to do in the UI thread. In this example, fetching and downsampling the image, and setting up the ViewHolder should be done in the background.
However, I suggest you do not try and fix the ListView by yourself, but rather have a look at already existing solutions, like: https://github.com/lucasr/smoothie
Also, I highly suggest you downsample your bitmaps, otherwise they will consume a lot of excess computing time and memory. While the previous can lag your UI when scrolling, the latter will get you a nice OutOfMemoryException. See: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
I have a global bitmap cache using LruCache class. when loading thumbnails for the listview, the cache is used first. It works just OK.
But one issue is: sometimes the Bitmap instance from the cache cannot be displayed on the listview. it seems such bitmap from cache is not valid any more. I have checked the bitmap from cache if it is not null and if it is not recycled, but it still seems such bitmap cannot be displayed (even it is not null and it is not recycled).
The cache class:
public class ImageCache {
private LruCache<String, Bitmap> mMemoryCache;
private static ImageCache instance;
public static ImageCache getInstance() {
if(instance != null) {
return instance;
}
instance = new ImageCache();
instance.initializeCache();
return instance;
}
protected void initializeCache() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
}
public Bitmap getImage(String url) {
return this.mMemoryCache.get(url);
}
public void cacheImage(String url, Bitmap image) {
this.mMemoryCache.put(url, image);
}
}
and the code to use the cache is in the Adapter class which is subclass of CursorAdapter:
final ImageCache cache = ImageCache.getInstance();
// First get from memory cache
final Bitmap bitmap = cache.getImage(thumbnailUrl);
if (bitmap != null && !bitmap.isRecycled()) {
Log.d(TAG, "The bitmap is valid");
viewHolder.imageView.setImageBitmap(bitmap);
}
else {
Log.d(TAG, "The bitmap is invalid, reload it.");
viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);
// use the AsyncTask to download the image and set in cache
new DownloadImageTask(context, viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
}
the code of DownloadImageTask:
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
private String url;
private String dir;
private String filename;
private Context context;
public DownloadImageTask(Context context, ImageView imageView, String url, String dir, String filename) {
this.mImageView = imageView;
this.url = url;
this.filename = filename;
this.dir = dir;
this.context = context;
this.cache = cache;
}
protected Bitmap doInBackground(String... urls) {
// String urldisplay = urls[0];
final Bitmap bitmap = FileUtils.readImage(context, dir, filename, url);
return bitmap;
}
protected void onPostExecute(Bitmap result) {
final ImageCache cache = ImageCache.getInstance();
if(result != null) {
cache.put(url, result);
mImageView.setImageBitmap(result);
}
}
}
any help will be appreciated. Thanks!
Updates: I have followed the link suggested by greywolf82: section "Handle Configuration Changes". I put the following attribute in my activity class and the two fragment classes:
public LruCache mMemoryCache;
In the activity class, I try to initialize the cache when calling the fragment:
// Get the cache
mMemoryCache = mIndexFragment.mRetainedCache;
if (mMemoryCache == null) {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
// Initialize the cache
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
Log.d(TAG, "Initialized the memory cache");
mIndexFragment.mRetainedCache = mMemoryCache;
}
in the fragment class:
setRetainInstance(true);
and I pass the cache instance to the adapter constructor so that the adapter can use the cache.
but I still got the same issue.
Update 2:
the two adapter classes with changes to accept the LruCache instance:
NewsCursorAdapter:
public class NewsCursorAdapter extends CursorAdapter {
private static final String TAG = "NewsCursorAdapter";
private LruCache<String, Bitmap> cache;
private Context mContext;
public NewsCursorAdapter(Context context, LruCache<String, Bitmap> cache) {
super(context, null, false);
this.mContext = context;
this.cache = cache;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
final Setting setting = ApplicationContext.getSetting();
// Get the view holder
ViewHolder viewHolder = (ViewHolder) view.getTag();
final String thumbnail = cursor.getString(NewsContract.Entry.THUMBNAIL_CURSOR_INDEX);
if(thumbnail != null) {
String pictureDate = cursor.getString(NewsContract.Entry.PIC_DATE_CURSOR_INDEX);
final String dir = "thumbnails/" + pictureDate + "/";
final String filepath = thumbnail + "-small.jpg";
final String thumbnailUrl = setting.getCdnUrl() + dir + filepath;
//final ImageCache cache = ImageCache.getInstance();
// First get from memory cache
final Bitmap bitmap = cache.get(thumbnailUrl);
if (bitmap != null && !bitmap.isRecycled()) {
Log.d(TAG, "The bitmap is valid: " + bitmap.getWidth());
viewHolder.imageView.setImageBitmap(bitmap);
}
else {
Log.d(TAG, "The bitmap is invalid, reload it.");
viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);
new DownloadImageTask(viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
}
}
else {
viewHolder.imageView.setVisibility(View.GONE);
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.listview_item_row, parent,
false);
// Initialize the view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.titleView = (TextView) view.findViewById(R.id.title);
viewHolder.timeView = (TextView) view.findViewById(R.id.news_time);
viewHolder.propsView = (TextView) view.findViewById(R.id.properties);
viewHolder.imageView = (ImageView) view.findViewById(R.id.icon);
view.setTag(viewHolder);
return view;
}
static class ViewHolder {
TextView titleView;
TextView timeView;
TextView propsView;
ImageView imageView;
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
private String url;
private String dir;
private String filename;
public DownloadImageTask(ImageView imageView, String url, String dir, String filename) {
this.mImageView = imageView;
this.url = url;
this.filename = filename;
this.dir = dir;
}
protected Bitmap doInBackground(String... urls) {
final Bitmap bitmap = FileUtils.readImage(mContext, dir, filename, url);
return bitmap;
}
protected void onPostExecute(Bitmap result) {
//final ImageCache cache = ImageCache.getInstance();
if(result != null) {
cache.put(url, result);
mImageView.setImageBitmap(result);
}
}
}
}
the list adapter, NewsTopicItemAdapter:
public class NewsTopicItemAdapter extends ArrayAdapter<NewsTopicItem> {
private Context context = null;
private EntryViewHolder viewHolder;
private HeaderViewHolder headerViewHolder;
private LruCache<String, Bitmap> mCache;
public NewsTopicItemAdapter(Context context, List<NewsTopicItem> arrayList, LruCache<String, Bitmap> cache) {
super(context, 0, arrayList);
this.context = context;
this.mCache = cache;
}
public void setItems(List<NewsTopicItem> items) {
this.addAll(items);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final NewsTopicItem item = getItem(position);
View view;
if(!item.isHeader()) {
view = this.getEntryView((NewsTopicEntry)item, convertView, parent);
}
else {
view = this.getHeaderView((NewsTopicHeader)item, convertView, parent);
}
return view;
}
protected View getEntryView(NewsTopicEntry newsItem, View convertView, ViewGroup parent) {
View view;
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
viewHolder = new EntryViewHolder();
view = inflater.inflate(R.layout.listview_item_row, parent,
false);
// Initialize the view holder
viewHolder.titleView = (TextView) view.findViewById(R.id.title);
viewHolder.timeView = (TextView) view.findViewById(R.id.news_time);
viewHolder.propsView = (TextView) view.findViewById(R.id.properties);
viewHolder.imageView = (ImageView) view.findViewById(R.id.icon);
view.setTag(viewHolder);
viewHolder.propsView.setText(newsItem.getSource());
if (newsItem.getThumbnail() != null) {
final String dir = "thumbnails/" + newsItem.getPictureDate() + "/";
final String filepath = newsItem.getThumbnail() + "-small.jpg";
final String thumbnailUrl = "http://www.oneplusnews.com/static/" + dir + filepath;
//final ImageCache cache = ImageCache.getInstance();
// First get from memory cache
final Bitmap bitmap = mCache.get(thumbnailUrl);
if (bitmap != null && !bitmap.isRecycled()) {
viewHolder.imageView.setImageBitmap(bitmap);
} else {
viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);
new DownloadImageTask(viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
}
}
else {
viewHolder.imageView.setVisibility(View.GONE);
}
viewHolder.titleView.setText(newsItem.getTitle());
viewHolder.timeView.setText(DateUtils.getDisplayDate(newsItem.getCreated()));
return view;
}
protected View getHeaderView(NewsTopicHeader header, View convertView, ViewGroup parent) {
View view;
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
headerViewHolder = new HeaderViewHolder();
view = inflater.inflate(R.layout.news_list_header, parent,
false);
// Initialize the view holder
headerViewHolder.topicView = (TextView) view.findViewById(R.id.topic);
view.setTag(headerViewHolder);
final View imageView = view.findViewById(R.id.more_icon);
imageView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Start the Fragement
}
});
Topic topic = header.getTopic();
if(topic.isKeyword()) {
headerViewHolder.topicView.setText(topic.getName());
}
else {
// This is a hack to avoid error with - in android
headerViewHolder.topicView.setText(ResourceUtils.getStringByName(context, topic.getName()));
}
return view;
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
private String url;
private String dir;
private String filename;
public DownloadImageTask(ImageView imageView, String url, String dir, String filename) {
this.mImageView = imageView;
this.url = url;
this.filename = filename;
this.dir = dir;
}
protected Bitmap doInBackground(String... urls) {
final Bitmap mIcon11 = FileUtils.readImage(context, dir, filename, url);
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
//final ImageCache cache = ImageCache.getInstance();
if(result != null) {
mCache.put(url, result);
mImageView.setImageBitmap(result);
}
}
}
static class EntryViewHolder {
TextView titleView;
TextView timeView;
TextView propsView;
ImageView imageView;
TextView topicView;
}
static class HeaderViewHolder {
TextView topicView;
}
}
Update 3: I have attached the debug information from eclipse: the 1st picture is the working bitmap, and the 2nd one is the non-working bitmap from cache. I didn't find anything suspicious.
The debug information of the working bitmap from the cache:
The debug information of the non-working bitmap from the cache:
Finally I figured out the problem. It is becuase of the adapter. in the adapter I have set some ImageView as invisible if no thumbnail is needed. when the user scrolls the list view, such ImageView instance will be reused, but the visibility is not updated.
so the cache itself is OK now. The solution is to check the visibility of the ImageView and update it if needed.
Anyway thanks a lot to greywolf82 for your time and the tip about the singleton pattern.
The singleton pattern is the evil :) Please avoid it completely and use a fragment with setReteainInstance(true) as explained here
I want to load images from a server, AFTER loading the data in a list view.
I know that a lot of topics existing for this problem but I haven't found the solution...
So this is my code :
//asyncTackClass for loadingpictures
public class LoadImagesThread extends AsyncTask<Bundle, Void, Bitmap> {
private ImageView view;
private Bitmap bm;
private Context context;
private final WeakReference<ImageView> imageViewReference;
private final String BUNDLE_URL = "url";
private final String BUNDLE_NAME = "name";
private final String BUNDLE_BM = "bm";
public LoadImagesThread(Context context, ImageView view) {
this.context=context;
imageViewReference = new WeakReference<ImageView>(view);
}
#Override
protected Bitmap doInBackground(Bundle... b) {
Bitmap bm =null;
if (StorageHelper.getBitmap(b[0].getString(BUNDLE_NAME)) != null) { // Check the sdcard
bm = StorageHelper.getBitmap(b[0].getString(BUNDLE_NAME));
Log.w("LoadImagesThread", "Get image from sdcard : "+b[0].getString(BUNDLE_NAME));
} else { // Check the server
bm = ServiceHelper.getBitmapFromURL(b[0].getString(BUNDLE_URL));
StorageHelper.saveBitmap(bm, b[0].getString(BUNDLE_NAME)); // Save image on sdcard
Log.w("LoadImagesThread", "Get image from server : "+b[0].getString(BUNDLE_NAME));
}
return bm;
}
#Override
protected void onPostExecute(final Bitmap bm) {
super.onPostExecute(bm);
if (bm != null){ //if bitmap exists...
view = imageViewReference.get();
// Fade out
Animation fadeOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fadeoutimage);
fadeOutAnimation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
// Fade in
view.setImageBitmap(bm);
Animation fadeInAnimation = AnimationUtils.loadAnimation(context, R.anim.fadeinimage);
view.startAnimation(fadeInAnimation);
}
});
// Launch the fadeout
view.startAnimation(fadeOutAnimation);
}else{ //if not picture, display the default ressource
view.setImageResource(R.drawable.productcarre);
}
}
}
The code is used to display a Bitmap in a ImageView
And this is the adapter:
public class ListViewShoplistStoresAdapter extends BaseAdapter {
private ArrayList<Shop> shopList;
private Activity activity;
private HashMap<Integer, ImageView> views;
private final String BUNDLE_URL = "url";
private final String BUNDLE_NAME = "name";
private final String BUNDLE_POS = "pos";
private final String BUNDLE_ID = "id";
public ListViewShoplistStoresAdapter(Activity activity, ArrayList<Shop> shopList) {
super();
this.activity = activity;
this.shopList = shopList;
this.views = new HashMap<Integer, ImageView>();
}
public int getCount() {
return shopList.size();
}
public Object getItem(int position) {
return shopList.get(position);
}
public long getItemId(int position) {
return shopList.get(position).getId();
}
private class ViewHolder {
public TextView store;
public TextView name;
public ImageView ImageStore;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder view;
LayoutInflater inflator = activity.getLayoutInflater();
if(convertView == null) {
view = new ViewHolder();
convertView = inflator.inflate(R.layout.listviewshops, null);
view.store = (TextView) convertView.findViewById(R.id.store);
view.name = (TextView) convertView.findViewById(R.id.name);
view.ImageStore = (ImageView) convertView.findViewById(R.id.imgstore);
convertView.setTag(view);
}else {
view = (ViewHolder) convertView.getTag();
}
Typeface regular=Typeface.createFromAsset(activity.getAssets(), "fonts/RobotoRegular.ttf");
view.store.setTypeface(regular);
Typeface light=Typeface.createFromAsset(activity.getAssets(), "fonts/RobotoLight.ttf");
view.store.setTypeface(light);
Brand brand = StorageHelper.getBrand(activity, shopList.get(position).getBrandId());
if (brand == null) {
Log.e("SetShopInAdapter","Brand null");
Toast.makeText(activity, "Impossible d'afficher la liste de magasins", Toast.LENGTH_LONG).show();
} else {
view.store.setText(brand.getName());
view.name.setText(shopList.get(position).getName());
view.ImageStore.setImageResource(R.drawable.productcarre);
}
Bundle b = new Bundle();
//url of the pict
b.putString(BUNDLE_URL, ServiceHelper.getImageUrl("brand", brand.getName()));
// name of image
b.putString(BUNDLE_NAME, ServiceHelper.getCleanImageName(brand.getName()));
//position in the listView
b.putInt(BUNDLE_POS, position);
//id of the current object
b.putInt(BUNDLE_ID, brand.getId());
//put info in the map in order to display in the onPostExecute
if(views.get(position)==null){
views.put(position, view.ImageStore);
// launch thread
new LoadImagesThread(activity.getApplicationContext(), view.ImageStore).execute(b);
}
return convertView;
}
}
So, when I used a GridView, there were no problems, but when I use a ListView the image is changed only in the first item !
Example:
I want to display product images for "car", "house" and "apple" items.
The code will launch the thread and all images (car then house and finally apple) will be displayed in the first item (the car item)...
And the house and the apple while not have images !!
Do you know what I should do ?
Thanks
There is a lot about this here on SO..
asynchrnous loading like that is called "Lazy Loading"
https://github.com/thest1/LazyList
for full implementation of such a process with list
For loading images in general i recomend this :
https://github.com/nostra13/Android-Universal-Image-Loader
You can use volley to improve in listview performance.
Take a look at this simple example: Android Custom ListView with Image and Text using Volley