You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android开发:在RecyclerView适配器中判断GIF项是否完全可见及失焦

嘿,这个需求挺实用的,刚好我之前做过类似的功能,给你分享下具体实现思路和代码~

实现RecyclerView中GIF的可见性触发加载/停止

核心思路是通过RecyclerView滚动监听+LayoutManager的可见性判断方法,跟踪每个GIF item的可见状态,进而精准控制加载和停止逻辑。以下是具体步骤:


1. 自定义滚动监听器:实时跟踪完全可见的Item

我们需要一个OnScrollListener,在滚动过程中(包括滚动状态变化、滚动位移时),实时获取当前完全可见的Item范围,然后通知对应的ViewHolder启动/停止GIF加载。

class GifScrollListener(
    private val recyclerView: RecyclerView,
    private val adapter: GifAdapter
) : RecyclerView.OnScrollListener() {

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        // 不管是滚动中、停止还是拖拽状态,都检查可见状态
        updateGifLoadingState()
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        updateGifLoadingState()
    }

    private fun updateGifLoadingState() {
        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val firstFullVisiblePos = layoutManager.findFirstCompletelyVisibleItemPosition()
        val lastFullVisiblePos = layoutManager.findLastCompletelyVisibleItemPosition()

        // 没有完全可见的Item时直接返回
        if (firstFullVisiblePos == -1 || lastFullVisiblePos == -1) return

        // 给所有完全可见的Item启动GIF加载
        for (pos in firstFullVisiblePos..lastFullVisiblePos) {
            recyclerView.findViewHolderForAdapterPosition(pos)?.let { holder ->
                (holder as GifAdapter.GifViewHolder).startGifLoading()
            }
        }

        // 停止那些之前在加载但现在不再完全可见的Item
        val iterator = adapter.currentLoadingPositions.iterator()
        while (iterator.hasNext()) {
            val pos = iterator.next()
            if (pos < firstFullVisiblePos || pos > lastFullVisiblePos) {
                recyclerView.findViewHolderForAdapterPosition(pos)?.let { holder ->
                    (holder as GifAdapter.GifViewHolder).stopGifLoading()
                }
                iterator.remove()
            }
        }
    }
}

2. 适配器与ViewHolder:封装加载逻辑

在Adapter中维护一个集合记录当前正在加载的Item位置,同时在ViewHolder中实现启动/停止加载的方法,还要处理ViewHolder回收时的资源释放,避免内存泄漏。

class GifAdapter(private val gifUrls: List<String>) : RecyclerView.Adapter<GifAdapter.GifViewHolder>() {

    // 记录正在加载GIF的Item位置,避免重复操作
    val currentLoadingPositions = mutableSetOf<Int>()

    inner class GifViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val gifImageView: ImageView = itemView.findViewById(R.id.gif_image)
        // 这里以Glide为例,替换成你实际使用的加载库即可
        private var glideRequest: RequestBuilder<Drawable>? = null

        fun bind(position: Int) {
            // 初始设置占位图,不加载GIF
            gifImageView.setImageResource(R.drawable.gif_placeholder)
        }

        fun startGifLoading() {
            val position = adapterPosition
            if (currentLoadingPositions.contains(position)) return

            val gifUrl = gifUrls[position]
            // 启动GIF加载
            glideRequest = Glide.with(itemView.context)
                .asGif()
                .load(gifUrl)
                .into(gifImageView)
            currentLoadingPositions.add(position)
        }

        fun stopGifLoading() {
            // 停止GIF加载并重置占位图
            glideRequest?.clear(gifImageView)
            gifImageView.setImageResource(R.drawable.gif_placeholder)
            currentLoadingPositions.remove(adapterPosition)
        }

        // ViewHolder被回收时,必须停止加载
        override fun onViewRecycled() {
            super.onViewRecycled()
            stopGifLoading()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GifViewHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_gif, parent, false)
        return GifViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: GifViewHolder, position: Int) {
        holder.bind(position)
        // 绑定的时候如果Item已经完全可见,直接启动加载
        val layoutManager = (holder.itemView.parent as RecyclerView).layoutManager as LinearLayoutManager
        if (layoutManager.isViewCompletelyVisible(holder.itemView)) {
            holder.startGifLoading()
        }
    }

    override fun getItemCount() = gifUrls.size
}

3. 初始化RecyclerView:绑定监听器

在你的Activity或Fragment中,给RecyclerView设置适配器和滚动监听器即可:

val recyclerView = findViewById<RecyclerView>(R.id.rv_gif_list)
val gifUrls = listOf("你的GIF链接1", "你的GIF链接2", "...")
val adapter = GifAdapter(gifUrls)

recyclerView.apply {
    this.adapter = adapter
    layoutManager = LinearLayoutManager(this@MainActivity)
    addOnScrollListener(GifScrollListener(this, adapter))
}

额外注意事项

  • 如果使用GridLayoutManager,只需要把代码中的LinearLayoutManager替换成GridLayoutManager即可,它同样提供完全可见位置的判断方法。
  • 如果你用的是其他GIF加载库(比如Picasso、Fresco),只需要替换startGifLoading()和stopGifLoading()中的逻辑,确保库支持暂停/恢复加载。
  • 若需要调整“触发加载”的可见度阈值(比如Item显示70%以上才加载),可以自定义可见区域占比的计算方法,替换默认的完全可见判断。

内容的提问来源于stack exchange,提问作者Hiren Shah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:22:19