调用NotifyItemChanged时RecyclerView项丢失无障碍焦点排查
notifyItemChanged() 绑定完成后影响无障碍焦点的后续逻辑 onBindViewHolder执行完成不代表整个item变更流程结束,后续还有几类逻辑会直接触发无障碍焦点异常跳转:
- 无障碍内容变更事件派发
重绑完成后RecyclerView会向系统无障碍服务发送TYPE_WINDOW_CONTENT_CHANGED事件,标记变更类型为子树更新。如果当前无障碍焦点正落在被更新的item上,无障碍服务会默认判定原焦点依附的节点已经失效,自动启动全局焦点搜索:默认搜索顺序是从视图根节点自上而下遍历,选中第一个可聚焦的无障碍节点,直接把焦点移到屏幕顶部的可聚焦元素上。 - 默认ItemAnimator的视图状态切换
系统默认的DefaultItemAnimator处理change类型更新时,会在bind完成后给旧item视图加淡出动画、给新绑定的item视图加淡入动画。动画过程中旧的持有焦点的视图会被临时设为INVISIBLE状态,甚至短暂从视图树中移除,这个状态变化会直接触发系统清除该视图的无障碍焦点;动画结束后新的item视图不会自动承接焦点,系统就会按默认规则把焦点分配给顶层可聚焦节点。 - RecyclerView内部焦点校验修正
重绑结束后RecyclerView会执行一次布局合法性校验,如果检测到当前持有焦点的view在更新后不在可见范围内、或者view重绑后isFocusable()返回值发生变化,会主动触发子View焦点查找,默认会选中当前可见区域内最靠上的可聚焦子View,也就是屏幕顶部的item。 - 无障碍节点树重建逻辑
RecyclerView自带的无障碍代理在收到item变更通知后,会重新构建当前可见区域所有item的无障碍节点信息。如果重绑后的itemimportantForAccessibility属性被修改、或者item内部可聚焦控件的id发生了变化,无障碍服务会判定原焦点对应的节点已不存在,自动重置焦点到节点树的第一个有效节点。
快速验证方案:
- 调用
recyclerView.setItemAnimator(null)禁用默认change动画后复现问题,如果焦点不再跳转,即可定位为ItemAnimator的视图状态切换导致的焦点丢失- 调用
notifyItemChanged(position, 固定payload)做局部更新,避免整个item子树被判定为全量变更,减少无障碍事件的影响范围- 重写RecyclerView的
onRequestFocusInDescendants方法,拦截默认的顶层焦点查找逻辑,优先将焦点分配给更新前持有焦点的position对应的View
内容的提问来源于stack exchange,提问作者Hasan Syed
相关产品推荐
相关产品推荐

