如何避免列表视图中的图像闪烁

How to avoid image flickering in a listview

我有一个显示一堆图像的列表。我正在使用 Universal Image Loader 将此图像从文件加载到图像视图。

这些图片有不同的尺寸,我希望所有图片都具有相同的宽度但每个图片的纵横比不同。

为此,我尝试将以下内容设置到我的 imageview

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

此方法的问题是 滚动列表视图时会出现很多闪烁,因为事先不知道图像视图的高度,必须先使用我的宽度缩放图像来计算每个图像相对于纵横比的高度.

如何预先计算每个图像的高度而不是让 imageview 处理它?

如果我有一个 400 X 700 的图像,并且我希望 imageview 的宽度为 300px,我如何使用我的图像尺寸计算 imageview 的高度并保持图像的纵横比?这有助于避免滚动列表视图时出现闪烁。

出现这种闪烁的原因是,在列表视图中,列表项被重复使用。重新使用时,列表项中的图像视图保留首先显示的旧图像引用。稍后,一旦下载了新图像,它就会开始显示。这会导致闪烁行为。 为避免此闪烁问题,请在重新使用图像视图时始终清除旧图像引用。

在您的情况下,在 holder = (ViewHolder) convertView.getTag();[= 之后添加 holder.image.setImageBitmap(null); 19=]

因此,您的 getView() 方法将如下所示:

@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;
}

你可以这样做:

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;

经过几个小时的研究,我知道了在保持图像纵横比的同时可以用来计算新图像视图高度的方法。

    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);
 });

我的解决方法是创建一个 Bitmap[] 数组变量来存储图像,然后在适配器的 getView() 中,我使用位置检查 Bitmap[] 数组中的图像是否为 null 或具有价值。如果它有价值,那么我将使用该值而不是再次调用 new DownloadImageTask() 构造。

例如:

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    
   }
}

然后,在您的图像下载器构造中,在下载图像后,为该位置插入 Bitmap[] 图像数组。例如:

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);                  
       }
    }
}

我的建议是使用网格视图以避免第一次加载的图像闪烁如果相同url,它将从缓存中加载

 Glide.with(mContext)
            .load(item.getImageUrl())
            .into(holder.mIVGridPic);