How to avoid image flickering in a listview

2020-03-25 03:39发布

I have a listivew that display a bunch of images. am using Universal Image Loader to load this images from files to imageviews.

This images have different dimensions and i want all of them to have same width but different height in respect to each image aspect ratio.

To achieve this, i have tried setting the following to my imageview

<ImageView
   android:layout_width = "400dp"
   android:layout_height="wrap_content"
   android:scaleType="centerCrop"
   android:adjustViewBounds="true"/>

The issue with this method is that there is a lot of flickering when one scrolls the listview since imageview height is not known in advance and images have to be scaled first using my width to calculate each image height in respect to it's aspect ratio.

How can i calculate each image height in advance instead of letting imageview handle it?

if i have an image which is 400 X 700, and i want the imageview to be 300px wide, how can i calculate imageview's height using my image dimension and maintain image aspect ratio? this can help avoid flickering wnen one scroll the listview.

5条回答
老娘就宠你
2楼-- · 2020-03-25 04:00

The reason for this flicker is that, in listview list items are reused. When re-used, the imageviews in the list item retains the old image reference which is displayed first. Later on once new image is downloaded, it starts to show. this causes the flickering behavior. To avoid this flickering issue, always clear the old image reference from the imageview when it is getting reused.

In your case, add holder.image.setImageBitmap(null); after holder = (ViewHolder) convertView.getTag();

So, your getView() method will look like:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    ...

    if (convertView == null) {
        LayoutInflater inflater = getLayoutInflater();
        convertView = inflater.inflate(viewResourceId, null);

        holder = new ViewHolder(convertView);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
        holder.image.setImageBitmap(null)
    }

    ...

    return convertView;
}
查看更多
对你真心纯属浪费
3楼-- · 2020-03-25 04:09

my suggestion is to use grid view to avoid flickering of images it will load at first time if it is same url , it will load from cache

 Glide.with(mContext)
            .load(item.getImageUrl())
            .into(holder.mIVGridPic);
查看更多
够拽才男人
4楼-- · 2020-03-25 04:13

How I solved it was by creating a Bitmap[] array variable to store images, then in adapter's getView(), I used position to check if image in Bitmap[] array is null or has value. If it is has value, then I use the value instead of calling the new DownloadImageTask() construct again.

For example:

YourCustomArrayAdapter.java

public class MyCustomArrayAdapter extends ArrayAdapter {
   private static Bitmap[] myListViewImageViewsArray = new Bitmap[listViewItemsArray.length];
   private String[] myListViewImageURLsArray = new String[listViewItemsArray.length]{
      "image_url_1",
      "image_url_2",
      ...
      ...
   };


   @Override
   public View getView(int position, View view, ViewGroup parent){
       CustomViewHolder vHolder;
        if(view == null){
            view = inflater.inflate(R.layout.movies_coming_soon_content_template, null, true);
            vHolder = new CustomViewHolder();
            vHolder.imageView = (AppCompatImageView) view.findViewById(R.id.my_cutom_image);
            vHolder.imageUrl = "";
            view.setTag(vHolder);
        }
        else{
            vHolder = (CustomViewHolder)view.getTag();
            // -- Set imageview src to null or some predefined placeholder (this is not really necessary but it might help just to flush any conflicting data hanging around)
            vHolder.imageView.setImageResource(null);
        }

       // ... 


       // -- THIS IS THE MAIN PART THAT STOPPED THE FLICKERING FOR ME
       if(myListViewImageViewsArray[position] != null){
          vHolder.imageView.setImageBitmap(myListViewImageViewsArray[position]);
       }else{
          new DownloadImageTask(position, vHolder.imageView).execute(vHolder.imageUrl);
       }
       // -- END OF THE FLICKERING CONTROL    
   }
}

Then, in your image downloader construct, after downloading the image, make an insertion into the Bitmap[] image array for that position. For example:

YourImageDownloaderClass.java

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
   AppCompatImageView imageView;
   int position;

   public DownloadImageTask(int position, AppCompatImageView imageView){
       this.imageView = imageView;
       this.position = position;
   }

   @Override
   protected Bitmap doInBackground(String...urls) {

       String urlOfImage = urls[0];
       Bitmap logo = null;
       try{            
           logo = BitmapFactory.decodeStream((InputStream) new URL(urlOfImage).getContent());
       }catch(Exception e){
           e.printStackTrace();            
       }
       return logo;
   }

   @Override
   protected void onPostExecute(Bitmap result){
       if(result != null) {
          YourCustomArrayAdapter.myListViewImageViewsArray [position] = result;
          imageView.setImageBitmap(result);                    
       }else{
          YourCustomArrayAdapter.myListViewImageViewsArray [position] = null;
          imageView.setImageResource(null);                  
       }
    }
}
查看更多
迷人小祖宗
5楼-- · 2020-03-25 04:15

After hours of research, i was able to know the method that i can use to calculate new imageview height while maintaining image aspect ratio.

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;

//Returns null, sizes are in the options variable
BitmapFactory.decodeFile("/sdcard/image.png", options);
int width = options.outWidth;
int height = options.outHeight;

//calculating image aspect ratio
float ratio =(float) height/(float) width;

//calculating my image height since i want it to be 360px wide
int newHeight = Math.round(ratio*360);

//setting the new dimentions
 imageview.getLayoutParams().width = 360;
 imageview.getLayoutParams().height = newHeight;

 //i'm using universal image loader to display image
 imaheview.post(new Runnable(){
  ImageLoader.getInstance().displayImage(imageuri,imageview,displayoptions);
 });
查看更多
Melony?
6楼-- · 2020-03-25 04:18

You can do something like this :

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

//Returns null, sizes are in the options variable
BitmapFactory.decodeFile("/sdcard/image.png", options);
int width = options.outWidth;
int height = options.outHeight;
//If you want, the MIME type will also be decoded (if possible)
String type = options.outMimeType;
查看更多
登录 后发表回答