Android Listview scroll lag with view holder - android

I have noticed that a ListView in my application has started to stutter quite badly all of a sudden.
I am using Volley to load images for my listview items - downloading and caching the images are fine and scroll smooth as butter.
However I currently have a spinner that sits on top of the NetworkImageView while I wait for the image to load. The lag comes in once the image has successfully loaded - I set the spinner to be invisible and the image to visible. Changing the visibility of these items seems to be the source of the lag.
I am currently using the View Holder pattern, my onResponseLoad looks like the following:
#Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null){ //Check that the image is not null
ProgressBar progress = holder.getProgress(); //Find Spinner - this doesnt cause lag
progress.setVisibility(View.GONE); //Hide spinner (This causes lag)
img.setVisibility(View.VISIBLE); //Image is a network image from the holder (This causes lag)
}
}
(Note that commenting out those two offending lines results in buttery smooth scrolling again)
The other strange thing is that I haven't touched this part of the application in some time and in my current live version, as well as previous commits there is no lag. Comparing my current code base to previous non-lagging versions show that there has been 0 change to the code surrounding this aspect of the application. Furthermore other lists that I have implemented using almost the exact same technique have not experienced this issue.
The only thing I can think of that could be different is that I am now using the latest version of Gradle - although I don't think that should have an impact at run-time.
I am at a total loss as to what is going on, would appreciate any insight on what I should be doing to achieve smooth ListView scrolling (or what may have lead to my implementation's degradation)
EDIT: Posting code of getView() as requested
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View placeSelectorView = convertView;
PlaceViewHolder placeSelectorHolder = null;
if(placeSelectorView == null){ //If we are creating the row for the first time
LayoutInflater inflater = (LayoutInflater) mCtx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); //Inflate the view
placeSelectorView = inflater.inflate(R.layout.place_selector, parent, false); //Get the view
placeSelectorHolder = new PlaceViewHolder(placeSelectorView); //Create holder object
placeSelectorView.setTag(placeSelectorHolder); //Attach reference to the view
}else{
placeSelectorHolder = (PlaceViewHolder) placeSelectorView.getTag(); //Load holder from memory
if(!placeSelectorHolder.isHasImage()){ //Need to optimise this
placeSelectorHolder.getLayout().addView(placeSelectorHolder.getrLayoutThumbnail(), 0);
placeSelectorHolder.setHasImage(true);
}
if(!placeSelectorHolder.isSpinnerVisible()){
placeSelectorHolder.getProgressBar().setVisibility(View.VISIBLE);
placeSelectorHolder.getPlaceImg().setVisibility(View.GONE);
placeSelectorHolder.setSpinnerVisible(true);
}
}
POI place = (values.get(position)); //Get POI object for the place
POI parentPlace = getParent(place); //Get parent POI for place
placeSelectorHolder.getPlaceName().setText(place.getName());
if(parentPlace != null){ //If place has a parent POI
placeSelectorHolder.getParentPlaceName().setText(parentPlace.getName());
}else{ //We don't want the parent text in the view
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) placeSelectorHolder.getParentPlaceName().getLayoutParams();
layoutParams.weight = 0; //Setting weight to 0 will remove it from the LinearLayout
placeSelectorHolder.getParentPlaceName().setLayoutParams(layoutParams);
}
final PlaceViewHolder holder = placeSelectorHolder;
loadThumbnail(holder, place);
return placeSelectorView;
}
public void loadThumbnail(final PlaceViewHolder placeSelectorHolder, POI place){
RealmList<poiPhoto> photos = place.getPhotos();
String mUrl;
if(!photos.isEmpty()){
mUrl = photos.get(0).getSmall();
}else{
mUrl = "";
}
final NetworkImageView placeImg = placeSelectorHolder.getPlaceImg();
if(!mUrl.equals("")){ //If there is an Image Available
ImageLoader imageLoader = ServerSingleton.getInstance(getContext()).getImageLoader(); //Get volley imageloader from Singleton
imageLoader.get(mUrl, new ImageLoader.ImageListener() { //Custom get so we can use override onResponse and OnErrorResponse
#Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null){ //Check that the image is not null
ProgressBar progressBar = placeSelectorHolder.getProgressBar(); //Find Spinner
placeImg.setVisibility(View.VISIBLE);
if(progressBar != null) progressBar.setVisibility(View.GONE); //Make the spinner invisible
placeSelectorHolder.setSpinnerVisible(false);
}
}
#Override
public void onErrorResponse(VolleyError error) {
//TO-DO: Get an error image
}
});
placeImg.setImageUrl(mUrl, imageLoader); //Send the request
placeSelectorHolder.setHasImage(true);
}else{ //There is no image
LinearLayout layout = placeSelectorHolder.getLayout(); //Find the horizontal layout
layout.removeView(placeSelectorHolder.getrLayoutThumbnail()); //Remove the Thumbnail layout
placeSelectorHolder.setHasImage(false);
}
}

Related

Alternative to adding dynamic view inside of a ViewHolder RecyclerView Adapter

I am currently adding ImageViews and setting the images within a ViewHolder. The creation time is not smooth and the first scroll through is not smooth either. Any suggestion or an alternative to having smooth dynamic views within a viewholder?
ViewHolder
class ViewHolderRecipientExpanded extends RecyclerView.ViewHolder {
private LinearLayout mFlagLayout;
//
public ViewHolderRecipientExpanded(View v) {
super(v);
mFlagLayout = (LinearLayout) v.findViewById(R.id.flagLayout);
//
}
public void initiateRecipientsDetails() {
Recipient recipient = objectList.get(getAdapterPosition());
int totalAmountOfCountries = recipient.getFlags() != null ? recipient.getFlags().size() : 0;
//
int countriesLimitedToThree = recipient.getFlags() != null ? (totalAmountOfCountries > 3 ? 3 : totalAmountOfCountries) : 0;
View imageView;
ImageView image;
Drawable drawable;
for (int i = 0; i < countriesLimitedToThree; i++) {
imageView = inflater.inflate(R.layout.view_image_flag, null);
image = (ImageView) imageView.findViewById(R.id.image);
drawable = new BitmapDrawable(context.getResources(), recipient.getFlags().get(i).getFlag());
image.setImageDrawable(drawable);
mFlagLayout.addView(imageView);
}
}
}
Load your images asynchronously, and cache them in memory when they are not in view. This will not block the UI thread when loading images, allowing the user to scroll even if the images aren't loaded yet. Though, it will still take some time for the images to actually appear.
There are also a number of great libraries that will handle this for you:
Picasso
Glide
Fresco

Android function zoomImageFromThum(param1, param2) use a lot of RAM and CPU

How avoid the 40% of CPU usage and 70MB of RAM (start with 10MB) when a thumb is touched and the function start?
This is the scenario:
I have a ListView that can contains text or image.
The user can take pictures and that pictures are saved to sdcard0 and loaded by path to the relative ImageView.
I have some problem with the performance of this.
Adapter that save image or text and put it into a listview:
PS: i try with the ViewHolder for optimize the listview but i get some problem with the image loading with Picasso so i removed it!
#Override
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.evento_list_view_line, null);
ImageView img = (ImageView)convertView.findViewById(R.id.compiti_imageView);
TextView compito = (TextView)convertView.findViewById(R.id.compiti_text);
Compiti m = getItem(position);
if(m.getTestoCompito() != null){
compito.setText(m.getTestoCompito());
//Invisibile e non presente nel layout
img.setVisibility(View.GONE);
}else if(m.getPathFoto() != null){
//load img
Picasso.with(getContext())
.load(m.getPathFoto())
.resize(200, 200)
.centerCrop()
.into(img);
//Set the tag for retrieve the path of image from listview
img.setTag(m.getPathFoto());
}
return convertView;
}
As you can see i use the setTag(ImgPath) for retrieve the path from ImageView.
This is how i retrieve touched image if the image is in the ImageView:
// ListView Item Click Listener
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ImageView image_to_zoom = (ImageView) view.findViewById(R.id.compiti_imageView);
if (image_to_zoom.getDrawable() != null) {
//Image IN
String path = image_to_zoom.getTag().toString();
zoomImageFromThumb(image_to_zoom, path);
} else {
//Imane nope
Toast.makeText(getApplicationContext(), "No photo here!" , Toast.LENGTH_LONG).show();
}
}
});
// Retrieve and cache the system's default "short" animation time.
mShortAnimationDuration = getResources().getInteger(
android.R.integer.config_shortAnimTime);
I found the method zoomImageFromThumb(image_to_zoom, path); from google.developer and I made ​​some changes because the original code take images from R.resources and i need to take image from memory (folder Pictures).
My change:
private void zoomImageFromThumb(final View thumbView, String imagePath) {
// If there's an animation in progress, cancel it
// immediately and proceed with this one.
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Load the high-resolution "zoomed-in" image.
final ImageView expandedImageView = (ImageView) findViewById(
R.id.expanded_image);
//expandedImageView.setImageResource(imageResId);
Picasso.with(this)
.load(imagePath)
.into(expandedImageView);
If i set the path manually without the method getTag() the app load very fast the touched image, but with the method getTag() for retrieve the path of the image the app is very slow to load the zoom of touched image.
Some suggestion or help?
There is another way for retrieve the path of an image in a ImageView?

ListView deliver wrong position value in getView(..)

I'm very confusing with this.
Question is simple, I'm trying to resize the ImageView height, to do, I get the display width and add on it 0.25 of percentage.
Problem, if I set the new value of height outside of the post() ImageView method, the position parameter is deliver in getView() wrong. If I do it inside post() the first elements showed are not rescaled.
Read comments inside code.
public static class ViewHolder {
ImageView imgAvatar;
public void loadAvatar(SocialService socialService, long userId) {
try {
// SocialService.loadAvatar(..) is working with UniversalImageLoader.
socialService.loadAvatar(this.imgAvatar, userId, true, false);
} catch (Exception ex) {
Log.e("APPERROR", ex.getMessage());
}
}
}
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
final User user = this.users.get(position);
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.adapter_list, parent, false);
holder.imgAvatar = (ImageView) view.findViewById(R.id.people_list_avatar);
// With this commented snippet of code, the first 4 elements (showed in the top of the listview) are not rescaled.
// Position is deliver ok.
// The rest of elements that are going showing while scrolling works pretty fine.
// If scroll down and come back to the top then the 4 top first elements are showing rescaled.
/*final ImageView imgAvatarAux = holder.imgAvatar;
holder.imgAvatar.post(new Runnable() {
#Override
public void run() {
imgAvatarAux.getLayoutParams().height =
(int) ((Display.deviceWidth(PeopleAdapter.this.context) / 2) * 1.25F);
}
});*/
view.setTag(holder);
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
holder.loadAvatar(this.socialService, user.getId());
}
holder = (ViewHolder) view.getTag();
...
if (position == 0) {
// An interface method for another purposes...
this.waitFinish.onFinish();
}
return view;
}
Post your ListView Item layout adapter_list. Code fix [operations on list items outside if(){}else{}]:
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.loadAvatar(this.socialService, user.getId());

ImageViews in Android ListView are Showing Previously Loaded Images

I have an android ListView which uses an AsyncTask to download a different image for each ListViewItem. Each image gets added to an ArrayList<Bitmap> the first time it is downloaded and is accessed from the ArrayList<Bitmap> each time after that.
If I slowly scroll through the ListView, each image loads as expected, but if I start scrolling faster, the visible ListViewItems, which have not yet downloaded their images, start to display images from items that have already scrolled off the top of the screen.
Eventually everything syncs up and the images update via the onPostExecution call, but what is going wrong / how can I prevent the images from previous items from appearing in the lower items in the ListView ?
Here's the trimmed down code:
private ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>(500);
#Override
public View getItemView(final int position, View convertView, final ViewGroup parent) {
View itemView;
if (convertView == null) {
itemView = getInflater().inflate(ITEM_LAYOUT, parent, false);
} else {
itemView = convertView;
}
final ImageView mImageView = (ImageView) itemView.findViewById(R.id.app_component_joblistitemview_logo);
if (bitmapArray.size()>position && bitmapArray.get(position)!=null) {
// Item exists at bitmapArray[position], use it
Log.i("JobListView","Setting Existing for Position:"+position);
mImageView.setImageBitmap(bitmapArray.get(position));
} else {
// Have not downloaded this ListView[position] logo yet
bitmapArray.add(position,null); // add temporary null value until the image is downloaded
mImageView.setImageBitmap(null);
new DownloadImageTask().execute(logo.getLogoUUID(), mImageView, position);
}
return itemView;
}
// This AsyncTask downloads an image / logo from a specified URL and then updates the specified ImageView
private class DownloadImageTask extends AsyncTask<Object, Void, Bitmap> {
ImageView imageView;
Integer listViewPosition;
#Override
protected Bitmap doInBackground(Object... params) {
String urlString = "http://www.myimages.com/something/"+params[0].toString();
this.imageView = (ImageView)params[1];
this.listViewPosition = (Integer)params[2];
try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap myBitmap = BitmapFactory.decodeStream(input);
bitmapArray.set(listViewPosition,myBitmap);
return myBitmap;
} catch (Exception e) {
// Log exception
}
}
protected void onPostExecute(Bitmap result) {
imageView.setImageBitmap(result);
}
}
else {
// other methods, then set tag and run task
mImageView.setTag(logo.getLogoUUID());
new DownloadImageTask().execute(logo.getLogoUUID(), mImageView, position);
}
and
int uuid=-1;
protected Bitmap doInBackground(Object... params) {
uuid=params[0];
//other methods
}
protected void onPostExecute(Bitmap result) {
//if tag exists check validity
if(imageView.getTag()==null && imageView.getTag().equals(uuid))
imageView.setImageBitmap(result);
}
lets assume you have 2 pictures - first 10px square, second FHD. when your item needs FHD bitmap Downloader starts downloading, obvius. when you still scrolling and this item is "scrolled out" it apperas (as recycled/convert view) below (or above, depends on scroll direction). then you are setting download 10px bitmap which is ofc much faster. 10 px is set, but previously asynctask downloading fhd picture is still running and still keeps reference to imageView. so when fhd is downloaded it will replace this 10 px. for workaround you might set any identyfying param, your "uuid" is probably perfect for this
how listView and adapter works you may read here and here - check what is convertView in method getView and you might use ViewHolder pattern
PS. I see some null pointers in your code, because of cleaning? example:
View itemView;
final ImageView mImageView = //itemView is just created, findViewById will throw NPE
(ImageView) itemView.findViewById(R.id.app_component_joblistitemview_logo);

Is it necessary to store images in memory to show on list view in android

I'm showing list with one ImageView on every row of list.
For that, I download images from net in another AsyncTask using Drawable.createFromStream
And store them as Drawable in ArrayList which I pass to my Adapter class extending BaseAdapter class.
But the images are taken with high-resolution camera, so may be of very large size.
And I'm getting OutOfMemory error.
So my questions :
What is more efficient, storing images as drawable or as bitmap or any other format?
Am I doing right, by storing all images in memory(in array list). i.e. I'm thinking, once I get a image, I will show it on ImageView and will not store in ArrayList.
is there any way, I can compress the images after download, so they will take less space in memory.
My total code is present here
Android documentation provides a very good example showing how to handle bitmaps in your android app. The example uses an on-disk and in-memory cache and loads the images in the background. By doing so, the main UI thread is not slowed down by loading the images.
Loading Bitmaps effectively
In the example the images are loaded from picasa. It's easy, however, to adapt the example, so that pictures stored locally are used. You simply have to write your own ImageLoader extending from the 'ImageResizer':
public class ImageLoader extends ImageResizer {
public ImageLoader(Context context, int imageWidth, int imageHeight) {
super(context, imageWidth, imageHeight);
}
public ImageLoader(Context context, int imageSize) {
super(context, imageSize);
}
#Override
protected Bitmap processBitmap(Object data) {
return decodeSampledBitmapFromFile((String)data, imageWidth, imageHeight);
}
}
But to answer your question directly: it's ok to load images as Bitmaps. But you have to use a cache and weak references, so that the images can be garbage collected in case they are not visible on the screen. Caching them and using a background task for loading allows for a slick UI.
I don't see any efficiency in storing high-density images into memory - it's totally not recommended to store large ammount of images as bitmaps in memory (good for you that you have a good device ;))
See p.1
Try downscaling the images to fit the device's needs - that's not a simple job though. Also, see View.setTag(Object tag)
The adapter
public class MyImageListAdapter extends BaseAdapter implements ImageLoadingNotifier {
private LayoutInflater inflater = null;
public MyImageListAdapter() {
inflater = LayoutInflater)HomeActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return listImageInfo.size();
}
public Object getItem(int position) {
return listImageInfo.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.list_row, null);
}
TextView tvName = (TextView) vi.findViewById(R.id.tv_name);
TextView tvTime = (TextView) vi.findViewById(R.id.tv_time);
ImageView image = (ImageView) vi.findViewById(R.id.iv_image);
final Button btnDelete = (Button) vi.findViewById(R.id.btn_delete);
image.setImageDrawable(R.drawable.default_placeholder);//set default place-holder
new GetDrawableFromUrl(listImageInfo.get(position), vi).execute();
tvName.setText("Name: " + listImageInfo.get(position).getImage_name());
tvTime.setText("Date: " + listImageInfo.get(position).getDate_created());
btnDelete.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final int position = listView.getPositionForView((View) v.getParent());
positionOgBtnToDelete = position;
Log.v("delete btn clicked", "delete btn no: " + position);
Toast.makeText(HomeActivity.this, "Btn delete position: " + position, Toast.LENGTH_LONG).show();
showAlertToConfirmDelete();
}
});
return vi;
}
}
The AsyncTask GetDrawableFromUrl
public class GetDrawableFromUrl extends AsyncTask<Void, Void, Drawable> {
public ImageInfo imageInfoObj;
private ImageView view;
GetDrawableFromUrl(ImageInfo imageInfo, ImageView view) {
imageInfoObj = imageInfo;
this.view = view;
}
#Override
protected Drawable doInBackground(Void... params) {
try {
return Drawable.createFromStream(((java.io.InputStream) new java.net.URL(imageInfoObj.getImageUrl()).getContent()), "src_name");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Drawable drawable) {
if (drawable != null) {
//imageInfoObj.setImage(drawable);
this.view.setImageDrawable(drawable);
//listImageInfo.add(imageInfoObj); //this one is called when the json is parsed
showImagesInList(); //don't know what it does (??)
}
}
}
The JSON parsing
JSONArray jsonArray = jsonObj.getJSONArray("result");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObjInner = jsonArray.getJSONObject(i);
ImageInfo imageInfo = new ImageInfo();
imageInfo.setImageUrl("http://www.dvimaytech.com/markphoto/" + jsonObjInner.getString("image"));
//new GetDrawableFromUrl(imageInfo).execute(); //don't needed here
imageInfo.setEmail(jsonObjInner.getString("emailid"));
imageInfo.setImage_id(jsonObjInner.getString("image_id"));
imageInfo.setImage_name(jsonObjInner.getString("image_name"));
imageInfo.setAmount(jsonObjInner.getString("amount"));
imageInfo.setImage_description(jsonObjInner.getString("image_description"));
imageInfo.setDate_created(jsonObjInner.getString("date_created"));
listImageInfo.add(imageInfo);
}
And, the use of any kind of List of images becomes unnecesary :)
Instead of starting the async task (GetDrawableFromUrl) when parsing the json objects, you can start the task in getView(...) method. This way you will not be constrained to store the drawables into that ArrayList, since you'll be modifying the ImageView after the image was downloaded. And, by default, you can put a placeholder, until the image is downloaded (or in case there are some network errors).
This way the images will start downloading only when the getView method will be called for that specific item.
The bottom line is that each view from the ListView will keep a reference to it's specific drawable (that was set using vi.setTag(image).
If this helps somehow, you know what to do ;)
There is pretty good library calling AQuery. YOu can use it and simple get all stuff like memory and file caching by writting only 2 line of code. So you even wouldn't need to prepare a drawable, you can call it directly from Adapter.getView() callback.
AQuery aq = new AQuery(rowView);
aq.id(R.id.image).image(url, false, true);
Hope it help you!
From AQuery docs:
Down Sampling (handling huge images)
We are loading a huge image from the network, but we only need the image to be bigger than 200 pixels wide. Passing in the target width of 200 will down sample the image to conserve memory.Aquery will only down sample with power of 2 (2,4,8...) for good image quality and
efficiency.The resulting image width will be between 200 and 399 pixels
String imageUrl = "http://farm6.static.flickr.com/5035/5802797131_a729dac808_b.jpg";
aq.id(R.id.image1).image(imageUrl, true, true, 200, 0);

Categories

Resources