【问题标题】:images keep changing repeatedly when using a recycled view in a custom adapter - android在自定义适配器中使用回收视图时,图像不断变化 - android
【发布时间】:2018-05-22 12:09:42
【问题描述】:

我的自定义适配器有以下代码,其中每一行由一个名称和一张图片组成,图片取自 firebase 存储:

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageException;
import com.google.firebase.storage.StorageReference;
import com.squareup.picasso.Picasso;

import java.util.List;

public class CustomAdapter extends BaseAdapter {

    Context context;
    List<RowItem> rowItems;


    CustomAdapter(Context context, List<RowItem> rowItems) {
        this.context = context;
        this.rowItems = rowItems;
    }

    @Override
    public int getCount() {
        return rowItems.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

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

    /* private view holder class */
    private class ViewHolder {
        ImageView profile_pic;
        TextView member_name;

    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        final RowItem row_pos = rowItems.get(position);

        final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child(row_pos.getFirebaseUserUid()+".jpg");




        //if (convertView == null) {
        LayoutInflater mInflater = LayoutInflater.from(context);
        convertView = mInflater.inflate(R.layout.list_item, parent, false);


        holder = new ViewHolder();

        holder.member_name = convertView
                .findViewById(R.id.member_name);
        holder.profile_pic = convertView
                .findViewById(R.id.profile_pic);





        //convertView.setTag(holder);


        /*} else {
        holder = (ViewHolder) convertView.getTag();

        }*/

        holder.member_name.setText(row_pos.getName());



        storageReference.getDownloadUrl().addOnSuccessListener(new 
OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri uri) {





 Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            int errorCode = ((StorageException) exception).getErrorCode();
            if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
            }
        }
    });


    return convertView;
    }

}

现在,如果我这样运行它,它会加载每个新行,因此滚动不会像我希望的那样平滑。 为了克服这个问题,许多帖子建议添加以下内容(基本上是上面代码中标记为 cmets 的内容):

if (convertView == null){
    /.../
    convertView.setTag(holder);
}

} else {
      holder = (ViewHolder) convertView.getTag();
}

但是当我这样做时,在列表项的 profilePic 视图中,它会以非常快的速度一个接一个地显示许多图像。

任何人都可以帮助检测问题并提出任何解决方案吗?

这是该错误的视频:https://vimeo.com/271555362

【问题讨论】:

  • 使用 RecyclerView 代替 Listview
  • 还有其他选择吗?我不想重写很多代码
  • 在Adapter中请求URL的问题是,当视图(行)被复用时,需要手动停止对Firebase的请求。这取决于您拥有多少项目以及检索 url 需要多长时间,但您可以在检索 url 时显示一个带有进度条的空列表,然后在您拥有所有元素时显示该列表。
  • 在实现任何东西之前,您需要了解 ListView 的工作原理(特别是它的适配器)。 Adapter 有一定数量的视图,当你滚动时复用它们来显示数据。例如,如果您的列表中有 100 个项目,但您只能同时看到其中的 10 个,则列表视图将尝试仅管理 10 个视图。如果您没有正确编写适配器并在每次调用 getView 时创建一个新视图,则对未使用的视图或内容发生更改时执行操作,您将以泄漏和缓慢的 UI 告终。
  • 这就是@firegloves 在谈到“回收过程”时的意思。所以我建议你阅读一些关于实现 ListView 的最佳实践的文档。如上所述,最好还是使用 RecyclerView

标签: android firebase firebase-storage custom-adapter


【解决方案1】:

我认为问题在于 Eselfar 指出的问题。 当您请求 uri 到 firebase 时,它​​应该在异步线程上完成。这意味着在检索迭代 n 的 url 的同时,适配器继续在您的列表上进行迭代。

这 2 个流程同时执行,因此持有者变量不断重新分配,可能直到迭代到达其最后一个可见项(视图)。

这导致了这种情况:

for each view {
   request image url       (for example 2 seconds)
   assign holder variable  (for example 0.0002 seconds)
}

在检索到第一个图像 url 的同时(2 秒)适配器已经结束了迭代。 所以 holder 变量现在具有相同的值,即整个迭代的最后一个值。 第一个图像 url 2 秒后(其余图像 url 以此类推)进入 OnSuccessListener.onSuccess 方法,它使用 Glide 设置 holder.profile_pic 变量上的图像。但是,如前所述,该变量现在属于最后一次完成的迭代。

所以你的问题是你混合了同步和异步方法。

一种可能的解决方案是首先检索所有图像 url,然后删除该异步任务 (storageReference.getDownloadUrl()) 并在同一个 UIThread 中执行所有操作。

我认为最简单的解决方案(但可能不是最好的)是像这样创建一个自定义 OnSuccessListener 和一个自定义 OnFailureListener:

public class MySuccessListener extends OnSuccessListener {


          Holder holder;

            public MySuccessListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onSuccess(Uri uri) {
                Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
          }
}

public class MyFailureListener extends OnFailureListener {


          Holder holder;

            public MyFailureListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onFailure(@NonNull Exception exception)
                int errorCode = ((StorageException) exception).getErrorCode();
                 if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                    Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
                 }
          }
}

请注意,您将当前持有者传递给您的侦听器构造函数,因此当异步任务结束时,您可以定位正确的持有者。 然后你可以像这样更新你的代码:

storageReference.getDownloadUrl().addOnSuccessListener(new MyFailureListener<Uri>(holder))
                            .addOnFailureListener(new MyFailureListener(holder));

现在我无法将其测试到 IDE 中,我的代码可能不精确,但基本上你应该理解建议的方式。

请注意,您使用的是 Picasso 和 Glide。这两个库做同样的事情,我建议你使用其中一个并删除不必要的依赖。

【讨论】:

  • 感谢您的回复。您的第一个解决方案-我没有找到任何方法来阻止 storageReference.getDownloadUrl() 异步任务,我尝试的任何方法似乎都不起作用...至于其他解决方案,我真的看不到这样做的方法...也许你有什么想法吗?提前致谢
  • storageReference.getDownloadUrl() 包含什么?指向图片的链接,例如 xxx.yyy/image.jpg?
  • 是的,正是你所说的
猜你喜欢
  • 2016-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 2020-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多