如何在ListView中避免图像闪烁

4
我有一个列表视图展示一些图片。我正在使用通用图片加载器 Universal Image Loader 从文件中将这些图片加载到图像视图中。
这些图片具有不同的尺寸,我希望它们的宽度相同,但高度根据每个图片的纵横比而有所不同。
为了实现这一点,我尝试将以下内容设置为我的 ImageView。
<ImageView
   android:layout_width = "400dp"
   android:layout_height="wrap_content"
   android:scaleType="centerCrop"
   android:adjustViewBounds="true"/>

这种方法的问题在于,当滚动listview时会出现很多闪烁,因为imageview的高度事先不知道,必须先使用我的宽度对每个图像进行缩放,才能根据其纵横比计算出每个图像的高度
如何提前计算每个图像的高度,而不是让imageview处理呢?
如果我有一张400 X 700的图片,我希望imageview的宽度为300像素,如何使用我的图片尺寸计算imageview的高度并保持图像的纵横比?这可以帮助避免滚动listview时的闪烁。

为什么不设置固定高度呢?并使用一些动画,比如渐变淡入,使其更加流畅?比例由scaleType参数保持 :) - An-droid
@安卓 我不想设置固定高度。我试图实现ImageView在后台执行的操作。如果您设置了固定宽度并将高度设置为“wrap content”,它将缩放图像并计算高度。但是我想提前计算高度,这样用户在滚动图像时就不会看到ImageView的丑陋调整大小。 - Edijae Crusar
在像Spotify这样的知名应用程序中,大多数imageView都只设置了固定大小或matchparent,并将scaleType设置为centerCrop。它是做什么的?图像将填充您给予它的所有空间,并按比例调整大小以适应其高度。这很好,因为您不知道用户将在哪些设备上使用您的应用程序,使用哪种密度...使用固定宽度是有问题的,我认为这是不可取的。 - An-droid
@An-droid,你有没有使用过WhatsApp或其他可以发送图片的聊天应用程序?这些图片都能够很好地被调整大小,即使你发送了20张图片,也不会看到ImageView自动调整大小。一切都非常流畅。我正在尝试实现类似的功能。 - Edijae Crusar
5个回答

2
这种闪烁的原因在于,listview列表项被重复使用。当重新使用时,列表项中的图像视图保留了旧的图像引用,首先显示它。稍后一旦下载新图像,就开始显示。这会导致闪烁行为。 为避免此闪烁问题,在重新使用图像视图时,始终清除旧的图像引用。 在您的情况下,在“holder =(ViewHolder)convertView.getTag();”之后添加“holder.image.setImageBitmap(null);”。 因此,您的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;
}

这也是一个问题之一,但我的意思是当我向下滚动时,ImageView 的宽度为 400 像素,高度为 0,然后在计算出高度后,它突然出现。我想提前计算高度并将其设置为 ImageView,以避免 ImageView 的重新调整大小。 - Edijae Crusar
你明白我的关注吗? - Edijae Crusar
holder.setImageBitmap(null)与在通用图片加载器DisplayImageOptions()中使用.resetViewBeforeLoading(true)相同。 - Edijae Crusar

1
经过长时间的研究,我找到了一种方法,可以在保持图像宽高比的同时计算出新的ImageView高度。
    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);
 });

0
你可以像这样做:
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;

0

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

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

0
我是通过创建一个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);                  
       }
    }
}

感谢您的编辑,@CodyGray。我还是很新于发布答案。希望随着时间的推移我会变得更好。 - olushola.lana
没问题!你的回答已经很有帮助了。我和另一个用户刚刚清理了一些格式和英语语法。感谢你关心这个网站并尝试改进! - Cody Gray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接