web-dev-qa-db-ja.com

RecyclerViewアダプターが誤った画像を表示する

次のようなRecyclerViewアダプターがあります。

_public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    private static Context context;
    private List<Message> mDataset;

    public RecyclerAdapter(Context context, List<Message> myDataset) {
        this.context = context;
        this.mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
        public TextView title;
        public LinearLayout placeholder;

        public ViewHolder(View view) {
            super(view);
            view.setOnCreateContextMenuListener(this);

            title = (TextView) view.findViewById(R.id.title);
            placeholder = (LinearLayout) view.findViewById(R.id.placeholder);
        }
    }

    @Override
    public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
        ViewHolder vh = new ViewHolder((LinearLayout) view);

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Message item = mDataset.get(position);

        holder.title.setText(item.getTitle());

        int numImages = item.getImages().size();

        if (numImages > 0) {
            View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
            ImageView image = (ImageView) test.findViewById(R.id.image);
            Glide.with(context)
                .load("http://www.website.com/test.png")
                .fitCenter()
                .into(image);
            holder.placeholder.addView(test);
        }
    }

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

}
_

ただし、RecyclerViewの一部のアイテムには、本来表示されるべきでない画像が表示されています。これを防ぐにはどうすればよいですか?

私はif (numImages > 0) {onBindViewHolder()のチェックを行っていますが、それでも画像を含める必要のないアイテムの画像を表示することはできます。

17
user5578746

グライドを使用して画像を設定する前に、imageView.setImageDrawable (null)onBindViewHolder()に設定する必要があります。

Image drawableをnullに設定すると、問題が修正されます。

それが役に立てば幸い!

18

問題はここのonBindViewHolderにあります:

_    if (numImages > 0) {
        View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
        ImageView image = (ImageView) test.findViewById(R.id.image);
        Glide.with(context)
            .load("http://www.website.com/test.png")
            .fitCenter()
            .into(image);
        holder.placeholder.addView(test);
    }
_

NumImagesが0に等しい場合は、以前に開始した、再利用しているビューへのロードを続行できるようにするだけです。完了すると、古い画像がビューに読み込まれます。これを防ぐには、clearを呼び出して前のロードをキャンセルするようにGlideに指示します。

_   if (numImages > 0) {
        View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
        ImageView image = (ImageView) test.findViewById(R.id.image);
        Glide.with(context)
            .load("http://www.website.com/test.png")
            .fitCenter()
            .into(image);
        holder.placeholder.addView(test);
    } else {
        Glide.clear(image);
    }
_

into()を呼び出すと、Glideは古いロードのキャンセルを処理します。 into()を呼び出さない場合は、clear()を自分で呼び出す必要があります。

OnBindViewHolderへのすべての呼び出しには、load()呼び出しまたはclear()呼び出しのいずれかを含める必要があります。

13
Sam Judd

RecyclerViewで間違った画像が表示される問題もありました。これは、RecyclerViewがすべての新しいリストアイテムのビューを拡大するのではなく、リストアイテムがリサイクルされているために発生します。

ビューをリサイクルすることで、複製ビューを大まかに理解できます。複製されたビューには、前の対話からの画像セットが含まれる場合があります。

これは、非同期読み込みにPicasso、Glide、またはその他のlibを使用している場合に特に適切です。これらのライブラリはImageViewへの参照を保持し、画像が読み込まれるときにその参照に画像を設定します。

画像が読み込まれるまでに、アイテムビューは複製されている可能性があり、画像は間違った複製に設定されます。

簡単に言うと、RecyclerViewがアイテムビューのクローンを作成しないように制限することで、この問題を解決しました。

setIsRecyclable(false) in ViewHolderコンストラクタ。

RecyclerViewの動作は少し遅くなりましたが、少なくとも画像は正しく設定されています。

または、onViewRecycled(ViewHolder holde)に画像をロードすることをキャンセルできます

8

ここでの問題は、リサイクルされるビューで作業しているときに、ビューをバインドするときに考えられるすべてのシナリオを処理する必要があることです。

たとえば、データソースの位置0でLinearLayoutにImageViewを追加する場合、位置4が条件を満たさない場合、位置0をバインドするときにビューのImageViewが追加される可能性が高くなります。

R.layout.imagesコンテンツのコンテンツを

R.layout.message_layoutレイアウトのR.id.placeholderで、ケースに応じてプレースホルダーを表示/非表示にします。

したがって、onBindViewHolderメソッドは次のようになります。

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Message item = mDataset.get(position);

        holder.title.setText(item.getTitle());

        int numImages = item.getImages().size();

        if (numImages > 0) {
            holder.placeholder.setVisivility(View.VISIBLE);
            ImageView image = (ImageView)holder.placeholder.findViewById(R.id.image);
            Glide.with(context)
                .load("http://www.website.com/test.png")
                .fitCenter()
                .into(image);
        }else{
           holder.placeholder.setVisibility(View.INVISIBLE);
        }
    }
3
mmark

RecyclerViewを使用すると、ビューが再利用され、現在の位置で変更される前の位置のサイズが保持される場合があります。これらのケースを処理するために、新しい[ViewTargetを作成し、waitForLayoutにtrueを渡す]ことができます。

@Override
public void onBindViewHolder(VH holder, int position) {
  Glide.with(fragment)
    .load(urls.get(position))
    .into(new DrawableImageViewTarget(holder.imageView,/*waitForLayout=*/ true));

https://bumptech.github.io/glide/doc/targets.html

2
Pouya Khalilzad

これは私にとってonBindViewHolderで機能します!

if(!m.getPicture().isEmpty())
{
    holder.setIsRecyclable(false);
    Picasso.with(holder.profile_pic.getContext()).load(m.getPicture()).placeholder(R.mipmap.ic_launcher_round).into(holder.profile_pic);
    Animation fadeOut = new AlphaAnimation(0, 1);
    fadeOut.setInterpolator(new AccelerateInterpolator());
    fadeOut.setDuration(1000);
    holder.profile_pic.startAnimation(fadeOut);
}
else
{
    holder.setIsRecyclable(true);
}
1
Pospai

私は同じ問題を抱えていたので、次のように修正しました:

GOAL:onViewAttachedToWindow

@Override
public void onViewAttachedToWindow(Holder holder) {
    super.onViewAttachedToWindow(holder);
    StructAllItems sfi = mArrayList.get(position);   
    if (!sfi.getPicHayatParking().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicHayatParking() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicSleepRoom().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSleepRoom() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicSalonPazirayi().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSalonPazirayi() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
    if (!sfi.getPicNamayeStruct().isEmpty()) {
        holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicNamayeStruct() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
    }
}
0
Hadi Note

フォトギャラリーから画像を取得し、GridLayoutManagerを使用してリサイクルビューに配置するときにも同様の問題がありました(Glideには問題がありませんでした)。そのため、アダプターでは、onBindViewHolderを使用してHashMapまたはSparseIntArrayを使用し、現在のハッシュコード(これはリサイクルされたビューに共通するものです)とアダプター内の位置を配置します。次に、バックグラウンドタスクを呼び出し、完了したら、画像を設定する前に、ハッシュコードキー(常に現在のアダプター位置が値として保持されます)が、最初と同じ値(アダプター位置)であるかどうかを確認します。バックグラウンドタスクと呼ばれます。

(Global variable)
   private SparseIntArray hashMap = new SparseIntArray();

onBindViewHolder(ViewHolder holder, int position){

   holder.imageView.setImageResource(R.drawable.grey_square);
   hashMap.put(holder.hashCode(), position);
   yourBackgroundTask(ViewHolder holder, int position);
}

yourBackGroundTask(ViewHolder holder, int holderPosition){

   do some stuff in the background.....
      *if you want to stop to image from downloading / or in my case 
       fetching the image from MediaStore then do - 
         if(hashMap.get(holder.hashCode())!=(holderPos)){
                 return null;
             } 
         - in the background task, before the call to get the 
           image

   onPostExecute{
      if(hashMap.get(holder.hashCode())==(holderPosition)){
         holder.imageView.setImageBitmap(result);
      }
   }
}
0
BrianG7

私も同じ問題を抱えていて、以下の解決策で終わりましたが、それは私にとってはうまくいきました。

この解決策を手に入れてもうまくいくかもしれません(アダプタークラスのコードの下に配置してください)。

Kotlinを使用している場合-

override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getItemViewType(position: Int): Int {
        return position
    }

Java-を使用している場合

 @Override
    public long getItemId(int position) {
        return position;
    }

@Override
    public int getItemViewType(int position) {
        return position;
    }
0
Bajrang Hudda