【问题标题】:RecyclerView with Images blocking the UI带有图像的 RecyclerView 阻塞了 UI
【发布时间】:2017-12-02 02:44:06
【问题描述】:

我有一个 recyclerview 加载一组主要显示图像的项目。我在后台检索这些项目,每批 100 个。我使用 Picasso 加载图像。图片很大,但我使用 fit() 调整它们的大小。

每当使用 SwipeRefreshLayout 加载或刷新屏幕时,UI 都会阻塞不到一秒,但足以引起注意。如果我不加载图像而只放置文本,则不会发生 UI 块。

我在 Picasso 上放置了日志行,每次刷新都会检索 100 张图像,但我猜 Picasso 是在后台线程中工作?

适配器:

@ActivityScope
class LimitableListAdapter @Inject constructor() : RecyclerView.Adapter<LimitableListAdapter.ViewHolder>() {

    private var events: MutableList<Event> = mutableListOf()
    private var itemClick: ((Event, View) -> Unit)? = null


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        val binding : ItemVideoGridScoreBinding = holder.binding

        var viewModel = binding.viewModel
        val event = events[position]

        //Unbind old viewModel if we have one
        viewModel?.unbind()

        // Create new ViewModel, set it, and bind it
        viewModel = EventViewModel(event)
        binding.viewModel = viewModel
        viewModel.bind()


        holder.setClickListener(itemClick)
    }

    override fun getItemCount(): Int = events.size


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = DataBindingUtil.inflate<ItemVideoGridScoreBinding>(
                LayoutInflater.from(parent.context),
                R.layout.item_video_grid_score,
                parent,
                false
        )
        return ViewHolder(binding)
    }


    fun updateEvents(events: List<Event>, stride: Int) {
        var size = this.events.size
        Timber.w("Updating with: " + events.joinToString(",", transform = { e -> e.id.toString() }))
        this.events = events.toMutableList()
        notifyDataSetChanged()
        /*if (size == 0) {
            Timber.w("branch 1")
            var mutableList = events.toMutableList()
            if(mutableList.size == 0)
                return
            mutableList.add(Event.mockEvent(stride))
            this.events.addAll(mutableList)
            notifyDataSetChanged()
        } else {
            if (size > 2) {
                Timber.w("branch 2.1")
                this.events.addAll(size - 1, events.toMutableList())
                notifyItemRangeChanged(size-1, events.size)
            }
            else {
                Timber.w("branch 2.2")
                this.events.addAll(size, events.toMutableList())
                notifyItemRangeChanged(size, events.size)
            }

        }*/
        Timber.i("New list is: " +  this.events.joinToString(",", transform = { e -> e.id.toString() }))
    }

    fun clearList(){
        this.events.clear()
        notifyDataSetChanged()
    }

    fun setClickListener(itemClick: ((Event, View) -> Unit)?) {
        this.itemClick = itemClick
    }


    class ViewHolder(val binding: ItemVideoGridScoreBinding) : RecyclerView.ViewHolder(binding.root) {
        fun setClickListener(callback: ((Event, View) -> Unit)?) {
            binding.viewModel.clicks().subscribe() {
                callback?.invoke(binding.viewModel.event, itemView)
            }
        }
    }

}

BindingUtils:

@BindingAdapter({"app:coverUrl"})
    public static void loadCover(ImageView view, String imageUrl) {

            Picasso p = Picasso.with(view
                    .getContext());
                    p.setIndicatorsEnabled(true);

                    p.load(imageUrl)
                    .fit()
                    .centerInside()
                    .error(R.drawable.ic_broken_image)
                    .into(view);
        }
    }

xml:

(...)
     <ImageView
                    android:id="@+id/event_cover"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    app:coverUrl="@{videoItem.cover}"
                    tools:src="@drawable/img"
                    />
(...)

【问题讨论】:

  • 输入你的适配器代码
  • @diegoveloper 发布
  • 同时检索100张图片并通知ui是一项繁重的工作。我也不认为您同时显示所有这些图像。所以可以考虑实现分页。
  • 即使每页有 20 个,也会发生 UI 阻塞...
  • 用 Glide 代替 Picasso Glide 比 Picasso 更快更高效。 Glide 性能比较在这里。 medium.com/@multidots/glide-vs-picasso-930eed42b81d看看性能有没有提升。

标签: android android-recyclerview picasso


【解决方案1】:

假设您无法在服务器端创建缩略图(这将是最简单的解决方案),我的建议是使用以下方法之一

  1. 使用本地可绘制对象作为占位符。这不会阻塞 UI 线程并且图像可以在后台加载。行为将类似于加载图像网格时 Instagram 的行为。如下所示。

p.load(imageurl).placeholder(R.drawable.localFile).fit().centerInside().error(R.drawable.ic_broken_image).into(view)

  1. 使用滑行。 Glide 确实具有创建缩略图的强大功能。并且您可以比完整图像更快地将缩略图加载到视图中。

【讨论】:

  • 最终这样做了。 Glide 确实比 Picasso 快很多
【解决方案2】:

调整几张大图片的大小需要一些时间。虽然 Picasso 对调整后的图像进行缓存,但第一次它仍然需要调整每个图像的大小(随后它只会使用缓存中调整大小的图像,因此花费的时间更少)。 此外,使用fit 可能比仅使用resize 花费更多时间,因为它需要根据布局计算大小。如果您可以自己计算静态大小(只需一次),然后使用resize,它应该会缩短加载时间。

但大多数情况下,如果您的服务器可以在预览时向您发送较小的图像/缩略图,然后如果您想在某个细节屏幕上看到它,则发送一个完整尺寸的图像会更好。

【讨论】:

  • 这帮助很大,但毕加索仍然在挣扎,Glide 是一个进步!
【解决方案3】:

当您加载 100 张图片并且您提到图片很大时, 所以它肯定会阻塞 UI 线程,如果你想避免这种情况,你可以resize Picasso 的图像:

Picasso.with(context)
        .load(-yourImgURL-)
        .resize(450, 200) // resizes image to desired dimensions here
        .into(-yourImageView);

【讨论】:

  • 谢谢,正如您在我的代码 sn-p 中看到的那样,我正在使用 fit 来有效地调整图像大小。
猜你喜欢
  • 2019-08-11
  • 2018-02-17
  • 1970-01-01
  • 2013-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多