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

WindowManager内RecyclerView submitList视图不更新问题

RecyclerView挂载到WindowManager后submitList不刷新问题解决

问题现象

实现应用内全局通知悬浮层时,选择将RecyclerView直接添加到WindowManager上展示应用级重要事件通知,初始加载状态正常,但存在列表更新失效问题:

  • 调用show()方法时,预置的not1、not2两条初始通知可正常展示
  • 调用removeNotification方法时,通知对象已从数据源移除,也调用了适配器的submitList提交更新后列表,但屏幕视图未同步刷新
  • 如果在submitList后追加windowManager.updateViewLayout(recyclerView, layoutParams),可强制刷新视图,但会完全丢失RecyclerView内置的动画效果

初始实现代码如下:

class NotificationOverlay(private val context: Context) {

    private val windowManager: WindowManager =
        context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

    private val layoutParams = WindowManager.LayoutParams().apply {
        gravity = android.view.Gravity.TOP or android.view.Gravity.CENTER_HORIZONTAL
        width = WindowManager.LayoutParams.MATCH_PARENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        format = PixelFormat.TRANSLUCENT
        dimAmount = 0.5f
        flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
        type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
    }

    private val notifications = mutableListOf<NotificationItem>().also {
        it.addAll(listOf(
            NotificationItem(title = "not 1", message = "first notification"),
            NotificationItem(title = "not 2", message = "second notification")
        ))
    }
    private val notificationsAdapter = NotificationAdapter(::removeNotification)
    private val recyclerView = RecyclerView(context).apply {
        layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        adapter = notificationsAdapter
    }


    private fun removeNotification(notification: NotificationItem){
        notifications.remove(notification)
        notificationsAdapter.submitList(notifications)
        if(notificationsAdapter.currentList.isEmpty()){
            windowManager.removeView(recyclerView)
        }
    }

    fun show(){
        windowManager.addView(recyclerView, layoutParams)
        notificationsAdapter.submitList(notifications)
    }
}

初步排查

最初怀疑是WindowManager的使用逻辑问题,后续排查排除了这个方向,将问题定位到DiffUtil相关逻辑,但自定义的DiffCallback是多次使用的标准实现,未发现明显错误,适配器代码如下:

class NotificationAdapter(private val onCloseClicked: (NotificationItem) -> Unit):
    ListAdapter<NotificationItem, NotificationAdapter.NotificationViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
        val binding = ItemNotificationOverlayBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return NotificationViewHolder(binding, onCloseClicked)
    }

    override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
        holder.bind(currentList[position])
    }

    class NotificationViewHolder(private val itemBinding: ItemNotificationOverlayBinding, private val onCloseClicked: (NotificationItem) -> Unit): RecyclerView.ViewHolder(itemBinding.root) {
        fun bind(item: NotificationItem) {
            itemBinding.title.text = item.title
            itemBinding.message.text = item.message
            itemBinding.close.setOnClickListener {
                onCloseClicked.invoke(item)
            }
        }
    }

    class DiffCallback : DiffUtil.ItemCallback<NotificationItem>() {
        override fun areItemsTheSame(oldItem: NotificationItem, newItem: NotificationItem) =
            oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: NotificationItem, newItem: NotificationItem) =
            oldItem == newItem
    }
}

排查到这一步时一度打算放弃DiffUtil,改回传统的notifyItemRemoved方法实现列表更新。

根因定位

最终确认问题和WindowManager完全无关,是AsyncListDiffer的使用误区:
androidx.recyclerview.widget.AsyncListDiffer的submitList方法开头存在引用相等校验逻辑:

if (newList == mList) {
    // nothing to do (Note - still had to inc generation, since may have ongoing work)
    if (commitCallback != null) {
        commitCallback.run();
    }
    return;
}

之前的实现中,每次提交给submitList的都是同一个notifications可变列表对象,和Differ内部持有的列表引用完全一致,因此差异计算流程直接被跳过,不会触发视图刷新。

修复方案

调用submitList时传入原列表的新副本即可,在提交参数后追加toList()调用,创建新的列表实例:

// 修改removeNotification中的提交逻辑
private fun removeNotification(notification: NotificationItem){
    notifications.remove(notification)
    // 传入列表副本,触发diff计算
    notificationsAdapter.submitList(notifications.toList())
    if(notificationsAdapter.currentList.isEmpty()){
        windowManager.removeView(recyclerView)
    }
}

// show方法中的初始提交也统一使用该写法
fun show(){
    windowManager.addView(recyclerView, layoutParams)
    notificationsAdapter.submitList(notifications.toList())
}

toList()会基于原列表创建新的ArrayList实例,和原列表引用不同,可正常触发Differ的差异计算,同时保留RecyclerView的内置动画效果。

注意:使用ListAdapter+AsyncListDiffer时,禁止直接提交可变的原列表对象,必须每次传入新的列表实例,否则会因为引用相等校验直接跳过diff流程,导致列表不更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 12:31:09