RecyclerView 消失的图像

RecyclerView disappearing images

我想要创建的是一个水平滚动的图片库。我有一个 RecyclerView(支持 22.0.0)。我遇到的问题是,当我滚动到末尾然后向后滚动时,通常会丢失一张图像,有时会丢失两张。奇怪的是,当我不断来回滑动时,可能会丢失不同的图像。这是项目的布局:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="160dp">

<ImageView
    android:id="@+id/product_variation_image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:layout_gravity="center"/>

这是 Adaper:

public class TestAdapter extends RecyclerView.Adapter<TestAdapter.ViewHolder> {
private String[] mDataset;

public static class ViewHolder extends RecyclerView.ViewHolder {

    public ImageView mImageView;
    public ViewHolder(View v) {
        super(v);
        mImageView = (ImageView) v.findViewById(R.id.product_variation_image);
    }
}

public TestAdapter(String[] myDataset) {
    mDataset = myDataset;
}

@Override
public TestAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.variaton_list_item, parent, false);
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    holder.mImageView.setImageDrawable(null);
    String url = mDataset[position];
    Log.i("TEST", "position = " + position);
    ((MainActivity)MainActivity.getInstance()).imageDownloader.download(url, holder.mImageView);
}

@Override
public int getItemCount() {
    return mDataset.length;
}

下载方法从 URL 获取图像,如果已缓存,则从内存中获取图像。这适用于所有其他布局,例如列表视图或网格视图。这是我用来在片段中设置它的代码:

    final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    mRecyclerView.setLayoutManager(layoutManager);

这是在onCreateView方法中。当我得到 url 时,我填充它们并使用以下方法设置适配器:

  myDataset[i] = imageURL; // for each image       
  mAdapter = new TestAdapter(myDataset);
  mRecyclerView.setAdapter(mAdapter);

有趣的是适配器中 onBindViewHolder 方法中的一行,我在其中记录了位置。我发现未显示图像的单元格未调用此方法。就好像它出于某种原因正在跳过那个单元格。更奇怪的是,如果我按住一个单元格并从左向右滑动,如果一个单元格离开屏幕然后又回来,它的图像就会消失,因为不会调用 onBindViewHolder 方法。

是否可以测试一些东西?您可以使用此库从 URL 加载图像吗? http://square.github.io/picasso/ 它缓存所有内容并以异步方式处理所有内容。

使用类似...

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    Picasso.with(mImageView.getContext()).cancelRequest(holder.mImageView);

    String url = mDataset[position];
    Picasso.with(mImageView.getContext()).load(url).placeholder(R.drawable.placeholder).into(holder.mImageView);

}

...看看它是否仍然不显示某些图像。如果确实如此,那么至少您可以 100% 确定问题不在您的下载机制中(我认为可能是)。

如果你正在使用 Android Studio 那么只需添加依赖项 compile 'com.squareup.picasso:picasso:2.5.2',如果没有你可以添加你在上面找到的库 link.

值得一试...

我认为无关紧要的那个 class 是导致问题的那个。我不确定原因是什么,但它位于我从 BitmapFun 示例中获得的用于回收的自定义 ImageView class。

    public class RecyclingImageView extends ImageView {

    public RecyclingImageView(Context context) {
        super(context);
    }

    public RecyclingImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @see android.widget.ImageView#onAttachedToWindow()
     */
    @Override
    protected void onAttachedToWindow() {}

    /**
     * @see android.widget.ImageView#onDetachedFromWindow()
     */
    @Override
    protected void onDetachedFromWindow() {
        // This has been detached from Window, so clear the drawable

        setImageDrawable(null); 

        super.onDetachedFromWindow();
    }

    /**
     * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
     */
    @Override
    public void setImageDrawable(Drawable drawable) {
        // Keep hold of previous Drawable
        final Drawable previousDrawable = getDrawable();

        // Call super to set new Drawable
        super.setImageDrawable(drawable);

        // Notify new Drawable that it is being displayed
        notifyDrawable(drawable, true);

        // Notify old Drawable so it is no longer being displayed
        notifyDrawable(previousDrawable, false);
    }

    /**
     * Notifies the drawable that it's displayed state has changed.
     *
     * @param drawable
     * @param isDisplayed
     */
    private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
        if (drawable instanceof RecyclingBitmapDrawable) {
            // The drawable is a CountingBitmapDrawable, so notify it
            ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
        } else if (drawable instanceof LayerDrawable) {
            // The drawable is a LayerDrawable, so recurse on each layer
            LayerDrawable layerDrawable = (LayerDrawable) drawable;
            for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
                notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
            }
        }
    }

}

当我用普通的 ImageView 替换它时,我不再遇到问题。

我们可以通过扩展 LinearLayoutManager 和 ImageView 来解决这个问题。

1.创建一个 PrecachingLinearLayoutManager

public class PrecachingLinearLayoutManager extends LinearLayoutManager {

    private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600;

    private int extraLayoutSpace = -1;

    @SuppressWarnings("unused")
    private Context mContext;

    public PrecachingLinearLayoutManager(Context context) {
        super(context);
        this.mContext = context;
    }

    public PrecachingLinearLayoutManager(Context context, int extraLayoutSpace) {
        super(context);
        this.mContext = context;
        this.extraLayoutSpace = extraLayoutSpace;
    }

    public PrecachingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        this.mContext = context;
    }

    public void setExtraLayoutSpace(int extraLayoutSpace) {
        this.extraLayoutSpace = extraLayoutSpace;
    }

    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        if (extraLayoutSpace > 0) {
            return (extraLayoutSpace);
        }
        return (DEFAULT_EXTRA_LAYOUT_SPACE);
    }
}

2。使用 PrecachingLinearLayoutManager 替换 LinearLayoutManager

    DisplayMetrics displayMetrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    PrecachingLinearLayoutManager layout = new PrecachingLinearLayoutManager(getActivity());
    layout.setExtraLayoutSpace(displayMetrics.heightPixels);
    recyclerview.setLayoutManager(layout);

3。创建一个 RecycleImageView

private Object tag = null;

@Override
protected void onAttachedToWindow() {
    Object tag = getTag();
    if (tag == null || !tag.equals(this.tag)) {
        // Will cause displayed bitmap wrapper to 
        // be 'free-able'
        setImageDrawable(null);
        this.tag = null;
        super.onDetachedFromWindow();
    }
    super.onAttachedToWindow();
}

@Override
protected void onDetachedFromWindow() {
    Object tag = getTag();
    if (tag != null) {
        this.tag = tag;
    } else {
        // Will cause displayed bitmap wrapper to 
        // be 'free-able'
        setImageDrawable(null);
        this.tag = null;
        super.onDetachedFromWindow();
    }
}

4.使用RecycleImageView替换ImageView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:extends="http://schemas.android.com/apk/res/com.yourdomain.yourpackage"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/viewgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.yourdomain.yourpackage.RecycleImageView
    android:id="@+id/photo"
    android:layout_width="40dp"
    android:layout_height="40dp"
    extends:delayable="true"
    android:contentDescription="@string/nothing"
    android:src="@drawable/photo_placeholder" >
</com.yourdomain.yourpackage.RecycleImageView>
</LinearLayout>